簡體   English   中英

如何將包含可變大小數組的結構編組到C#?

[英]How do I marshal a struct that contains a variable-sized array to C#?

如何封送這種C ++類型?

ABS_DATA結構用於將任意長的數據塊與長度信息相關聯。 Data數組的聲明長度為1,但實際長度由Length成員給定。

typedef struct abs_data {
  ABS_DWORD Length;
  ABS_BYTE Data[ABS_VARLEN];
} ABS_DATA;

我嘗試了以下代碼,但無法正常工作。 數據變量始終為空,我確定其中有數據。

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
    public struct abs_data
    {
        /// ABS_DWORD->unsigned int
        public uint Length;

        /// ABS_BYTE[1]
       [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1)]
        public string Data;
    }

老問題了,但是我最近不得不自己做,所有現有的答案都很差,所以...

在結構中封送可變長度數組的最佳解決方案是使用自定義封送程序 這使您可以控制運行時用來在托管數據和非托管數據之間轉換的代碼。 不幸的是,自定義封送處理的文獻記錄很少,並且有一些奇怪的限制。 我將快速介紹這些內容,然后再解決問題。

令人討厭的是,您不能在結構或類的數組元素上使用自定義封送處理。 沒有記錄或邏輯上的限制說明,編譯器也不會抱怨,但是您會在運行時遇到異常。 另外,有一個自定義封送程序必須實現的函數int GetNativeDataSize() ,這顯然不可能准確實現(它不會向您傳遞對象的實例來詢問其大小,因此您只能關閉該類型,當然是可變大小的!)幸運的是,此功能無關緊要。 我從來沒有見過它被調用過,並且即使它返回假值(一個MSDN示例中它返回-1),自定義封送程序也能正常工作。

首先,這是我認為您的本機原型可能是什么樣子(我在這里使用P / Invoke,但它也適用於COM):

// Unmanaged C/C++ code prototype (guess)
//void DoThing (ABS_DATA *pData);

// Guess at your managed call with the "marshal one-byte ByValArray" version
//[DllImport("libname.dll")] public extern void DoThing (ref abs_data pData);

這是您可能如何使用自定義封送處理程序的天真的版本(它確實應該起作用)。 我會稍等一下...

[StructLayout(LayoutKind.Sequential)]
public struct abs_data
{
    // Don't need the length as a separate filed; managed arrays know it.
    [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ArrayMarshaler<byte>))]
    public byte[] Data;
}

// Now you can just pass the struct but it takes arbitrary sizes!
[DllImport("libname.dll")] public extern void DoThing (ref abs_data pData);

不幸的是,在運行時,您顯然無法將數組中的數組編組為SafeArrayByValArray之外的任何其他東西。 SafeArrays被計算在內,但看起來卻與您在這里尋找的(極其常見)的格式完全不同。 這樣就行不通了。 當然,ByValArray要求在編譯時知道該長度,因此(在遇到問題時)也不起作用。 但是,奇怪的是,您可以在數組參數上使用自定義封送處理,這很煩人,因為您必須將MarshalAsAttribute放在使用此類型的每個參數上,而不是僅將其放在一個字段上並使其應用到所有使用包含該類型的類型的地方字段,但c'est la vie。 看起來像這樣:

[StructLayout(LayoutKind.Sequential)]
public struct abs_data
{
    // Don't need the length as a separate filed; managed arrays know it.
    // This isn't an array anymore; we pass an array of this instead.
    public byte Data;
}

// Now you pass an arbitrary-sized array of the struct
[DllImport("libname.dll")] public extern void DoThing (
    // Have to put this huge stupid attribute on every parameter of this type
    [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ArrayMarshaler<abs_data>))]
    // Don't need to use "ref" anymore; arrays are ref types and pass as pointer-to
    abs_data[] pData);

在該示例中,我保留了abs_data類型,以防您要對其進行特殊操作(構造函數,靜態函數,屬性,繼承等)。 如果數組元素由復雜類型組成,則可以修改該結構以表示該復雜類型。 但是,在這種情況下, abs_data基本上只是一個重命名的字節-它甚至沒有“包裝”該字節。 就本機代碼而言,它更像是typedef-因此您可以傳遞一個字節數組並完全跳過該結構:

// Actually, you can just pass an arbitrary-length byte array!
[DllImport("libname.dll")] public extern void DoThing (
    // Have to put this huge stupid attribute on every parameter of this type
    [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ArrayMarshaler<byte>))]
    byte[] pData);

OK,現在您可以看到如何聲明數組元素類型(如果需要),以及如何將數組傳遞給非托管函數。 但是,我們仍然需要該自定義封送程序。 您應該閱讀“ 實現ICustomMarshaler接口 ”,但是我將在此用內聯注釋對此進行介紹。 請注意,我使用一些速記約定(例如Marshal.SizeOf<T>() ),它們需要.NET 4.5.1或更高版本。

// The class that does the marshaling. Making it generic is not required, but
// will make it easier to use the same custom marshaler for multiple array types.
public class ArrayMarshaler<T> : ICustomMarshaler
{
    // All custom marshalers require a static factory method with this signature.
    public static ICustomMarshaler GetInstance (String cookie)
    {
        return new ArrayMarshaler<T>();
    }

    // This is the function that builds the managed type - in this case, the managed
    // array - from a pointer. You can just return null here if only sending the 
    // array as an in-parameter.
    public Object MarshalNativeToManaged (IntPtr pNativeData)
    {
        // First, sanity check...
        if (IntPtr.Zero == pNativeData) return null;
        // Start by reading the size of the array ("Length" from your ABS_DATA struct)
        int length = Marshal.ReadInt32(pNativeData);
        // Create the managed array that will be returned
        T[] array = new T[length];
        // For efficiency, only compute the element size once
        int elSiz = Marshal.SizeOf<T>();
        // Populate the array
        for (int i = 0; i < length; i++)
        {
            array[i] = Marshal.PtrToStructure<T>(pNativeData + sizeof(int) + (elSiz * i));
        }
        // Alternate method, for arrays of primitive types only:
        // Marshal.Copy(pNativeData + sizeof(int), array, 0, length);
        return array;
    }

    // This is the function that marshals your managed array to unmanaged memory.
    // If you only ever marshal the array out, not in, you can return IntPtr.Zero
    public IntPtr MarshalManagedToNative (Object ManagedObject)
    {
        if (null == ManagedObject) return IntPtr.Zero;
        T[] array = (T[])ManagedObj;
        int elSiz = Marshal.SizeOf<T>();
        // Get the total size of unmanaged memory that is needed (length + elements)
        int size = sizeof(int) + (elSiz * array.Length);
        // Allocate unmanaged space. For COM, use Marshal.AllocCoTaskMem instead.
        IntPtr ptr = Marshal.AllocHGlobal(size);
        // Write the "Length" field first
        Marshal.WriteInt32(ptr, array.Length);
        // Write the array data
        for (int i = 0; i < array.Length; i++)
        {   // Newly-allocated space has no existing object, so the last param is false
            Marshal.StructureToPtr<T>(array[i], ptr + sizeof(int) + (elSiz * i), false);
        }
        // If you're only using arrays of primitive types, you could use this instead:
        //Marshal.Copy(array, 0, ptr + sizeof(int), array.Length);
        return ptr;
    }

    // This function is called after completing the call that required marshaling to
    // unmanaged memory. You should use it to free any unmanaged memory you allocated.
    // If you never consume unmanaged memory or other resources, do nothing here.
    public void CleanUpNativeData (IntPtr pNativeData)
    {
        // Free the unmanaged memory. Use Marshal.FreeCoTaskMem if using COM.
        Marshal.FreeHGlobal(pNativeData);
    }

    // If, after marshaling from unmanaged to managed, you have anything that needs
    // to be taken care of when you're done with the object, put it here. Garbage 
    // collection will free the managed object, so I've left this function empty.
    public void CleanUpManagedData (Object ManagedObj)
    { }

    // This function is a lie. It looks like it should be impossible to get the right 
    // value - the whole problem is that the size of each array is variable! 
    // - but in practice the runtime doesn't rely on this and may not even call it.
    // The MSDN example returns -1; I'll try to be a little more realistic.
    public int GetNativeDataSize ()
    {
        return sizeof(int) + Marshal.SizeOf<T>();
    }
}

哇,好長! 好吧,那里有。 我希望人們看到這一點,因為那里有很多錯誤的答案和誤解...

這是不可能的包含可變長度數組編組結構(但它可能的編組可變長度數組作為函數參數)。 您將必須手動讀取數據:

IntPtr nativeData = ... ;
var length = Marshal.ReadUInt32 (nativeData) ;
var bytes  = new byte[length] ;

Marshal.Copy (new IntPtr ((long)nativeData + 4), bytes, 0, length) ;

如果要保存的數據不是字符串,則不必將其存儲在字符串中。 除非原始數據類型為char*否則我通常不編組字符串。 否則, byte[]應該起作用。

嘗試:

[MarshalAs(UnmanagedType.ByValArray, SizeConst=[whatever your size is]]
byte[] Data;

如果以后需要將其轉換為字符串,請使用:

System.Text.Encoding.UTF8.GetString(your byte array here). 

顯然,盡管UTF-8通常就足夠了,但您仍需要根據需要更改編碼。

我現在看到了問題,您必須封送一個VARIABLE長度數組。 MarshalAs不允許這樣做,並且必須通過引用發送數組。

如果數組長度可變,則您的byte[]必須為IntPtr,因此您可以使用,

IntPtr Data;

代替

[MarshalAs(UnmanagedType.ByValArray, SizeConst=[whatever your size is]]
byte[] Data;

然后,您可以使用Marshal類訪問基礎數據。

就像是:

uint length = yourABSObject.Length;
byte[] buffer = new byte[length];

Marshal.Copy(buffer, 0, yourABSObject.Data, length);

您可能需要在完成操作后清理內存以避免泄漏,盡管我懷疑當yourABSObject超出范圍時GC會清理內存。 無論如何,這是清理代碼:

Marshal.FreeHGlobal(yourABSObject.Data);

您試圖byte[ABS_VARLEN]一個byte[ABS_VARLEN]東西,好像它是一個長度為1的string 。您需要弄清楚ABS_VARLEN常量是什么,並將數組封送為:

[MarshalAs(UnmanagedType.LPArray, SizeConst = 1024)]
public byte[] Data;

(1024中有一個占位符;請填寫ASB_VARLEN的實際值。)

我認為,固定數組並獲取其地址更簡單,更有效。

假設您需要將abs_data傳遞給myNativeFunction(abs_data*)

public struct abs_data
{
    public uint Length;
    public IntPtr Data;
}

[DllImport("myDll.dll")]
static extern void myNativeFunction(ref abs_data data);

void CallNativeFunc(byte[] data)
{
    GCHandle pin = GCHandle.Alloc(data, GCHandleType.Pinned);

    abs_data tmp;
    tmp.Length = data.Length;
    tmp.Data = pin.AddrOfPinnedObject();

    myNativeFunction(ref tmp);

    pin.Free();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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