Как я могу выделить структуру, которая содержит массив переменного размера для С#?

Как мне маршалировать этот тип С++?

Структура 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;
    }

Ответы

Ответ 1

Старый вопрос, но я недавно должен был сделать это сам, и все существующие ответы плохие, поэтому...

Лучшим решением для маршалинга массива переменной длины в структуре является использование настраиваемого маршалера. Это позволяет вам управлять кодом, который использует среда выполнения для преобразования управляемых и неуправляемых данных. К сожалению, пользовательский маршалинг плохо документирован и имеет несколько странных ограничений. Я быстро их рассмотрю, затем перейду к решению.

Раздражающе, вы не можете использовать настраиваемый маршалинг для элемента массива структуры или класса. Там нет документированной или логической причины этого ограничения, и компилятор не будет жаловаться, но вы получите исключение во время выполнения. Кроме того, существует функция, которую должны реализовать пользовательские маршалисты, 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);

К сожалению, во время выполнения вы, по-видимому, не можете массировать массивы внутри структур данных как угодно, кроме SafeArray или ByValArray. 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);

ОК, теперь вы можете увидеть, как объявить тип элемента массива (если необходимо) и как передать массив неуправляемой функции. Тем не менее, нам все еще нужен этот пользовательский маршалер. Вы должны прочитать "" Внедрение интерфейса 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>();
    }
}

Ну, это было долго! Ну, вот оно. Я надеюсь, что люди это видят, потому что там много плохих ответов и недоразумений...

Ответ 2

Невозможно выстроить структуры, содержащие массивы переменной длины (но возможно массирование массивов переменной длины в качестве параметров функции). Вам нужно будет прочитать свои данные вручную:

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

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

Ответ 3

Если сохраненные данные не являются строкой, вам не нужно сохранять ее в строке. Обычно я не маршалирую строку, если исходный тип данных не был char*. В противном случае следует выполнить byte[].

Try:

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

Вам может потребоваться очистить память, когда вы закончите, чтобы избежать утечки, хотя я подозреваю, что GC очистит ее, когда ваш ABSObject выходит за рамки. В любом случае, вот код очистки:

Marshal.FreeHGlobal(yourABSObject.Data);

Ответ 4

Вы пытаетесь маршалировать то, что есть byte[ABS_VARLEN], как если бы это была string длины 1. Вам нужно выяснить, что такое константа ABS_VARLEN и маршалировать массив как:

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

(1024 есть местозаполнитель, заполните все, что имеет значение ASB_VARLEN.)

Ответ 5

По-моему, проще и эффективнее связывать массив и принимать его адрес.

Предполагая, что вам нужно пройти 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();
}