Search notes:

Accessing and calling DLLs from VBA: returning a string

This example tries to explore various ways to return a string from a DLL to a Visual Basic for Application environment.

dll.c

The DLL exposes four functions.
The first function (charArray) returns a pointer to a const char (i.e. a const char*). The VBA code that receives the char-array needs then to convert the single byte array to a multi byte array (that VBA internally uses to store strings) with the WinAPI function MultiByteToWideChar.
The second function (wcharArray) returns a wide character array (const wchar_t*). It turns out that this is still not sufficient for VBA to be recognized as VBA-String. I guess this is because VBA expects a BSTR-string, not just a wide-character string. So, the VBA code has the function wcharPtrToString which creates such a string.
The third function (bstr) returns a BSTR which is created by the WinAPI function SysAllocString(). Although the returned value can now be directly assigned to a string, the String contains the null character that intersperses the letters in the wide charater string.
The fourth function (bstr_c) also returns a BSTR, but this time, SysAllocString is not given a wide character string but an ordinary C-string. This seems to finally work as intended.
//
// Because of SysAllocString, compile with oleAut32.lib:
//   gcc -shared dll.o -loleAut32 -o the.dll -Wl,--add-stdcall-alias
//
#include <windows.h>

__declspec(dllexport) const char* __stdcall charArray() {
    return "This char string originates in C";
}

__declspec(dllexport) const wchar_t* __stdcall wcharArray() {
    return L"This wchar_t string originates in C";
}

__declspec(dllexport) BSTR __stdcall bstr() {
    return SysAllocString(L"This bstr-string originates in C");
}

__declspec(dllexport) BSTR __stdcall bstr_c() {
    wchar_t* c = (wchar_t*) "This c-bstr-string originates in C";
    return SysAllocString(c);
}
Github repository VBA-calls-DLL, path: /return-char-array/dll.c
2020-09-20: As I currently assume, the string that was allocated with SysAllocString does not need to be explicitly freed as the ownership of the string is passed to the VBA runtime when assigned to the VBA string, see this discussion.

vba.bas

option explicit

declare ptrSafe function charArray  _
        lib "the.dll" (             _
        ) as longPtr

declare ptrSafe function wcharArray _
        lib "the.dll" (             _
        ) as longPtr

declare ptrSafe function bstr       _
        lib "the.dll" (             _
        ) as string

declare ptrSafe function bstr_c     _
        lib "the.dll" (             _
        ) as string


private const CP_UTF8 as long = 65001
declare ptrsafe function MultiByteToWideChar lib "kernel32" ( _
  byVal CodePage        as long   , _
  byVal dwFlags         as long   , _
  byVal lpMultiByteStr  as longPtr, _
  byVal cbMultiByte     as long   , _
  byVal lpWideCharStr   as longPtr, _
  byVal cchWideChar     as long     _
) as long

declare ptrsafe function lstrlenW lib "kernel32" ( _
  byVal lpSTring        as longPtr  _
) as long

declare ptrsafe function lstrcpyW lib "kernel32" ( _
  byVal lpString1       as longPtr,  _
  byVal lpString2       as longPtr   _
) as longPtr

'#if Win64 then
function utf8PtrToString(byVal pUtf8string as longPtr) as string ' {
' #Else
'   function utf8PtrToString(byVal pUtf8string as long   ) as string
' #End if
'
'   Found @ https://github.com/govert/SQLiteForExcel/blob/master/Source/SQLite3VBAModules/Sqlite3_64.bas
'
    dim buf    as string
    dim cSize  as long
    dim mbVal  as long
    
    cSize = MultiByteToWideChar(CP_UTF8, 0, pUtf8String, -1, 0, 0)
  ' cSize includes the terminating null character
    if cSize <= 1 Then
       utf8PtrToString = ""
       exit function
    end if
    
    utf8PtrToString = string(cSize - 1, "*") ' and a termintating null char.
    mbVal = MultiByteToWideChar(CP_UTF8, 0, pUtf8String, -1, strPtr(utf8PtrToString), cSize)

    if mbVal = 0 then
       err.raise 1000, "Error", "MultiByteToWideChar failed"
    end if

end function ' }

function wcharPtrToString(byVal wcharPtr as longPtr) as string ' {
    dim cSize  as long

    csize = lstrlenW(wcharPtr)
    wcharPtrToString = string(cSize, "*")
    lstrcpyW strPtr(wcharPtrToString), wcharPtr

end function ' }

sub main() ' {

    dim  s as string
    dim ws as string

    s = utf8PtrToString(charArray())
    debug.print("s = " & s)

    ws = wcharPtrToString(wcharArray())
    debug.print("ws = " & ws)

    debug.print("bstr = " & bstr())

    debug.print("bstr_c = " & bstr_c())

end sub ' }
Github repository VBA-calls-DLL, path: /return-char-array/vba.bas

dll.def

TODO …

TODO

How is it possible to return a string that contains special characters (such as the smiley etc.)?

See also

Accessing and calling DLLs from VBA (Visual Basic for Applications)

Index