从.NET传递一个字节缓冲区到COM并获取更新后的结果

3
我有以下COM方法需要从C#中调用,它会将一个字符串复制到提供的缓冲区pchText中(该字符串不一定是以零结尾的),并返回复制的字符数pcch
HRESULT Next([in, out] long* pcch, [out, size_is(*pcch)] OLECHAR* pchText);

我该如何定义C#互操作的签名?

目前,我尝试了以下方法:

void Next(ref int pcch,
    [MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 0)]
    System.Text.StringBuilder pchText);

看起来它是有效的,但我不确定SizeParamIndex是否对StringBuilder有任何影响。


这是你定义的方法还是必须使用的东西? - acelent
@PauloMadeira,COM方法签名是我必须使用的。我有自己的COM对象模拟,用于单元测试。但现在我在C#中使用char[] pchText。http://stackoverflow.com/q/25039290/2674222 - avo
非常不幸,一个更可靠和可远程的签名应该是 HRESULT Next([in] cch, [out] long* pcch, [out, size_is(cch), length_is(*pcch)] OLECHAR* pchText);。这个方法属于哪个接口/对象? - acelent
@PauloMadeira,这是由第三方供应商提供的DLL COM库。它仅在进程内使用。 - avo
1个回答

4

这确实是一个难以正确调用的函数。你的声明大致没问题,只需要应用[PreserveSig]属性,并将返回值类型设置为int,以便发现返回值为S_FALSE表示没有下一个元素。

困难在于必须事先猜测要传递多大的StringBuilder。本机代码得到指向构建器缓冲区的GC堆中的原始指针,因此意外情况会非常严重。您必须事先猜测构建器的适当容量,并将其作为初始pcch参数传递。

马歇尔程序在函数返回后确实会注意SizeParamIndex。它只会复制ppch指定的字符数。如果由于任何原因它写入的内容超过了缓冲区,则程序将立即中止,并显示ExecutionEngineException,因为这表明GC堆已被损坏。

请注意,如果您猜测的容量太低,则可能无法发现这一点。当函数仅复制适合的字符并且不返回错误代码时,您可能只会得到截断的字符串。发现这是否是问题的最佳方法是通过测试并故意传递一个小的构建器。注意返回值。

值得指出的一个怪癖是,函数签名暗示了早期COM中常见的一种黑客方式,实际上是通过OLECHAR*返回二进制数据而不是文本。强烈提示这种情况,因为字符串不能保证为零终止。在.NET中,这将无法成功,当字符串被标准化时,数据将被损坏。如果是这种情况,则需要使用short[]而不是StringBuilder。


谢谢,提到缓冲区中可能有多个\0x0字符是一个好观点。我能否使用这样的签名 [PreserveSig] int Next(ref int pcch, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)], char[] pchText)?对于这个问题,char[]short[]之间是否有任何主要区别? - avo
1
如果它实际上是二进制数据,那么使用char是不明智的。PInvoke marshaller也会假定8位ansi字符,这与OLECHAR不匹配。 - Hans Passant
创建一些c++/CLI包装器在它们自己的小项目中会比猜测如何编排更有意义,不是吗? - dbc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接