【问题标题】:Excel VBA script crashes when calling a DDL function written in C, which returns a string BSTRExcel VBA 脚本在调用用 C 编写的 DDL 函数时崩溃,该函数返回字符串 BSTR
【发布时间】:2016-03-15 17:44:25
【问题描述】:

我正在使用 Code::Blocks 用 C 语言编写一个 DLL,我打算在 Winbatch 脚本中使用它,但目前我正在使用 Excel VBA 对其进行测试。 VBA 脚本运行 DLL getVersion() 函数的那一刻,Excel 崩溃。在网上搜索了几天:我没有找到合适的解决方案。

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

调用 DLL 的 C 程序和 getVersion() 函数工作正常。

此运行时错误的原因可能是什么以及如何解决它。

提前致谢。

【问题讨论】:

    标签: c excel vba dll


    【解决方案1】:

    我最近没有做过 Windows 编程,也没有现成的开发环境可供试验,但这里有一些想法。

    这里最可能的问题是getVersion() 返回BSTR,当返回类型声明为String 时,调用VBA 代码也(可能)期望出现这种情况。现在,BSTR 应该在内存中以 4 字节长度前缀开头,请参阅https://msdn.microsoft.com/en-us/library/windows/desktop/ms221069%28v=vs.85%29.aspxgetVersion() 只是返回一个指向“V”的指针,字符串的第一个字符,当 VBA 试图将前面的 4 个字节解释为“V”之前的长度时,这是一场灾难......

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

    • 使用SysAllocString() 函数构造一个BSTR getVersion() 上述参考文章中的建议。
    • 通过引用将DLLVersion 字符串传递给getVersion() 并查看 如果你能以某种方式将它填充到getVersion() 中。请记住 您在这里处理的是 Unicode。

    出于好奇,还有一些其他可以玩的东西:

    • 验证是否可以从 VBA 调用 DLL 函数;让 getVersion() return void 看看是否只是调用该函数 崩溃 Excel。如果是这样,那么问题就更深了。
    • getVersion()返回一个整数,看看能不能进去 您的 VBA 代码。
    • 使getVersion()接受一个整数作为参数,传递给它一个 来自 VBA 的整数,看看 C 代码是否能得到正确的值。
    • 在 C 代码中有足够大的全局字符数组,将 (Unicode!)从数组中偏移量 4 开始的版本字符串,以及 用一个(我认为是小端序)整数填充第一个 4 个字节 表示 Unicode 字符串的长度,不包括 NULL 终结者。使getVersion()返回指向buf + 4的指针, 其中buf 是全局字符数组。

    抱歉,我对此记忆不深,无法提供更具体的建议,但希望对您有所帮助。

    【讨论】:

    • 非常感谢您的建议,我会在等待其他提示的同时尝试一下。
    【解决方案2】:

    以下解决方案适用于安装了 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的返回类型声明为LongLong而不是字符串来获取目标字符串的地址。
    2. 分配一个字符数相同的空字符串
    3. 将内存从源地址复制到目标字符串地址。

          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
      

    测试是否一切正常:

    在即时窗口中评估 ?getVersion 会返回字符串而不会崩溃:

    在 32 位系统上,或 VBA 版本

    记得删除 PtrSafe 关键字并将所有出现的 LongLong 更改为 Long。或者,使用编译器检查 - 可以在此处找到示例:how to make VBA code compatible for office 2010 - 64 bit version and older office versions

    【讨论】:

      猜你喜欢
      • 2016-08-03
      • 2017-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-12
      • 1970-01-01
      相关资源
      最近更新 更多