C #: объект с пользовательским маршаллером, не содержащий данных после вызова PInvoke - PullRequest
1 голос
/ 14 апреля 2010

У меня проблема с PInvoking некоторых функций WinAPI, которые принимают WAVEFORMATEX структуры в качестве параметров.Поскольку длина структуры WAVEFORMATEX может варьироваться, я реализовал класс WaveFormatEX, который маршалируется специальным классом маршаллера (который реализует ICustmoMarshaller).Это следует примеру Аарона Лерча в его блоге ( Часть 1 , Часть 2 ), но с некоторыми изменениями с моей стороны.

Когда я звонюфункция API из моего кода, методы MarshalManagedToNative и MarshalNativeToManaged пользовательского маршаллера вызываются, а в конце MarshalNativeToManaged, управляемый объект содержит правильные значения .Но когда выполнение возвращается к моему вызывающему коду, объект WaveFormatEx не содержит значений, считанных во время вызова API .

Таким образом, вопрос заключается в следующем: почему данные, которые правильно перенаправлены из нативного в управляемый, не отображаются в моем WaveFormatEx объекте после нативного вызова API?Что я здесь не так делаю?

Редактировать:
Для пояснения, вызов функции завершается успешно, так же как и маршалинг объекта WaveFormatEx обратно в управляемый код.Как только выполнение возвращается из метода маршаллинга в область, из которой был вызван метод, объект WaveFormatEx, который был объявлен в этой области вызова, не содержит данных результата.

Вот прототип функции и класс WaveFormatEx:

[DllImport("avifil32.dll")]
public static extern int AVIStreamReadFormat(
    int Stream,
    int Position,
    [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, 
        MarshalTypeRef = typeof(WaveFormatExMarshaler))]
    WaveFormatEx Format,
    ref int Size
    );

[StructLayout(LayoutKind.Sequential)]
public class WaveFormatEx
{
    public int FormatTag;
    public short Channels;
    public int SamplesPerSec;
    public int AvgBytesPerSec;
    public short BlockAlign;
    public short BitsPerSample;
    public short Size;
    public byte[] AdditionalData;

    public WaveFormatEx(short AdditionalDataSize)
    {
        WaveFormat.Size = AdditionalDataSize;
        AdditionalData = new byte[AdditionalDataSize];
    }
}

Методы сортировки выглядят следующим образом:

public object MarshalNativeToManaged(System.IntPtr NativeData)
{
    WaveFormatEx ManagedObject = new WaveFormatEx(0);
    ManagedObject = (WaveFormatEx)Marshal.PtrToStructure(
       NativeData, typeof(WaveFormatEx));

    ManagedObject.AdditionalData = new byte[ManagedObject.Size];

    // If there is extra data, marshal it
    if (ManagedObject.WaveFormat.Size > 0)
    {
        NativeData = new IntPtr(
            NativeData.ToInt32() + 
            Marshal.SizeOf(typeof(WaveFormatEx)));
        ManagedObject.AdditionalData = new byte[ManagedObject.WaveFormat.Size];
        Marshal.Copy(NativeData, ManagedObject.AdditionalData, 0, 
            ManagedObject.WaveFormat.Size);
    }
    return ManagedObject;
}

public System.IntPtr MarshalManagedToNative(object Object)
{
    WaveFormatEx ManagedObject = (WaveFormatEx)Object;

    IntPtr NativeStructure = Marshal.AllocHGlobal(
        GetNativeDataSize(ManagedObject) + ManagedObject.WaveFormat.Size);

    Marshal.StructureToPtr(ManagedObject, NativeStructure, false);

    // Marshal extra data 
    if (ManagedObject.WaveFormat.Size > 0)
    {
        IntPtr dataPtr = new IntPtr(NativeStructure.ToInt32() 
            + Marshal.SizeOf(typeof(WaveFormatEx)));
        Marshal.Copy(ManagedObject.AdditionalData, 0, dataPtr,  Math.Min(
            ManagedObject.WaveFormat.Size,
            ManagedObject.AdditionalData.Length));
    }
    return NativeStructure;
}

И это мой код вызова:

WaveFormatEx test = new WaveFormatEx(100);
int Size = System.Runtime.InteropServices.Marshal.SizeOf(test);

// After this call, test.FormatTag should be set to 1 (PCM audio), 
// but it is still 0, as well as all the other members
int Result = Avi.AVIStreamReadFormat(AudioStream, 0, test, ref Size);

1 Ответ

2 голосов
/ 14 апреля 2010

Есть несколько ошибок в коде и объявлениях, которые мешают этому коду работать в 64-битной операционной системе. Обязательно установите для платформы целевое значение x86.

Вы уверены, что нативная функция действительно возвращает данные? Что такое возвращаемое значение Result? Ненулевое значение указывает на ошибку.

Правильный способ вызова этой функции - вызвать ее дважды. Сначала с аргументом lpFormat, установленным в null (IntPtr.Zero), он сообщает вам, какой размер буфера ему нужен (возвращается lpbcFormat). Затем вы создаете буфер и вызываете его снова.

Вместо пользовательского маршаллера я просто создал бы буфер с Marshal.AllocHGobal после первого вызова и передал бы IntPtr, который он возвращает в качестве аргумента lpFormat во втором вызове. Затем, если вы получите успешный код возврата, используйте Marshal.PtrToStructure, чтобы написать WaveFormatEx. И Marshal.Copy, чтобы получить дополнительные данные.

Fwiw, использование ref заставляет маршаллера P / Invoke передавать WaveFormatEx ** в функцию, но он ожидает WaveFormatEx *. Что заставит его перезаписать данные в куче мусора, уничтожив его внутренний формат. Kaboom следующий, когда CLR замечает это.

Посмотрите на проект NAudio как хорошую альтернативу для самостоятельной работы.

...