Returning Strings to VBA - Is this the right way ?

Just starting out? Need help? Post your questions and find answers here.
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

Hi,

Using the SysAllocString() api, we can return a pointer to a BSTR that VBA (32 and 64 bits) almost understands as one of its strings.

PB DLL sample:

Code: Select all

EnableExplicit
DisableDebugger

#APP_VERSION = "1.0.0"

ProcedureDLL.i GetVersion()
  ProcedureReturn SysAllocString_("This is Version " + #APP_VERSION)
EndProcedure
VBA Calling code in a module (here an Access one):

Code: Select all

Option Compare Database
Option Explicit

Private Declare PtrSafe Function GetVersion Lib "ReturnStringTest.dll" () As String

Sub Main()
  'Sample in Access VBA, change directory where DLL is or place DLL in PATH
  Dim sVersion As String
  ChDir CurrentProject.Path
  sVersion = GetVersion()
  Debug.Print sVersion, Asc(Mid$(sVersion, 2, 1))
  Debug.Print StrConv(sVersion, vbFromUnicode)
End Sub
So "almost", because if I don't use StrConv(), each character in the string has an extra 0 (zero) after it.
That's ok with me, but my worry is that the BSTR allocated and returned by SysAllocString(), if not freed by VBA, could cause a difficult to spot memory leak.

Do you know if that is the case or if VBA does the necessary disposal in all cases ?
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5357
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Returning Strings to VBA - Is this the right way ?

Post by Kwai chang caine »

Hello franchsesko and welcome in the paradise of PureBasic 8)

Do you know COMATEPLUS of the great SROD, for all the thing to do with the OLE and COM ?

viewtopic.php?f=14&t=37214&hilit=comateplus
ImageThe happiness is a road...
Not a destination
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Re: Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

Hi kwai,

Thanks for jumping into this topic.
Actually yes, I've seen COMate, great work indeed, but I left it aside as I'm writing a non COM Dll (although I rudely borrow SysAllocString() from it).
I'll dig into it, but if you can point me where you know there's some code that can help me, it may save me some headache.

All the best.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5357
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Returning Strings to VBA - Is this the right way ?

Post by Kwai chang caine »

Apparently, when i see your last code in the other section, i'm not a big master like you :oops:

But me too, one of my interest there is a long time, is talking between PB and VB6 :wink:
And the great Masters SROD, TSSOFT, KIFFY, MKSOFT (COM specialists) and RENDFIELD (VBFRANCE Moderator) help me numerous time for do that, because CROSOFT have really the talent for complicating the simple things (BSTR, SafeArray, etc ..) :|

I don't remember if all works, but i have some codes of this period, perhaps one of him help you :oops:
This is one :D

Code: Select all

; http://www.purebasic.fr/english/viewtopic.php?p=295292#p295292
; Edwin Knoppert 
ProcedureDLL GetDirectString2()
 ProcedureReturn SysAllocStringByteLen_("DLL MESSAGE IS OKAY!", 20)
EndProcedure

; http://www.purebasic.fr/english/viewtopic.php?p=295295#p295295
; Edwin Knoppert 
ProcedureDLL.I GetDirectString3() 
    Protected tin.s
    Protected tout.s

    tin = "hello world"
    tout = tin + tin
    tin = tin + Chr( 0)
    PokeS(@tout, tin, Len( tin ), #PB_Unicode )
    ProcedureReturn SysAllocString_( tout) 
EndProcedure

; http://www.purebasic.fr/english/viewtopic.php?p=295974#p295974
; Edwin Knoppert 
ProcedureDLL.I Test2(sText.s) 
  ProcedureReturn SysAllocStringByteLen_(@sText, Len(sText)) 
EndProcedure

Code: Select all

VERSION 5.00
Begin VB.Form Form1 
   Caption         =   "Form1"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form1"
   ScaleHeight     =   3195
   ScaleWidth      =   4680
   StartUpPosition =   3  'Windows Default
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Private Declare Function GetDirectString Lib "Envoie BSTR.dll" () As String
Private Declare Function GetDirectString2 Lib "Envoie BSTR.dll" () As String
Private Declare Function GetDirectString3 Lib "Envoie BSTR.dll" () As String
' Edwin Knoppert
Private Declare Function Test2 Lib "Envoie BSTR.dll" (ByVal sText As String) As String

Private Sub Form_Load()

 ChDir App.Path
 
 ' Edwin Knoppert
 MsgBox GetDirectString2
 
 ' Edwin Knoppert
 MsgBox StrConv(GetDirectString3, vbFromUnicode)
 
 ' Edwin Knoppert
 Test
 
End Sub

 
Sub Test()

    Dim x As Long
    Dim MyString As String

    x = 100
   
    For a = 0 To x
     phrase = phrase + Test2("Kcc " + Str(a)) + Chr(13)
    Next
    
    MsgBox phrase

End Sub

Last edited by Kwai chang caine on Mon Jun 11, 2018 9:22 am, edited 1 time in total.
ImageThe happiness is a road...
Not a destination
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Returning Strings to VBA - Is this the right way ?

Post by infratec »

Hm...
BSTR SysAllocString(
_In_opt_ const OLECHAR *psz
);
And OLECHAR is wchar_t which is an UTF16 string.
So it is 'normal' that there are 0 bytes after each 'ASCII' character.

One other thing ... you need to free the memory occupied by SysAllocString_()

Bernd
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Re: Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

Thanks for the collection and the links Kwai.
I think I've got quite some lessons from the Masters here to go, before even considering myself to be an apprentice ;-)

So, basically, I'm doing it the "GetDirectString3()" way, except that I don't do the PokeS() thing as PB is now Unicode (UTF16 hopefully same as VB/A). So I guess there should be no need for that.
To verify that VB/A and PB strings are similar, I've added a function to the DLL to pass a VB/A string to PB.
I use the StrPtr() function to address the unicode character string buffer of the VB/A BSTR and it seems to work well.
That's why I don't understand why returning the string with SysAllocString() needs the extra StrConv() call and I worry for some leak in between.

Here's an updated code.

PB DLL:

Code: Select all

EnableExplicit
DisableDebugger

#APP_VERSION = "1.0.0"

ProcedureDLL.i GetVersion()
  ProcedureReturn SysAllocString_("This is Version " + #APP_VERSION)
EndProcedure

ProcedureDLL.w GimmeAString(psString.s)
  MessageRequester("Received string", "The string is :" + #CRLF$ + #CRLF$ + psString)
EndProcedure
VB/A test module:

Code: Select all

Option Compare Database
Option Explicit

Private Declare PtrSafe Function GetVersion Lib "ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GimmeAString Lib "ReturnStringTest.dll" (ByVal psString As LongPtr) As Integer

Sub Main()
  'Sample in Access VBA, change directory where DLL is or place DLL in PATH
  Dim sVersion As String
  ChDir CurrentProject.Path
  sVersion = GetVersion()
  Debug.Print sVersion, Asc(Mid$(sVersion, 2, 1))
  Debug.Print StrConv(sVersion, vbFromUnicode)
  Call GimmeAString(StrPtr("Hello World! here are some accents: éàûü"))
End Sub
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Re: Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

Hi bernd,

Thanks for chiming in.
And OLECHAR is wchar_t which is an UTF16 string.
So it is 'normal' that there are 0 bytes after each 'ASCII' character.
I agree with that.
One other thing ... you need to free the memory occupied by SysAllocString_()
That is what I fear.

When I return the (SysAllocString()) string from my PB routine, I loose its pointer.
And VB/A receives it as a (BSTR) string (as declared as the return type of the DLL function GetVersion()), so it would be quite inconvenient, if I were to change the return type to Long (or LongPtr). Then I would have to find a way to copy the string from the pointer to a VB variable and then free it, and I would like to avoid that.

So I'm counting on VB/A to free the string once the variable holding it goes out of scope, or when StrConv() creates one from the received one, as it would do for any other of its own string variables.
And it is this assumption that leaves me uneasy.

Of course I'm trying to avoid having to refactor my PB functions to receive a buffer into which I can copy the string, although I may end up just doing that to avoid any problem at all.

All the best.
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Re: Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

Just to follow up on my last resort technique, here's a version where I return a string with a more traditional buffer/copy technique (GetVersionLength/GetVersionV2).
Although there's a Unicode/Ascii(PB)/Unicode(VB/A) conversion in the process, so some potential loss.

PB DLL:

Code: Select all

EnableExplicit
DisableDebugger

#APP_VERSION = "1.0.0"

ProcedureDLL.i GetVersion()
  ProcedureReturn SysAllocString_("This is Version " + #APP_VERSION)
EndProcedure

ProcedureDLL.w GimmeAString(psString.s)
  MessageRequester("Received string", "The string is :" + #CRLF$ + #CRLF$ + psString)
EndProcedure

ProcedureDLL.w GetVersionLength()
  ProcedureReturn Len("This is Version " + #APP_VERSION)
EndProcedure

ProcedureDLL.w GetVersionV2(*psBuffer)
  Protected.s sVersion
  sVersion = "This is Version " + #APP_VERSION
  PokeS(*psBuffer, sVersion, Len(sVersion), #PB_Ascii)
EndProcedure
VB/A:

Code: Select all

Option Compare Database
Option Explicit

Private Declare PtrSafe Function GetVersion Lib "ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GimmeAString Lib "ReturnStringTest.dll" (ByVal psString As LongPtr) As Integer
Private Declare PtrSafe Function GetVersionLength Lib "ReturnStringTest.dll" () As Integer
Private Declare PtrSafe Function GetVersionV2 Lib "ReturnStringTest.dll" (ByVal lpBuffer As LongPtr) As Integer

Sub Main()
  'Sample in Access VBA, change directory where DLL is or place DLL in PATH
  Dim sVersion As String
  
  ChDir CurrentProject.Path
  sVersion = GetVersion()
  Debug.Print sVersion, Asc(Mid$(sVersion, 2, 1))
  Debug.Print StrConv(sVersion, vbFromUnicode)
  'Call GimmeAString(StrPtr("Hello World! here are some accents: éàûü"))
  
  Debug.Print "Version length is: "; GetVersionLength()
  ReDim abBuffer(1 To GetVersionLength() + 1) As Byte
  GetVersionV2 VarPtr(abBuffer(1))
  Debug.Print "Version 2: >"; StrConv(abBuffer(), vbUnicode); "<" 'gets the final \0 to strip off
End Sub
User avatar
mk-soft
Always Here
Always Here
Posts: 5409
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Returning Strings to VBA - Is this the right way ?

Post by mk-soft »

Code: Select all

EnableExplicit
DisableDebugger

#APP_VERSION = "1.0.0"

Threaded *strGetVersion

ProcedureDLL.i GetVersion()
  If *strGetVersion
    SysFreeString_(*strGetVersion)
  EndIf
  *strGetVersion = SysAllocString_("This is Version " + #APP_VERSION)
  ProcedureReturn *strGetVersion
EndProcedure

ProcedureDLL.i GetVersionVariant(*text.variant) ; <- parameter text Byref of type variant
  If *text
    VariantClear_(*text)
    *text\vt = #VT_BSTR
    *text\bstrVal = SysAllocString_("This is Version " + #APP_VERSION)
    ProcedureReturn 1
  Else
    ProcedureReturn 0
  EndIf
EndProcedure
Not testet... Edit
Declare Unicode Function GetVersion Lib "ReturnStringTest.dll" () As String
Declare Unicode Function GetVersionVariant Lib "ReturnStringTest.dll" (Byref Text As Object) As Long

Dim Version as Object
GetVersionVariant(Version)
P.S.
DLL as Unicode...
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Re: Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

Hi mk-soft,

Woaw, that is great.
I love the second one that can alter a variant passed by reference (no need to maintain pointers in the library, no need for conversion).
Also wouldn't have thought using "Thread(ed)" variables until now too.

Anyway, here's the updated and tested code (need to use a variant byref or Access crashes):

I've learned a lot today, and as for our other fellows here, thank you too for adding so much value to this conversation.

PB DLL:

Code: Select all

EnableExplicit
DisableDebugger

#APP_VERSION = "1.0.0"

Threaded *strGetVersion

ProcedureDLL.i GetVersion()
  If *strGetVersion
    SysFreeString_(*strGetVersion)
  EndIf
  *strGetVersion = SysAllocString_("This is Version " + #APP_VERSION)
  ProcedureReturn *strGetVersion
EndProcedure

ProcedureDLL.i GetVersionVariant(*text.variant) ; <- parameter text Byref of type variant
  If *text
    VariantClear_(*text)
    *text\vt = #VT_BSTR
    *text\bstrVal = SysAllocString_("This is Version " + #APP_VERSION)
    ProcedureReturn 1
  Else
    ProcedureReturn 0
  EndIf
EndProcedure
Access VB/A module:

Code: Select all

Option Compare Database
Option Explicit

Private Declare PtrSafe Function GetVersion Lib "ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GetVersionVariant Lib "ReturnStringTest.dll" (ByRef Text As Variant) As Long

Sub Main()
  'Sample in Access VBA, change directory where DLL is or place DLL in PATH
  Dim sVersion As String
  
  ChDir CurrentProject.Path
  sVersion = GetVersion()
  Debug.Print sVersion, Asc(Mid$(sVersion, 2, 1))
  Debug.Print StrConv(sVersion, vbFromUnicode)

  Dim Version As Variant 'Object = crash
  Call GetVersionVariant(Version)
  Debug.Print Version
End Sub
User avatar
mk-soft
Always Here
Always Here
Posts: 5409
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Returning Strings to VBA - Is this the right way ?

Post by mk-soft »

With VBA, the parameter ByRef is not as it should be.
A copy of the variable is passed. This means that you cannot use them as a return value.
But I found a solution via pointers.

DLL as usual...
Private Declare PtrSafe Function GetVersion Lib "ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GetVersionVariant Lib "ReturnStringTest.dll" (ByVal pVariant As LongPtr) As Long

Sub Schaltfläche1_Klicken()
Dim text As Variant
GetVersionVariant (VarPtr(text))
MsgBox text
End Sub
Testing with Excel 2010
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
mk-soft
Always Here
Always Here
Posts: 5409
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Returning Strings to VBA - Is this the right way ?

Post by mk-soft »

Array as Result :wink:

Code: Select all

EnableExplicit
DisableDebugger

; ***************************************************************************************

; Part of Variant helper

;- Structure SAFEARRAY
CompilerIf Defined(SAFEARRAYBOUND, #PB_Structure) = 0
  Structure SAFEARRAYBOUND
    cElements.l
    lLbound.l
  EndStructure
CompilerEndIf

CompilerIf Defined(pData, #PB_Structure) = 0
  Structure pData
    StructureUnion
      llVal.q[0]
      lVal.l[0]
      bVal.b[0]
      iVal.w[0]
      fltVal.f[0]
      dblVal.d[0]
      boolVal.w[0]
      bool.w[0]
      scode.l[0]
      cyVal.l[0]
      date.d[0]
      bstrVal.i[0]
      varVal.VARIANT[0] ; Added ?
      Value.VARIANT[0] ; Added ?
      *punkVal.IUnknown[0]
      *pdispVal.IDispatch[0]
      *parray[0]
      *pbVal.BYTE[0]
      *piVal.WORD[0]
      *plVal.LONG[0]
      *pllVal.QUAD[0]
      *pfltVal.FLOAT[0]
      *pdblVal.DOUBLE[0]
      *pboolVal.LONG[0]
      *pbool.LONG[0]
      *pscode.LONG[0]
      *pcyVal.LONG[0]
      *pdate.DOUBLE[0]
      *pbstrVal.INTEGER[0]
      *ppunkVal.INTEGER[0]
      *ppdispVal.INTEGER[0]
      *pparray.INTEGER[0]
      *pvarVal.VARIANT[0]
      *byref[0]
      cVal.b[0]
      uiVal.w[0]
      ulVal.l[0]
      ullVal.q[0]
      intVal.l[0]
      uintVal.l[0]
      *pdecVal.LONG[0]
      *pcVal.BYTE[0]
      *puiVal.WORD[0]
      *pulVal.LONG[0]
      *pullVal.QUAD[0]
      *pintVal.LONG[0]
      *puintVal.LONG[0]
      decVal.l[0]
      brecord.VARIANT_BRECORD[0]
    EndStructureUnion
  EndStructure
CompilerEndIf

CompilerIf Defined(SAFEARRAY, #PB_Structure) = 0
  Structure SAFEARRAY
    cDims.w
    fFeatures.w
    cbElements.l
    cLocks.l
    *pvData.pData
    rgsabound.SAFEARRAYBOUND[0]
  EndStructure
CompilerEndIf

; ***************************************************************************************

Procedure saCreateSafeArray(vartype, Lbound, Elements)

  Protected rgsabound.SAFEARRAYBOUND, *psa
 
  rgsabound\lLbound = Lbound
  rgsabound\cElements = Elements
  
  *psa = SafeArrayCreate_(vartype, 1, rgsabound)
  If *psa
    ProcedureReturn *psa
  Else
    ProcedureReturn 0
  EndIf
 
EndProcedure


; ***************************************************************************************



#APP_VERSION = "1.0.0"

Threaded *strGetVersion

ProcedureDLL.i GetVersion()
  If *strGetVersion
    SysFreeString_(*strGetVersion)
  EndIf
  *strGetVersion = SysAllocString_("This is Version " + #APP_VERSION)
  ProcedureReturn *strGetVersion
EndProcedure

ProcedureDLL.i GetVersionVariant(*text.variant) ; <- parameter text Byref of type variant
  If *text
    VariantClear_(*text)
    *text\vt = #VT_BSTR
    *text\bstrVal = SysAllocString_("This is Version " + #APP_VERSION)
    ProcedureReturn 1
  Else
    ProcedureReturn 0
  EndIf
EndProcedure

ProcedureDLL.i GetVersionsInfo(*info.variant)
  Protected *psa.SAFEARRAY
  VariantClear_(*info)
  *psa = saCreateSafeArray(#VT_VARIANT, 10, 5)
  If *psa
    *psa\pvData\varVal[0]\vt = #VT_BSTR
    *psa\pvData\varVal[0]\bstrVal = SysAllocString_("Version 1.0.0.1")
    *psa\pvData\varVal[1]\vt = #VT_BSTR
    *psa\pvData\varVal[1]\bstrVal = SysAllocString_("Copyright by mk-soft")
    *psa\pvData\varVal[2]\vt = #VT_I4
    *psa\pvData\varVal[2]\lVal = 1001
    *psa\pvData\varVal[3]\vt = #VT_R4
    *psa\pvData\varVal[3]\fltVal = 2.01
    *psa\pvData\varVal[4]\vt = #VT_BSTR
    *psa\pvData\varVal[4]\bstrVal = SysAllocString_("I Like Purebasic!")
    
    *info\vt = #VT_ARRAY | #VT_VARIANT
    *info\parray = *psa
    ProcedureReturn 1
  Else
    ProcedureReturn 0
  EndIf
EndProcedure
VBA
Private Declare PtrSafe Function GetVersion Lib "D:\Daten\Purebasic5\Ablage\ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GetVersionVariant Lib "D:\Daten\Purebasic5\Ablage\ReturnStringTest.dll" (ByVal pVariant As LongPtr) As Long
Private Declare PtrSafe Function GetVersionsInfo Lib "D:\Daten\Purebasic5\Ablage\ReturnStringTest.dll" (ByVal pVariant As LongPtr) As Long

Sub Schaltfläche1_Klicken()
Dim text As Variant
GetVersionVariant (VarPtr(text))
MsgBox text
End Sub

Sub Schaltfläche2_Klicken()
Dim info As Variant
Dim text
text = ""
GetVersionsInfo (VarPtr(info))
If IsArray(info) Then
For i = LBound(info) To UBound(info)
text = text & "" & i & ": " & info(i) & vbNewLine
Next
Else
text = "Info is not an array"
End If
MsgBox text
End Sub
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Re: Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

@mksoft,

I'll take the Array example as another cool Bonus, thanks :wink:

I think you got a problem with the ByRef variant because of extra parenthesis.
You have:

Code: Select all

Private Declare PtrSafe Function GetVersion Lib "ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GetVersionVariant Lib "ReturnStringTest.dll" (ByVal pVariant As LongPtr) As Long

Sub Schaltfläche1_Klicken()
Dim text As Variant
GetVersionVariant (VarPtr(text))
MsgBox text
End Sub
As GetVersionVariant is a function to VB/A, you should call it either of these two ways:

Code: Select all

GetVersionVariant VarPtr(text)

or:

Call GetVersionVariant(VarPtr(text))
The extra parenthesis around the VarPtr() call forces VB/A to create the temporary variable (which will hold the result of VarPtr) and then pass the address of this temporary var (PB assuming it's a pointer to a Variant will probably crash the calling app).
The first form is like calling a Sub and the second form forces VB/A to interpret the outer parenthesis as the beginning of function arguments.

Cheers.
User avatar
mk-soft
Always Here
Always Here
Posts: 5409
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Returning Strings to VBA - Is this the right way ?

Post by mk-soft »

It does not matter whether the pointer is passed temporarily or via variable.
It is only important that the variable of type Variant was created.
Dim Text as Variant
If this variable is not created, it leads to a crash.

The cleaning up of the variable 'text' is done automatically by VB.

A function can also be called as a sub. The return value is then ignored by the DLL.

Perhaps is this better...
Private Declare PtrSafe Function GetVersion Lib "D:\Daten\Purebasic5\Ablage\ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GetVersionVariant Lib "D:\Daten\Purebasic5\Ablage\ReturnStringTest.dll" (ByVal pVariant As LongPtr) As Long
Private Declare PtrSafe Function GetVersionsInfo Lib "D:\Daten\Purebasic5\Ablage\ReturnStringTest.dll" (ByVal pVariant As LongPtr) As Long

Sub Schaltfläche1_Klicken()
Dim text As Variant
Dim pVariant As LongPtr
pVariant = VarPtr(text)

If GetVersionVariant(pVariant) Then
MsgBox text
Else
MsgBox "No result"
End If
End Sub

Sub Schaltfläche2_Klicken()
Dim info As Variant
Dim text
text = ""
If GetVersionsInfo(VarPtr(info)) Then
If IsArray(info) Then
For i = LBound(info) To UBound(info)
text = text & "" & i & ": " & info(i) & vbNewLine
Next
Else
text = "Info is not an array"
End If
Else
text = "DLL can´t allocate SafeArray"
End If
MsgBox text
End Sub
P.S.
I am already very familiar with the variable type variant.

The type variant is always 16 bytes and with the function VarPtr the pointer to this memory area is passed to the DLL.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
franchsesko
User
User
Posts: 13
Joined: Sat Jun 02, 2018 4:45 pm

Re: Returning Strings to VBA - Is this the right way ?

Post by franchsesko »

If this variable is not created, it leads to a crash.
I agree.
A function can also be called as a sub. The return value is then ignored by the DLL.
Agreed too (although it's VB/A ignoring the return code).
The cleaning up of the variable 'text' is done automatically by VB.
That is what I was looking for to know.
I am already very familiar with the variable type variant.
I don't doubt that for a second :wink:

I've tested both forms of declaration for the parameter (Byref Variant and Byval LongPtr).
I modified the DLL to show the passed pointer value.
So, both declaration will point to the same memory address, then there's no need to have an extra LongPtr (pVariant in your sample) that carries the VarPtr() for us.

You've got no temporary problem with your extra parenthesis because you pass the argument Byval (see GetVersionVar2() down here).
So there's still an extra temporary variable that is created by VB at call time. It takes the address returned by VarPtr(), but as it's then passed byval the same and correct target Variant address is finally used in the DLL.

Tested in Access and Excel (both 2016).

PB:

Code: Select all

EnableExplicit
DisableDebugger

#APP_VERSION = "1.0.0"

Threaded *strGetVersion

ProcedureDLL.i GetVersion()
  If *strGetVersion
    SysFreeString_(*strGetVersion)
  EndIf
  *strGetVersion = SysAllocString_("This is Version " + #APP_VERSION)
  ProcedureReturn *strGetVersion
EndProcedure

ProcedureDLL.i GetVersionVariant(*text.variant) ; <- parameter text Byref of type variant
  If *text
    MessageRequester("GetVersionVariant in DLL", "Variant address="+Hex(*text))
    VariantClear_(*text)
    *text\vt = #VT_BSTR
    *text\bstrVal = SysAllocString_("This is Version " + #APP_VERSION)
    ProcedureReturn 1
  Else
    ProcedureReturn 0
  EndIf
EndProcedure
VB/A:

Code: Select all

Private Declare PtrSafe Function GetVersion Lib "ReturnStringTest.dll" () As String
Private Declare PtrSafe Function GetVersionVariant Lib "ReturnStringTest.dll" (ByRef text As Variant) As Long
Private Declare PtrSafe Function GetVersionVariant2 Lib "ReturnStringTest.dll" _
   Alias "GetVersionVariant" (ByVal pVariant As LongPtr) As Long

Sub Main()
  'Sample in Access VBA, change directory where DLL is or place DLL in PATH
  Dim sVersion As String
  
  ChDir CurrentProject.Path 'Access only, full dll path or dll in PATH for Excel
  
  Dim text As Variant
  Dim pVariant As LongPtr
  pVariant = VarPtr(text)
  Debug.Print "VarPtr(text)="; Hex$(pVariant)
  
  Call GetVersionVariant(text)
  Debug.Print text
  
  Call GetVersionVariant2(pVariant)
  Debug.Print text

  'NO temporary var problem with byval
  GetVersionVariant2 VarPtr(text)   'OK
  GetVersionVariant2 (VarPtr(text)) 'OK too, same address, but hidden temp var
End Sub
Post Reply