【问题标题】:Marshaling structure (that containes arrays) as return value编组结构(包含数组)作为返回值
【发布时间】:2014-09-04 17:01:23
【问题描述】:

结构的 C++ 代码如下:

typedef struct _a astruct;
struct _a {
    BYTE fi, Sec, *D, *IIV, PV;
    bool Visited;
};

以及使用它的函数:

astruct DoPDC(string *InitialData);

我想通过VB.NET使用这个函数(包含在一个DLL中),所以我为这个结构写了以下代码:

<StructLayout(LayoutKind.Sequential)> _
Public Structure astruct
    Dim fi As Byte
    Dim Sec As Byte
    Dim D As Byte()
    Dim IIV As Byte()
    Dim PV As Byte
    <MarshalAs(UnmanagedType.Bool)> Dim Visited As Boolean
End Structure

以及函数声明:

    <DllImport("astr.dll", EntryPoint:="DoPDC", BestFitMapping:=False, CallingConvention:=CallingConvention.Cdecl, ThrowOnUnmappableChar:=True, CharSet:=CharSet.Ansi)> _
    Public Function DoPDC(ByVal InitialData As String()) As <MarshalAs(UnmanagedType.Struct)> astruct
    End Function

所以,我得到了悲惨的错误MarshalDirectiveError,特别是Method's type signature is not PInvoke compatible.我以前从未见过这个错误,互联网上有很多不同的解决方案,但没有人有我的情况,我作为参数传递字符串数组,返回一个包含更多数组的结构体!

结果我不知道哪里出了问题:参数,返回值,返回值里面的数组还是一起?我很困惑...因此,我的问题是:如何修改我的 VB.NET P/Invoke 代码,使其正常工作?

(请注意,我无法更改 C++ DLL,因为它不是我的,因此我没有代码...)

【问题讨论】:

    标签: c++ arrays vb.net pinvoke marshalling


    【解决方案1】:

    问题在于您对DIIV 的定义。您将它们声明为字节数组,但 P/Invoke 编组器不知道这些数组有多大;传递长度的指向字节的指针没有任何内在的东西。如果数组是可变长度的,那么 P/Invoke 编组器如何确定这个长度?如果它们始终具有已知的固定长度,那么您可以使用它,其中 N 是数组的固定长度:

    <MarshalAs(UnmanagedType.LPArray, SizeConst = N)>
    

    如果返回的数组具有动态大小,并且您可以在函数返回后以某种方式确定它,则将DIIV声明为IntPtr并使用System.Runtime.InteropServices.Marshal的方法提取数据。

    如果指针指向它希望您释放的函数分配的内存,您将不得不手动释放分配,这将涉及将这些指针发送回非托管函数以进行释放。

    【讨论】:

    • 试过这个,但仍然得到同样的错误!!我讨厌 PInvoke ......所以,正如我所怀疑的那样,我有多个错误!至少我要感谢您 (+1),因为我纠正了 1 个无限错误...
    • @Jason 这是我看到的唯一错误。添加此属性后可以粘贴代码吗?
    • 结构是一样的,除了 byte() 已经被 IntPtr 替换了,我在其他地方学习过使用 IntPtrs,例如:Dim out(19) As ByteMarshal.Copy(astruct.D, out, 0, 20)(它的长度不是 20字节,这只是为了演示,但无论如何我知道如何提取数组)如果这有帮助,我已经看到here GCC 做了它所说的(第二个答案),所以也许 Visual Studio 编译器做了类似的事情? ?
    • 嗯,我明白了。如果你用IntPtr 替换了两个字节数组,那么我看不出有什么问题。在 C++ 代码中,它们是空指针而不是数组,所以我们不必担心那里的第一个答案。第二个很有趣。 RVO 可能很棘手。您可能需要反汇编 DLL 以确定这是否是您需要做的(很可能是)。
    • 我不懂汇编,但我可以告诉你用于 DLL 的编译器选项。
    【解决方案2】:

    这是一个不得不调用的非常糟糕的功能。 .NET 编组器对此无能为力,我们也没有。结构中的数组有两个基本问题:无法猜测它们有多长,也无法合理猜测谁拥有它们的内存以及应该如何释放它。

    您必须在您的结构声明中将它们声明为IntPtr。在你做更多的事情之前,编写一个小测试程序来重复调用。观察返回的 IntPtr 值。如果它们不同那么你需要放弃,因为你不能释放内存,所以没有办法使用 pinvoke。将需要 C++/CLI 包装器。

    如果它们没有什么不同,那么您可以尝试一下。使用 Marshal.Copy() 从 IntPtrs 复制数组。但是,您仍然必须知道要复制多少元素。如果您不知道,请致电 C 程序员。

    顺便说一句,bool 的 [MarshalAs] 是错误的,将其设为 UnmanagedType.U1

    【讨论】:

    • 如果它们不同,这并不必然意味着旧的分配没有被释放。我已经看到将分配存储在某处并在下次调用时释放它的函数。 (当然这些都是不好的、非线程安全的、不可重入的函数。)反复调用它几个小时并分析内存使用情况可能是一个更好的测试。 (检查拆卸也可以。)
    • 嗯,假设这样一个奇怪的方案不是很有效率。
    • @cdhowie 首先,我想通知您,我有另一个返回 IntPtr 的 C 函数,Marshall.Copy 对此非常有效(已对其进行测试)。所以,返回数组问题解决了(注意我不关心内存泄漏,操作系统应该能够处理进程未释放的内存。所以,这是一个 Windows 问题,而不是我的问题。另外,那些数组不'不包含敏感数据,所以我不太关心它们)。其次,我知道数组元素的数量。第三,我认为这是一个返回结构的问题,而不是一个返回的数组!
    • @Jason 是的,结构似乎是这里的主要问题,尽管我仍然不能 100% 确定原因。
    • @cdhowie 如果你用谷歌搜索我的错误“方法的类型签名与 PInvoke 不兼容。”你会看到一百个网站都关于某种结构的回归。所以,我不认为这是巧合,但我也无法理解如何应用他们的解决方案。
    猜你喜欢
    • 2012-02-05
    • 2019-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多