簡體   English   中英

編組從C ++到C#的結構數組?

[英]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 *,建議使用 StringBuilder和CharSet規范:

[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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM