Pergunta

Estou tendo problemas para chamar funções de uma biblioteca nativa do código C# gerenciado. Estou desenvolvendo a estrutura compacta 3.5 (Windows Mobile 6.x), caso isso faria alguma diferença.

Estou trabalhando com as funções Wavein* do CORELLL.DLL (elas estão no WinMm.dll em janelas regulares, acredito). Isso é o que eu criei:

// namespace winmm; class winmm
[StructLayout(LayoutKind.Sequential)]
public struct WAVEFORMAT
{
    public ushort wFormatTag;
    public ushort nChannels;
    public uint nSamplesPerSec;
    public uint nAvgBytesPerSec;
    public ushort nBlockAlign;
    public ushort wBitsPerSample;
    public ushort cbSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct WAVEHDR
{
    public IntPtr lpData;
    public uint dwBufferLength;
    public uint dwBytesRecorded;
    public IntPtr dwUser;
    public uint dwFlags;
    public uint dwLoops;
    public IntPtr lpNext;
    public IntPtr reserved;
}

public delegate void AudioRecordingDelegate(IntPtr deviceHandle, uint message, IntPtr instance, ref WAVEHDR wavehdr, IntPtr reserved2);

[DllImport("coredll.dll")]
public static extern int waveInAddBuffer(IntPtr hWaveIn, ref WAVEHDR lpWaveHdr, uint cWaveHdrSize);
[DllImport("coredll.dll")]
public static extern int waveInPrepareHeader(IntPtr hWaveIn, ref WAVEHDR lpWaveHdr, uint Size);
[DllImport("coredll.dll")]
public static extern int waveInStart(IntPtr hWaveIn);

// some other class
private WinMM.WinMM.AudioRecordingDelegate waveIn;
private IntPtr handle;
private uint bufferLength;

private void setupBuffer()
{
    byte[] buffer = new byte[bufferLength];
    GCHandle bufferPin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    WinMM.WinMM.WAVEHDR hdr = new WinMM.WinMM.WAVEHDR();
    hdr.lpData = bufferPin.AddrOfPinnedObject();
    hdr.dwBufferLength = this.bufferLength;
    hdr.dwFlags = 0;

    int i = WinMM.WinMM.waveInPrepareHeader(this.handle, ref hdr, Convert.ToUInt32(Marshal.SizeOf(hdr)));
    if (i != WinMM.WinMM.MMSYSERR_NOERROR)
    {
        this.Text = "Error: waveInPrepare";
        return;
    }
    i = WinMM.WinMM.waveInAddBuffer(this.handle, ref hdr, Convert.ToUInt32(Marshal.SizeOf(hdr)));
    if (i != WinMM.WinMM.MMSYSERR_NOERROR)
    {
        this.Text = "Error: waveInAddrBuffer";
        return;
    }
}

private void setupWaveIn()
{
    WinMM.WinMM.WAVEFORMAT format = new WinMM.WinMM.WAVEFORMAT();
    format.wFormatTag = WinMM.WinMM.WAVE_FORMAT_PCM;
    format.nChannels = 1;
    format.nSamplesPerSec = 8000;
    format.wBitsPerSample = 8;
    format.nBlockAlign = Convert.ToUInt16(format.nChannels * format.wBitsPerSample);
    format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
    this.bufferLength = format.nAvgBytesPerSec;
    format.cbSize = 0;

    int i = WinMM.WinMM.waveInOpen(out this.handle, WinMM.WinMM.WAVE_MAPPER, ref format, Marshal.GetFunctionPointerForDelegate(waveIn), 0, WinMM.WinMM.CALLBACK_FUNCTION);
    if (i != WinMM.WinMM.MMSYSERR_NOERROR) 
    {
        this.Text = "Error: waveInOpen";
        return;
    }

    setupBuffer();

    WinMM.WinMM.waveInStart(this.handle);
}

Eu li muito sobre marechar os últimos dias, no entanto, não faço com que esse código funcione. Quando minha função de retorno de chamada é chamada (Wavein) quando o buffer está cheio, a estrutura HDR passada de volta no WaveHDR é obviamente corrompida. Aqui está um exame de como a estrutura se parece nesse ponto:

-      wavehdr {WinMM.WinMM.WAVEHDR}   WinMM.WinMM.WAVEHDR
         dwBufferLength 0x19904c00  uint
         dwBytesRecorded    0x0000fa00  uint
         dwFlags    0x00000003  uint
         dwLoops    0x1990f6a4  uint
+       dwUser  0x00000000  System.IntPtr
+       lpData  0x00000000  System.IntPtr
+       lpNext  0x00000000  System.IntPtr
+       reserved    0x7c07c9a0  System.IntPtr

Obviamente, isso não é o que eu esperava passar. Estou claramente preocupado com a ordem dos campos na vista. Não sei se o Visual Studio .NET se preocupa com a ordem de memória real ao exibir o registro na visão "local", mas eles obviamente não são exibidos na ordem em que especiei na estrutura.

Em seguida, não há ponteiro de dados e o campo BufferLength é muito alto. Curiosamente, o campo Bytes -Recorded é exatamente 64000 - BufferLength e Bytes -Recorded, eu esperaria que ambos fossem 64000. Não sei o que exatamente está dando errado, talvez alguém possa me ajudar nisso. Eu sou um Noob absoluto para gerenciar programação de código e marechalling, então, por favor, não seja muito duro comigo por todas as coisas estúpidas que fiz.

Oh, aqui está a definição de código C para Wavehdr que eu encontrei aqui, Acredito que poderia ter feito algo errado na definição C# struct:

/* wave data block header */
typedef struct wavehdr_tag {
    LPSTR       lpData;                 /* pointer to locked data buffer */
    DWORD       dwBufferLength;         /* length of data buffer */
    DWORD       dwBytesRecorded;        /* used for input only */
    DWORD_PTR   dwUser;                 /* for client's use */
    DWORD       dwFlags;                /* assorted flags (see defines) */
    DWORD       dwLoops;                /* loop control counter */
    struct wavehdr_tag FAR *lpNext;     /* reserved for driver */
    DWORD_PTR   reserved;               /* reserved for driver */
} WAVEHDR, *PWAVEHDR, NEAR *NPWAVEHDR, FAR *LPWAVEHDR;

Se você está acostumado a trabalhar com todas essas ferramentas de baixo nível, como aritmética de ponteiro, elenco, etc., comece a escrever, o código gerenciado é uma dor no saco. É como tentar aprender a nadar com as mãos amarradas nas costas. Algumas coisas que tentei (sem efeito): .NET Compact Framework não parece suportar a diretiva pack = 2^x em [structlayout]. Eu tentei [structLayout (layoutkind.explicit)] e usei 4 bytes e 8 bytes. 4 bytes alinhamento me o mesmo resultado que o código acima e o alinhamento de 8 bytes só pioraram as coisas - mas é o que eu esperava.Curiosamente, se eu mover o código do SetupBuffer para o Setuppwavein e não declarar o gchandle no contexto da classe, mas em um contexto local de Setuppwavein the struct retornado pela função de retorno de chamada não parece estar corrompido. Não tenho certeza, no entanto, por que esse é o caso e como posso usar esse conhecimento para corrigir meu código. Esqueça isso. Eu misturei coisas com código muito mais antigo que usei.

Eu realmente apreciaria quaisquer bons links sobre o Marshalling, chamando código não gerenciado de C#, etc. Então ficaria muito feliz se alguém pudesse apontar meus erros. O que estou fazendo errado? Por que eu não entendo o que eu esperaria.

Foi útil?

Solução 3

Ok, eu descobri. Todo o meu código estava basicamente correto. No entanto, eu estraguei tudo com a estrutura do WaveHDR. O Wavein* funciona não apenas espera uma referência a uma estrutura WaveHDR, mas também espera que essa estrutura se sustente até que o WaveinHeaderunPrepare seja chamado. Portanto, a estrutura WaveHDR precisará ser criada em um contexto global ou pelo menos grande o suficiente para sustentar o WaveinHeaderunPrepare é chamado e provavelmente precisa ser preso também usando um gchandle, para que não mude sua posição na memória (que Afaik não é garantido no código gerenciado). Aqui é meu código atualizado e limpo:

    private WinMM.WinMM.AudioRecordingDelegate waveIn;
    private IntPtr handle;
    private WinMM.WinMM.WAVEHDR header;
    private GCHandle headerPin;
    private GCHandle bufferPin;
    private byte[] buffer;
    private uint bufferLength;

    private void setupBuffer()
    {
        header.lpData = bufferPin.AddrOfPinnedObject();
        header.dwBufferLength = bufferLength;
        header.dwFlags = 0;
        header.dwBytesRecorded = 0;
        header.dwLoops = 0;
        header.dwUser = IntPtr.Zero;
        header.lpNext = IntPtr.Zero;
        header.reserved = IntPtr.Zero;

        int i = WinMM.WinMM.waveInPrepareHeader(handle, ref header, Convert.ToUInt32(Marshal.SizeOf(header)));
        if (i != WinMM.WinMM.MMSYSERR_NOERROR)
        {
            this.Text = "Error: waveInPrepare " + i.ToString();
            return;
        }
        i = WinMM.WinMM.waveInAddBuffer(handle, ref header, Convert.ToUInt32(Marshal.SizeOf(header)));
        if (i != WinMM.WinMM.MMSYSERR_NOERROR)
        {
            this.Text = "Error: waveInAddrBuffer";
            return;
        }
    }

    private void setupWaveIn()
    {
        handle = new IntPtr();
        WinMM.WinMM.WAVEFORMAT format;
        format.wFormatTag = WinMM.WinMM.WAVE_FORMAT_PCM;
        format.nChannels = 1;
        format.nSamplesPerSec = 8000;
        format.wBitsPerSample = 8;
        format.nBlockAlign = Convert.ToUInt16(format.nChannels * format.wBitsPerSample);
        format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
        bufferLength = format.nAvgBytesPerSec / 800;
        headerPin = GCHandle.Alloc(header, GCHandleType.Pinned);
        buffer = new byte[bufferLength];
        bufferPin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        format.cbSize = 0;

        int i = WinMM.WinMM.waveInOpen(out handle, WinMM.WinMM.WAVE_MAPPER, ref format, Marshal.GetFunctionPointerForDelegate(waveIn), IntPtr.Zero, WinMM.WinMM.CALLBACK_FUNCTION);
        if (i != WinMM.WinMM.MMSYSERR_NOERROR) 
        {
            this.Text = "Error: waveInOpen";
            return;
        }

        setupBuffer();

        WinMM.WinMM.waveInStart(handle);
    }

    private void callbackWaveIn(IntPtr deviceHandle, uint message, IntPtr instance, ref WinMM.WinMM.WAVEHDR wavehdr, IntPtr reserved2)
    {
        if (message == WinMM.WinMM.WIM_DATA)
            if (this.InvokeRequired)
                this.Invoke(waveIn, deviceHandle, message, instance, wavehdr, reserved2);
            else
            {
                if (wavehdr.dwBytesRecorded > 0)
                {
                    foreach (byte buf in buffer)
                    {
                        // do something cool with your byte stream
                    }
                }

                int i = WinMM.WinMM.waveInUnprepareHeader(deviceHandle, ref header, Convert.ToUInt32(Marshal.SizeOf(wavehdr)));
                if (i != WinMM.WinMM.MMSYSERR_NOERROR)
                {
                    this.Text = "Error: waveInUnprepareHeader " + i;
                }
                setupBuffer();
            }
    }

Obrigado pela ajuda. Espero que alguém possa usar o código que eu consegui criar.

Outras dicas

Suas declarações P/Invoke são impecáveis. No entanto, há algo muito ímpar sobre o dump da onda que você postou. Está faltando o campo LPData. Se você inserir isso, todos os números se alinham corretamente (ou seja, lpdata = 0x19904c00, dwbufferLength = 0x0000FA00, etc).

Não tenho certeza de como isso aconteceu, mas de alguma forma está usando a declaração de WaveHDR errada. Winmm.winmm deve ser uma dica.

Pinvoke.net é o lugar para encontrar declarações de Pinvoke.Esta página Descreve o método WaveInaddBuffer e seu equivalente C#, bem como links para o WaveHDR.

Eu dei uma olhada em vários métodos que você usa, mas não consegui encontrar nada útil no seu caso. Uma diferença com a versão do Pinvoke.net e a sua é que Pinvoke usa a propriedade Charset da StructLayout, mas acho que não é relevante.

Um bom livro sobre o assunto da interoperabilidade é: Rede e coma

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top