Reputation: 23
I'm using Code::Blocks to write a DLL in C which I intend to use in a Winbatch script but for the moment I'm testing it using Excel VBA. The moment the VBA script runs the DLL getVersion() function Excel crashes. Searching on the internet for several days: I did not find a proper solution.
The C code is like this
#define MQTTPUB_VERSION "V3.1.1Test"
DLL_EXPORT BSTR __stdcall WINAPI getVersion(void)
{
return MQTTPUB_VERSION ;
}
The VBA code is like this
Public Declare Function mqttPubMsg Lib "mqttPubMsg.dll" _
(ByVal MQTT_ADDRESS As String, ByVal MQTT_CLIENTID As String, ByVal MQTT_TOPIC As String, ByVal MQTT_PAYLOAD As String) As Long
Public Declare Function getVersion Lib "mqttPubMsg.dll" () As String
Sub Test_DDL_mqttPubMsg()
'
' to test mqttPubMsg.DLL used in Visual Basic (VBA)
'
Dim DLLVersion As String * 35
Dim WorkDir As String
DLLVersion = Space(35)
WorkDir = ThisWorkbook.Path
ChDir WorkDir
If Dir(WorkDir & "\mqttPubMsg.dll", vbDirectory) = vbNullString Then
MsgBox "DLL not found"
Else
On Error GoTo DLLError
DLLVersion = getVersion() 'Excel crashes on executing this statement
End If
MsgBox ("Version DDL: " & DLLVersion)
Exit Sub
DLLError:
MsgBox ("DDL error")
End Sub
A C program calling the DLL and the getVersion() function works OK.
What could be the cause of this runtime error and how to solve it.
Thanks in advance.
Upvotes: 2
Views: 980
Reputation: 956
The following solution is for a 64-bit system with Office 2013 installed.
C code:
To get the C code working for me, I had to change the declaration slightly, and use a .def
file to make sure the DLL exported the right function name.
#define MQTTPUB_VERSION L"V3.1.1Test" // notice the L - it casts better to BSTR
extern "C" BSTR __stdcall WINAPI getVersion(void)
{
return MQTTPUB_VERSION;
}
VBA code:
On the VBA side, my approach was to
Copy memory from the source address to the destination string address.
Private Declare PtrSafe Sub CopyMemUC Lib "kernel32" Alias "RtlMoveMemory" _
(ByVal Destination As LongLong, ByVal Source As LongLong, ByVal Length As Long)
Private Declare PtrSafe Function wcslen Lib "msvcrt" _
(ByVal Source As LongLong) As Long
Private Declare PtrSafe Function _
getVersionAddress Lib "D:\codeblocks\xltest\bin\Debug\mqttPubMsg.dll" _
Alias "getVersion" _
() As LongLong
Function getVersion()
Dim sAddress As LongLong, sTemp As String, sLen
sAddress = getVersionAddress
sLen = wcslen(sAddress) ' Length (2 bytes per character)
sTemp = String(sLen, " ") ' Allocates memory
CopyMemUC ByVal StrPtr(sTemp), getVersionAddress, sLen * 2
getVersion = sTemp
End Function
Testing if everything works:
Evaluating ?getVersion
in the immediate window returns the string without crashing:
On 32 bit systems, or VBA version < 7:
Remember to remove the PtrSafe
keywords and change all LongLong
occurrences to Long
. Alternatively, use compiler checking - an example can be found here: how to make VBA code compatible for office 2010 - 64 bit version and older office versions
Upvotes: 0
Reputation: 4891
I haven't done Windows programming recently and don't have a readily available development environment to experiment in, but here are some thoughts.
The most likely problem here is that getVersion()
returns BSTR
, which is also (probably) expected by the calling VBA code when the return type is declared as String
. Now, BSTR
is supposed to be preceded in memory by a 4-byte length prefix, see https://msdn.microsoft.com/en-us/library/windows/desktop/ms221069%28v=vs.85%29.aspx. getVersion()
just returns a pointer to "V", the 1st character of the string, and when VBA tries to interpret the preceding 4 bytes, before the "V", as length, it's a disaster...
Here are some possible solutions that I see:
BSTR
using the SysAllocString()
function in
getVersion()
as recommended in the above referenced article.DLLVersion
string to getVersion()
by reference and see
if you can somehow populate it inside getVersion()
. Remember that
you are dealing with Unicode here.A few other things to play with out of curiosity:
getVersion()
return void and see if just calling that function
crashes Excel. If it does, then the problem is deeper.getVersion()
return an integer and see if you can get it in
your VBA code.getVersion()
take an integer as a parameter, pass it an
integer from VBA, and see if the C code can get the correct value.getVersion()
return the pointer to buf + 4
,
where buf
is the global char array.Sorry my memory is not fresh on this and I cannot provide a more specific advice, but hopefully this helps.
Upvotes: 1