编组从C++到C#的结构数组?

vla*_*mir 5 .net c# c++ interop marshalling

在我的C#代码中,我正在尝试从遗留C++ DLL中获取结构数组(代码我无法更改).

在该C++代码中,结构定义如下:

struct MyStruct
{
    char* id;
    char* description;
};
Run Code Online (Sandbox Code Playgroud)

我正在调用的方法(get_my_structures)返回一个指向MyStruct结构数组的指针:

MyStruct* get_my_structures()
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

还有另一种方法可以返回结构的数量,因此我知道返回了多少结构.

在我的C#代码中,我已经定义了MyStruct:

[StructLayout(LayoutKind.Sequential)]  
public class MyStruct
{
  [MarshalAsAttribute(UnmanagedType.LPStr)]    // <-- also tried without this
  private string _id;
  [MarshalAsAttribute(UnmanagedType.LPStr)]
  private string _description;
}
Run Code Online (Sandbox Code Playgroud)

互操作签名如下所示:

[DllImport("legacy.dll", EntryPoint="get_my_structures")]
public static extern IntPtr GetMyStructures();
Run Code Online (Sandbox Code Playgroud)

最后,获取MyStruct结构数组的代码如下所示:

int structuresCount = ...;
IntPtr myStructs = GetMyStructures();
int structSize = Marshal.SizeOf(typeof(MyStruct));    // <- returns 8 in my case
for (int i = 0; i < structuresCount; i++)
{
    IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i);
    MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct));
    ...
}
Run Code Online (Sandbox Code Playgroud)

麻烦的是,只有第一个结构(偏移零点处的一个)才能正确编组.后续的在_id和_description成员中有伪造的值.这些值并未完全删除,或者看起来如此:它们是来自其他一些内存位置的字符串.代码本身不会崩溃.

我已经验证了get_my_structures()中的C++代码确实返回了正确的数据.在通话期间或之后不会意外删除或修改数据.

在调试器中查看,返回数据的C++内存布局如下所示:

0: id (char*)           <---- [MyStruct 1]
4: description (char*)
8: id (char*)           <---- [MyStruct 2]
12: description (char*)
16: id (char*)          <---- [MyStruct 3]
...
Run Code Online (Sandbox Code Playgroud)

[2009年11月18日更新]

以下是C++代码如何准备这些结构(实际代码更加丑陋,但这是一个非常接近的近似值):

static char buffer[12345] = {0};
MyStruct* myStructs = (MyStruct*) &buffer;
for (int i = 0; i < structuresCount; i++)
{
    MyStruct* ms = <some other permanent address where the struct is>;
    myStructs[i].id = (char*) ms->id;
    myStructs[i].description = (char*) ms->description;
}
return myStructs;
Run Code Online (Sandbox Code Playgroud)

不可否认,上面的代码执行了一些丑陋的转换和复制原始指针,但它似乎仍然可以正确地做到这一点.至少这是我在调试器中看到的:上面的(静态)缓冲区确实包含一个接一个地存储的所有这些裸char*指针,并且它们指向内存中的有效(非本地)位置.

帕维尔的例子表明,这确实是唯一可能出错的地方.我将尝试分析字符串确实存在的那些"结束"位置会发生什么,而不是存储指针的位置.

fre*_*tje 0

您必须使用UnmanagedType.LPTStrchar*。StringBuilder建议使用非常量 char*: 以及 CharSet 规范:

[StructLayout(LayoutKind.Sequential, Charset = CharSet.Auto)]  
public class MyStruct
{
  [MarshalAsAttribute(UnmanagedType.LPTStr)]
  private StringBuilder _id;
  [MarshalAsAttribute(UnmanagedType.LPTStr)]
  private StringBuilder _description;
}
Run Code Online (Sandbox Code Playgroud)

至于DllImport声明,你尝试过吗

[DllImport("legacy.dll", EntryPoint="get_my_structures")]
public static extern MarshalAs(UnmanagedType.LPArray) MyStruct[] GetMyStructures();
Run Code Online (Sandbox Code Playgroud)

另外,如果前面的方法不起作用,请将其保留在 IntPtr 中并尝试对返回的结构进行 Mashal,如下所示:

for (int i = 0; i < structuresCount; i++)
{
    MyStruct ms = (MyStruct) Marshal.PtrToStructure(myStructs, typeof(MyStruct));
    ...
    myStructs += Marshal.SizeOf(ms);
}
Run Code Online (Sandbox Code Playgroud)