【问题标题】:VB.NET Pinvoke: How to fix the structure address passed into DLL?VB.NET Pinvoke:如何修复传递给 DLL 的结构地址?
【发布时间】:2018-12-14 11:53:36
【问题描述】:

我有一个用 C 语言编写的 DLL(使用 VC++2017 编译)。有几个函数接受指向结构的指针。

在 init 调用期间,它会保存传入的地址。在以后的调用中,DLL 期望传入的地址与第一次 init 调用相同。

在vb.net中,我定义了一个结构体(打包成4个),我检查了内存布局,传入DLL时和C完全一样。

但是,每次我使用结构 (ByRef) 调用函数时,地址可能会或可能不会改变(移位 4 个字节)。

我是否遗漏了什么或者甚至可以在 VB.NET 中做到这一点?

代码如下,c结构体(这是遗留代码,我不想改变它),

struct A
{
    char a[9] ;
    char b[9] ;
    char c[2] ;
    char d[9] ;
    int e;
    int f;
    char g[2] ;
    char h[9] ;
    int i;
    int j;
    char k[2] ;
    int l;
    char m[41] ;
    char n[41] ;
    char o[10] ;
} ;

这是我在 VB.NET 中定义的,

<StructLayout(LayoutKind.Sequential, Pack:=4)>
Structure A
    <VBFixedArray(9), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=9)> Dim a() As Byte
    <VBFixedArray(9), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=9)> Dim b() As Byte
    <VBFixedArray(2), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=2)> Dim c() As Byte
    <VBFixedArray(9), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=9)> Dim d() As Byte
    Dim e As Integer
    Dim f As Integer
    <VBFixedArray(2), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=2)> Dim g() As Byte
    <VBFixedArray(9), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=9)> Dim h() As Byte
    Dim i As Integer
    Dim j As Integer
    <VBFixedArray(2), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=2)> Dim k() As Byte
    Dim l As Integer
    <VBFixedArray(41), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=41)> Dim m() As Byte
    <VBFixedArray(41), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=41)> Dim n() As Byte
    <VBFixedArray(10), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=10)> Public o() As Byte
End Structure

这些是 C 原型:

__declspec( dllexport ) int __stdcall init(struct A * param1)
__declspec( dllexport ) int __stdcall dosomething(struct A * param1)

这些是 VB.NET 原型

Public Declare Function init Lib "A.dll" (ByRef param1 As A) As Integer
Public Declare Function dosomething Lib "A.dll" (ByRef param1 As A) As Integer

Dim a As New A
'ok
init(a)
' ok
dosomething(a)
' The second call to dosomething, the param1's address changed by 4 bytes
dosomething(a)

以上只是简化版。您会明白,param1 在 C 中的地址在不同的调用期间会发生变化。

有没有办法解决这个问题?

谢谢。

【问题讨论】:

  • 在 C 中存储该指针也是一个非常值得怀疑的做法。也许更容易逃脱,但是必须将该指针也传递给 dosomething() 就没有多大意义了。因为你已经有了指针。基于句柄的设计会更明智,init() 函数然后 返回 指针。无论如何,您必须将参数更改为 ByVal param As IntPtr 并在您的 vb.net 代码中编组结构。哎呀,如果 dosomething() 进行了更改,您必须编组回来。你实际上做了 C 代码应该做的事情,更痛苦的是因为它只是 C 中的 memcpy()。
  • @HansPassant 原始的 C 代码是遗留的,由 VB6 使用。我们正在将其升级到 VB.Net(也使用 VC++2017 重新编译 C 代码)。除非绝对必要,否则最好不要修改 C 部分,因为了解它背后的所有内存混乱需要时间,而且超出了我们的预算。我知道遗留的 C(20 多年前写的)不应该真的期望传入的内存地址总是相同的,如果在 .Net 中没有办法,那么修改 C 可能是我们唯一的解决方案。跨度>

标签: .net vb.net pinvoke marshalling


【解决方案1】:

编组结构的地址在每次调用中都是不同的,这是非常自然的。这是因为封送拆收器必须创建一个非托管结构以发送到非托管代码。托管结构与非托管结构的布局不同,这使得这一点成为必要。即使托管结构和非托管结构具有兼容的布局(即结构是 blittable),地址也可能会更改,因为 .net 内存管理器可以移动对象。

但是,您可以负责编组过程。分配一些非托管内存(例如,通过调用Marshal.AllocHGlobal,然后使用Marshal.StructureToPtr 用结构的封送版本填充该内存。然后您可以将该非托管内存的地址传递给非托管代码。当你完成所有您对非托管代码的调用,请调用 Marshal.PtrToStructure 以读取对结构所做的任何修改。

也许更大的问题是为什么需要在两次通话之间保持稳定的地址。我发现很难想象这样的场景是对调用者的合理期望。您的非托管代码是否有可能通过要求调用者来冒昧?

【讨论】:

    猜你喜欢
    • 2012-08-10
    • 2021-05-03
    • 1970-01-01
    • 2018-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-09
    • 1970-01-01
    相关资源
    最近更新 更多