web-dev-qa-db-ja.com

可変サイズの配列を含む構造体を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;
    }
18
Ezi

古い質問ですが、最近自分でやらなければならず、既存の答えはすべて貧弱なので...

構造体で可変長配列をマーシャリングするための最良の解決策は、 カスタムマーシャラー を使用することです。これにより、ランタイムがマネージデータとアンマネージデータの間で変換するために使用するコードを制御できます。残念ながら、カスタムマーシャリングは十分に文書化されておらず、いくつかの奇妙な制限があります。それらについて簡単に説明してから、解決策を検討します。

厄介なことに、構造体またはクラスの配列要素でカスタムマーシャリングを使用することはできません。この制限の文書化された理由や論理的な理由はなく、コンパイラーは文句を言いませんが、実行時に例外が発生します。また、カスタムマーシャラーが実装する必要のある関数int GetNativeDataSize()がありますが、これは明らかに正確に実装することは不可能です(オブジェクトのインスタンスを渡さずにサイズを尋ねるので、オフにすることしかできません。タイプ、もちろん可変サイズです!)幸い、この関数は重要ではありません。私はそれが呼び出されるのを見たことがなく、カスタムマーシャラーは偽の値を返しても正常に動作します(1つの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);
_

残念ながら、実行時には、データ構造内の配列をSafeArrayまたはByValArray以外のものとしてマーシャリングすることはできません。 SafeArrayはカウントされますが、ここで探している(非常に一般的な)形式とはまったく異なります。だからそれはうまくいきません。もちろん、ByValArrayでは、コンパイル時に長さがわかっている必要があるため、(遭遇したときに)どちらも機能しません。奇妙なことに、しかし、あなたはcan配列パラメータでカスタムマーシャリングを使用できます、これMarshalAsAttributeを、この型を使用するすべてのパラメーターに配置する必要があるため、面倒です。1つのフィールドに配置して、そのフィールドを含む型を使用するすべての場所に適用するのではなく、c'est lavieです。次のようになります。

_[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);
_

これで、配列要素の型を宣言する方法(必要な場合)と、配列をアンマネージ関数に渡す方法を確認できました。ただし、そのカスタムマーシャラーはまだ必要です。 " ICustomMarshalerインターフェイスの実装 "を読む必要がありますが、ここではインラインコメントで説明します。 .NET 4.5.1以降を必要とするいくつかの省略規則(Marshal.SizeOf<T>()など)を使用していることに注意してください。

_// 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>();
    }
}
_

ふぅ、それは長かった!さて、あなたはそれを持っています。悪い答えや誤解がたくさんあるので、人々がこれを見てくれることを願っています...

28
CBHacking

可変長配列を含む構造体をマーシャリングすることはできません(ただし、可変長配列を関数パラメーターとしてマーシャリングすることは可能です)。データを手動で読み取る必要があります。

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

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

保存するデータが文字列でない場合は、文字列に保存する必要はありません。元のデータ型がchar*でない限り、私は通常、文字列にマーシャリングしません。それ以外の場合は、byte[]で十分です。

試してください:

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

後でこれを文字列に変換する必要がある場合は、次を使用します。

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

明らかに、必要なものにエンコードを変更する必要がありますが、通常は TF-8 で十分です。

問題が発生しました。可変長配列をマーシャリングする必要があります。 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);
5
Jonathan Henson

byte[ABS_VARLEN]である何かを長さ1のstringであるかのようにマーシャリングしようとしています。ABS_VARLEN定数が何であるかを理解し、配列を次のようにマーシャリングする必要があります。

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

(1024にはプレースホルダーがあります。ASB_VARLENの実際の値を入力してください。)

2

私の意見では、配列を固定してそのアドレスを取得する方が簡単で効率的です。

_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();
}
_
0
Benoit Blanchon