当调用用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.aspxgetVersion()只返回一个指向“V”的string的第一个字符,当VBA试图解释前面的4个字节时,在“V”之前,长度是一个灾难。

以下是我看到的一些可能的解决scheme:

  • 使用getVersion()SysAllocString()函数构buildBSTR ,如上面引用的文章中所build议的。
  • 通过引用将DLLVersionstring传递给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方面,我的做法是

  1. 通过将getVersion的返回types声明为LongLong而不是string来获取目标string的地址。
  2. 用相同数量的字符分配一个空string
  3. 将内存从源地址复制到目标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版本