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);
}
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 ' }