当调用用C写的DDL函数时,Excel VBA脚本崩溃,该函数返回一个stringBSTR
我正在使用Code :: Blocks在C中编写一个DLL,我打算在Winbatch脚本中使用它,但是现在我正在使用Excel VBAtesting它。 VBA脚本运行DLL getVersion()函数的时刻Excel崩溃。 在网上search几天:我没有find一个合适的解决scheme。
C代码是这样的
#define MQTTPUB_VERSION "V3.1.1Test" DLL_EXPORT BSTR __stdcall WINAPI getVersion(void) { return MQTTPUB_VERSION ; }
VBA代码是这样的
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
AC程序调用DLL和getVersion()函数工作正常。
什么可能是这个运行时错误的原因,以及如何解决它。
提前致谢。
我最近没有做Windows编程,也没有一个可用的开发环境来进行实验,但是这里有一些想法。
这里最可能的问题是getVersion()
返回BSTR
,当返回types被声明为String
时,它也(可能)被调用VBA代码期望。 现在, BSTR
应该在内存中以4字节长度作为前缀,请参阅https://msdn.microsoft.com/en-us/library/windows/desktop/ms221069%28v=vs.85%29.aspx 。 getVersion()
只返回一个指向“V”的string的第一个字符,当VBA试图解释前面的4个字节时,在“V”之前,长度是一个灾难。
以下是我看到的一些可能的解决scheme:
- 使用
getVersion()
的SysAllocString()
函数构buildBSTR
,如上面引用的文章中所build议的。 - 通过引用将
DLLVersion
string传递给getVersion()
,并查看是否可以在getVersion()
填充它。 请记住,你在这里处理Unicode。
一些其他的事情可以出于好奇:
- validation一个DLL函数实际上可以从VBA调用; 让
getVersion()
返回无效,看看是否只是调用该函数崩溃的Excel。 如果是这样,那么问题就更深了。 - 让
getVersion()
返回一个整数,看你是否可以在你的VBA代码中得到它。 - 让
getVersion()
接受一个整数作为参数,从VBA中传递一个整数,看看C代码是否可以得到正确的值。 - 在你的C代码中有一个足够大的全局字符数组,把(Unicode!)版本string从数组中的偏移量4开始,并用一个(小端,我认为)整数来填充前4个字节,不包括NULL终止符的Unicodestring。 使
getVersion()
返回指向buf + 4
的指针,其中buf
是全局char数组。
对不起,我的记忆是不是新鲜的,我不能提供更具体的build议,但希望这有助于。
以下解决scheme适用于安装了Office 2013的64位系统。
C代码:
为了让C代码为我工作,我不得不稍微更改声明,并使用.def
文件来确保DLL导出正确的函数名称。
#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代码:
在VBA方面,我的做法是
- 通过将getVersion的返回types声明为LongLong而不是string来获取目标string的地址。
- 用相同数量的字符分配一个空string
-
将内存从源地址复制到目标string地址。
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一切是否正常:
在即时窗口中评估?getVersion
返回string而不崩溃:
在32位系统上,或VBA版本<7:
请记住删除PtrSafe
关键字并将所有LongLong
事件更改为Long
。 或者,使用编译器检查 – 一个例子可以在这里find: 如何使VBA代码兼容Office 2010 – 64位版本和旧版本的Office版本