Comment puis-je marsher une structure qui contient un tableau de taille variable à C#?

Comment puis-je rassembler ce type de C++?

la structure ABS_DATA est utilisée pour associer un bloc de données arbitrairement long à l'information de longueur. La longueur déclarée du tableau Data est 1, mais la longueur réelle est donnée par le membre Length .

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

j'ai essayé le code suivant, mais ça ne marche pas. La variable data est toujours vide et je suis sûr qu'elle contient des données.

[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;
    }
17
demandé sur Rob Kennedy 0000-00-00 00:00:00

5 réponses

question ancienne, mais j'ai récemment dû le faire moi-même et toutes les réponses existantes sont pauvres, donc...

la meilleure solution pour regrouper un réseau de longueur variable dans une structure est d'utiliser un custom marshaler . Cela vous permet de contrôler le code que l'exécution utilise pour convertir entre les données gérées et non gérées. Malheureusement, la mise en service sur mesure est mal documentée et comporte quelques limites bizarres. Je vais couvrir ceux rapidement, puis passer en revue la solution.

fâcheusement, vous ne pouvez pas utiliser la composition personnalisée sur un élément de tableau d'une structure ou d'une classe. Il n'y a aucune raison documentée ou logique pour cette limitation, et le compilateur ne se plaindra pas, mais vous obtiendrez une exception à l'exécution. En outre, il ya une fonction que les marshalers coutume doit mettre en œuvre, int GetNativeDataSize() , qui est évidemment impossible à mettre en œuvre avec précision (il ne vous passe pas une instance de l'objet pour demander sa taille, de sorte que vous pouvez seulement aller le type, qui est bien sûr variable de taille!) Heureusement, cette fonction n'a pas d'importance. Je ne l'ai jamais vu se faire appeler, et il fonctionne très bien même s'il renvoie une valeur bidon (un exemple MSDN le renvoie -1).

tout d'abord, voici à quoi je pense que votre prototype natif pourrait ressembler (j'utilise P/Invoke ici, mais cela fonctionne aussi pour 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);

Voici la version naïve de comment vous avez pu utiliser une coutume marshaleur (qui devraient avoir travaillé). Je vais arriver à le marshaleur lui-même en un peu...

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

malheureusement, à l'exécution, vous ne pouvez apparemment pas marshal tableaux à l'intérieur des structures de données comme quoi que ce soit sauf SafeArray ou ByValArray . Les SafeArrays sont comptés, mais ils ne ressemblent en rien au format (extrêmement commun) que vous recherchez ici. Donc cela ne marchera pas. ByValArray, bien sûr, exige que la longueur soit connue au moment de la compilation, travailler soit (comme vous avez couru dans). Bizarrement, cependant, vous can utilisez le marshaling personnalisé sur le tableau paramètres , c'est ennuyeux parce que vous devez mettre le MarshalAsAttribute sur chaque paramètre qui utilise ce type, au lieu de simplement le mettre sur un champ et ayant qui s'appliquent partout, vous utilisez le type contenant ce champ, mais c'est la vie. Il ressemble à ceci:

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

dans cet exemple, j'ai conservé le abs_data type, dans le cas où vous voulez faire quelque chose de spécial avec elle (constructeurs, fonctions statiques, propriétés, héritage, quoi que ce soit). Si votre tableau elements se composait d'un type complexe, vous modifieriez la structure pour représenter ce type complexe. Cependant, dans ce cas, abs_data est fondamentalement juste un byte renommé - il n'est même pas "envelopper" le byte; en ce qui concerne le code natif il est plus comme un typedef - donc vous pouvez juste passer un tableau d'octets et sauter entièrement la structure:

// 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, donc maintenant vous pouvez voir comment déclarer le type d'élément du tableau (si nécessaire), et comment passer le tableau à une fonction non gérée. Toutefois, nous avons encore besoin que marshaleur personnalisé. Vous devriez lire " implémenter L'Interface ICustomMarshaler " mais je vais couvrir ceci ici, avec des commentaires en ligne. Notez que j'utilise certaines conventions de raccourci (comme Marshal.SizeOf<T>() ) qui requièrent .NET 4.5.1 ou plus.

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

Ouf, c'était long! Eh bien, là vous l'avez. J'espère que les gens voient cela, parce qu'il y a beaucoup de mauvaises réponses et de l'incompréhension...

21
répondu CBHacking 2016-08-14 16:06:40

il n'est pas possible de regrouper des structures contenant des tableaux de longueur variable (mais il est possible de regrouper des tableaux de longueur variable comme paramètres de fonction). Vous devrez lire vos données manuellement:

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

Marshal.Copy (new IntPtr ((long)nativeData + 4), bytes, 0, length) ;
6
répondu Anton Tykhyy 2011-05-05 18:52:38

Si les données enregistrées n'est pas une chaîne, vous n'avez pas à stocker dans une chaîne de caractères. Je n'ai pas l'habitude de faire appel à une chaîne de caractères à moins que le type de données original ne soit un char* . Sinon, un byte[] devrait suffire.

, Essayez:

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

si vous devez convertir cette chaîne plus tard, utilisez:

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

évidemment, vous devez varier l'encodage à ce que vous avez besoin, bien que UTF-8 habituellement est suffisante.

je vois le problème maintenant, vous devez marshal un tableau de longueur VARIABLE. Les MarshalAs ne permettent pas cela et le tableau devra être envoyé par référence.

si la longueur du tableau est variable, votre byte[] doit être un IntPtr, donc vous devez utiliser,

IntPtr Data;

au lieu de

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

vous pouvez alors utiliser la classe Marshal pour accéder aux données sous-jacentes.

quelque chose comme:

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

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

vous pourriez avoir besoin de nettoyer votre mémoire lorsque vous avez terminé pour éviter une fuite, bien que je soupçonne le GC va nettoyer lorsque votre objet rebsobject sort de la portée. Bref, voici le code de nettoyage:

Marshal.FreeHGlobal(yourABSObject.Data);
5
répondu Jonathan Henson 2013-01-15 12:43:32

Vous essayez de maréchal de quelque chose qui est un byte[ABS_VARLEN] comme si c'était un string de longueur 1. Vous aurez besoin de comprendre ce qu'est la constante ABS_VARLEN et d'organiser le tableau comme:

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

(1024 il y a un espace réservé; remplir ce que la valeur réelle de ASB_VARLEN est.)

2
répondu Michael Edenfield 2011-05-05 18:10:38

à mon avis, il est plus simple et plus efficace d'épingler le tableau et de prendre son adresse.

en supposant que vous devez passer 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
répondu Benoit Blanchon 2016-12-15 14:48:35