[英]Marshalling an array of structures from C++ to C#?
在我的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年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;
不可否認,上面的代碼執行了一些丑陋的轉換和復制原始指針,但它似乎仍然可以正確地做到這一點。 至少這是我在調試器中看到的:上面的(靜態)緩沖區確實包含一個接一個地存儲的所有這些裸char *指針,並且它們指向內存中的有效(非本地)位置。
帕維爾的例子表明,這確實是唯一可能出錯的地方。 我將嘗試分析字符串確實存在的那些“結束”位置會發生什么,而不是存儲指針的位置。
我無法重現你的問題,這讓我懷疑它確實在C ++方面。 這是我嘗試的完整源代碼。
dll.cpp
- 使用cl.exe /LD
編譯:
extern "C" {
struct MyStruct
{
char* id;
char* description;
};
__declspec(dllexport)
MyStruct* __stdcall get_my_structures()
{
static MyStruct a[] =
{
{ "id1", "desc1" },
{ "id2", "desc2" },
{ "id3", "desc3" }
};
return a;
}
}
test.cs
- 使用csc.exe /platform:x86
編譯csc.exe /platform:x86
:
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPStr)]
public string _id;
[MarshalAsAttribute(UnmanagedType.LPStr)]
public string _description;
}
class Program
{
[DllImport("dll")]
static extern IntPtr get_my_structures();
static void Main()
{
int structSize = Marshal.SizeOf(typeof(MyStruct));
Console.WriteLine(structSize);
IntPtr myStructs = get_my_structures();
for (int i = 0; i < 3; ++i)
{
IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i);
MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct));
Console.WriteLine();
Console.WriteLine(ms._id);
Console.WriteLine(ms._description);
}
}
}
這正確地打印出所有3個結構。
你能展示填充結構的C ++代碼嗎? 您可以直接從C ++調用它並獲得正確的結果這一事實並不一定意味着它是正確的。 例如,您可能正在返回指向堆棧分配結構的指針。 在進行直接調用時,您將獲得技術上無效的指針,但數據可能仍會保留。 在進行P / Invoke編組時,P / Invoke數據結構可以通過它嘗試從那里讀取值來覆蓋堆棧。
我會改變結構。 而不是字符串等,使用IntPtr:
[StructLayout(LayoutKind.Sequential)] public class MyStruct { private IntPtr _id; private IntPtr _description; }
然后可以使用Marshal.PtrToString將C#數組的每個值手動編組為字符串,同時考慮字符集等。
您必須將UnmanagedType.LPTStr
用於char *。
對於非const char *,建議使用
和CharSet規范: StringBuilder
:
[StructLayout(LayoutKind.Sequential, Charset = CharSet.Auto)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private StringBuilder _id;
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private StringBuilder _description;
}
至於DllImport聲明,你試過嗎?
[DllImport("legacy.dll", EntryPoint="get_my_structures")]
public static extern MarshalAs(UnmanagedType.LPArray) MyStruct[] GetMyStructures();
?
此外,如果前一個不起作用,請將其保留在IntPtr並嘗試按照以下方式對返回的結構進行Mashal:
for (int i = 0; i < structuresCount; i++)
{
MyStruct ms = (MyStruct) Marshal.PtrToStructure(myStructs, typeof(MyStruct));
...
myStructs += Marshal.SizeOf(ms);
}
我通常會通過反復試驗來解決這些問題。 確保你在StructLayout上設置了CharSet屬性,我會嘗試使用UnmanagedType.LPTStr,似乎更適合char *,即使我不確定原因。
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private string _id;
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private string _description;
}
我認為,除了給出的答案之外,你還需要提供長度,即[MarshalAsAttribute(UnmanagedType.LPTStr),SizeConst =,ArraySubType = System.Runtime.InteropServices.UnmanagedType.AnsiBStr)]
為了實現這一點,這是一個試驗和錯誤,另外還有一件事需要考慮,在一些期望字符串參數的WinAPI調用中,通常是一個ref參數,嘗試StringBuilder類也值得你花點時間......沒有別的想到這一點,除了我在這里提到的要點之外......希望這會有所幫助,湯姆
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.