【发布时间】:2010-12-17 10:01:10
【问题描述】:
在我的 C# 代码中,我试图从旧版 C++ DLL(我无法更改的代码)中获取结构数组。
在那个 C++ 代码中,结构是这样定义的:
struct MyStruct
{
char* id;
char* description;
};
我正在调用的方法 (get_my_structures) 返回一个指向 MyStruct 结构数组的指针:
MyStruct* get_my_structures()
{
...
}
还有另一种方法可以返回结构的数量,所以我知道返回了多少结构。
在我的 C# 代码中,我这样定义 MyStruct:
[StructLayout(LayoutKind.Sequential)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPStr)] // <-- also tried without this
private string _id;
[MarshalAsAttribute(UnmanagedType.LPStr)]
private string _description;
}
互操作签名如下所示:
[DllImport("legacy.dll", EntryPoint="get_my_structures")]
public static extern IntPtr GetMyStructures();
最后,获取 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));
...
}
问题在于,只有第一个结构(偏移量为零的结构)才能正确编组。后续的 _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]
...
[2009 年 18 月 11 日更新]
以下是 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;
诚然,上面的代码做了一些丑陋的转换并复制了原始指针,但它似乎仍然正确地做到了这一点。至少这是我在调试器中看到的:上面的(静态)缓冲区确实包含所有这些裸 char* 指针一个接一个地存储,它们指向内存中的有效(非本地)位置。
Pavel 的示例表明,这确实是唯一可能出错的地方。我将尝试分析字符串真正所在的“结束”位置会发生什么,而不是存储指针的位置。
【问题讨论】:
标签: c# .net c++ interop marshalling