【问题标题】:Invoke C++ method from VB.Net with string handled as string array从 VB.Net 调用 C++ 方法,字符串作为字符串数组处理
【发布时间】:2010-11-17 12:57:55
【问题描述】:

我有一个带有这个签名的 C++ 方法:

STDMETHODIMP ClassName::FunctionName(long number, BSTR* names, long* status)

在方法内部,names 变量作为字符串数组访问,即

char *  tempString = NULL;

for (int n = 0; n < number; n++)
{
    tempString = OLE2T(names[n]);
...

我编译 C++ 项目,生成一个 dll,然后注册这个 dll 并在 VB 项目中添加对它的引用。 当我添加引用时,会自动生成一个Interop程序集,Interop程序集中的方法签名如下:

FunctionName (number as Integer, ByRef names as String) As Integer

从 VB.Net 我调用这样的方法:

result = FunctionName (number, names(0))

其中names是一个包含多个元素的字符串数组,number和result是整数。

问题在于,当 C++ 代码尝试访问 names 数组中的其余元素(names[1] 及前面)时,它开始在这些字段上获得“垃圾”。

我的问题是,如何发送整个字符串数组而不仅仅是第一个值。

C++ 代码是一个我无法修改的库,因此我所做的任何更改都必须在 VB.Net 代码上。

我在想也许使用 PInvoke 来调用该方法可能会成功(声明正确的签名),但我希望有更好的东西。

有什么想法吗?

谢谢!

编辑:

我不是 Interop/Marshaling 方面的专家,但我检查了该方法的 IDL 定义,如下所示:

[id(60), helpstring("method FunctionName")]
        HRESULT FunctionName(
                    [in] long number, 
                    [in, size_is(number)] BSTR* names,
                    [out, retval] long* status);

size_is不应该表明names参数是一个数组,因此在生成Interop程序集时会采取相应的行动吗?

再次感谢

【问题讨论】:

  • 您需要一些特殊的 IDL 指令来告诉 COM 编组器 number 表示字符串数组 names 的大小。否则编组器不知道这一点,只需要独立的值,根本不知道 names 是一个数组。当然你可以编写一个 C++ 包装器,它“知道”原始 C++ 库的无效行为,并更正接口。
  • 感谢您的及时回答,我刚刚用您所说的所需的 idl 信息编辑了问题。有什么建议吗?
  • VB 没有 p/invoke 或 MarshalAsAttribute,也许你的意思是 VB.net? names[0] 也不是 VB 中的数组元素语法吗?解决问题的最简单方法是使用接受 .NET array&lt;String^&gt; 的 C++/CLI 包装器,转换为 BSTR 的数组,然后调用 C++ 函数。
  • 是的,Ben,我指的是 VB.Net,数组的东西是一个错字,我刚刚修复了它。那么,有没有办法避免这个包装?我宁愿使用 Hans 方法(手动编辑 IL 并生成新的 Interop 程序集)。

标签: c++ vb.net interop marshalling


【解决方案1】:

不,[size_is] 是一个只有 midl.exe 知道如何使用的属性。当它为接口生成代理/存根时,它会这样做,当您想要跨进程边界进行调用时会使用它。

它不能在类型库中以其他方式表达。该参数作为“指向 BSTR 的指针”发出,它可以指示通过引用传递的 BSTR 或 BSTR 数组。 Tlbimp.exe 无法区分,它选择了前者。它必须,它不能合理地推断数组大小。您在运行时会遇到垃圾,因为 CLR 互操作层仅传递数组的单个元素。使用这种方法的任何 COM 客户端都有一个大问题,它不是 .NET 特定的。 .NET pinvoke marshaller 有解决此问题的方法,请注意 [MarshalAs] 属性的 SizeParamIndex 属性。

如果您无法修改 C++ 代码,则需要手动编辑互操作库以注入该 [MarshalAs] 属性。这很丑陋,您必须使用 ildasm.exe 反编译库,编辑 .il 并将其与 ilasm.exe 重新组合在一起。你不想经常这样做。

如果可以的话,你应该使用自动化兼容的方式来传递数组。使用 SAFEARRAY。类型库和 CLR 互操作管道完全支持这一点,您无需在托管端进行任何工作。另请注意,您现在不再需要 number 参数,安全数组知道它们有多长。与托管数组不同。

【讨论】:

  • 太棒了!非常感谢,如果有人感兴趣,我从以下修改了 IL 中方法的声明:[in] string& marshal(bstr) names) runtime managed internalcall to this:[in] string[] marshal(bstr [0] ) 名称) 运行时管理的内部调用。这表明参数将是一个字符串数组,数组的长度将是方法的第一个参数。
猜你喜欢
  • 2010-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多