0

I've implemented this method in c#:

HRESULT CreateSourceVoice(
  [out]           IXAudio2SourceVoice **ppSourceVoice,
  [in]            const WAVEFORMATEX *pSourceFormat,
  [in]            UINT32 Flags = 0,
  [in]            float MaxFrequencyRatio = XAUDIO2_DEFAULT_FREQ_RATIO,
  [in, optional]  IXAudio2VoiceCallback *pCallback = NULL,
  [in, out]       const XAUDIO2_VOICE_SENDS *pSendList = NULL,
  [in, optional]  const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL
);

But I've got a problem with marshaling the 5th parameter. But first this is my implementation of the IXAudio2VoiceCallback interface:

[SuppressUnmanagedCodeSecurity]
public interface IXAudio2VoiceCallback
{
    void OnVoiceProcessingPassStart([In] Int32 bytesRequired);

    void OnVoiceProcessingPassEnd();

    void OnStreamEnd();

    void OnBufferStart([In] IntPtr bufferContextPtr);

    void OnBufferEnd([In] IntPtr bufferContextPtr);

    void OnLoopEnd([In] IntPtr bufferContextPtr);

    void OnVoiceError([In] IntPtr bufferContextPtr, [In] int error);
}

public class VoiceCallback : IXAudio2VoiceCallback
{
    //... events ...

    void IXAudio2VoiceCallback.OnVoiceProcessingPassStart(int bytesRequired)
    {
        if(ProcessingPassStart != null)
            ProcessingPassStart(bytesRequired);
    }

    void IXAudio2VoiceCallback.OnVoiceProcessingPassEnd()
    {
        if(ProcessingPassEnd != null)
            ProcessingPassEnd();
    }

    void IXAudio2VoiceCallback.OnStreamEnd()
    {
        if (StreamEnd != null)
            StreamEnd();
    }

    void IXAudio2VoiceCallback.OnBufferStart(IntPtr bufferContextPtr)
    {
        if(BufferStart != null)
            BufferStart(bufferContextPtr);
    }

    void IXAudio2VoiceCallback.OnBufferEnd(IntPtr bufferContextPtr)
    {
        if(BufferEnd != null)
            BufferEnd(bufferContextPtr);
    }

    void IXAudio2VoiceCallback.OnLoopEnd(IntPtr bufferContextPtr)
    {
        if(LoopEnd != null)
            LoopEnd(bufferContextPtr);
    }

    void IXAudio2VoiceCallback.OnVoiceError(IntPtr bufferContextPtr, int error)
    {
        if(VoiceError != null)
            VoiceError(bufferContextPtr, error);
    }
}

But now my actual problem: I can call CreateSourceVoice and it returns 0 (-> S_OK) but at any time, the callback should be called, the process just stops. There are no error messages (I've also checked the windows event log). Since I am using a quite similar concept as SharpDX does (see here), I've checked the SharpDX source. As far as I could see, SharpDX builds a vtable (see here).

But I am asking myself, whether it is possible to avoid building my own vtable? Isn't there a easier way?

I just would need to be able to get the vtable of the VoiceCallback-class.

The code for CreateSourceVoice looks like this:

public unsafe int CreateSourceVoiceNative(out IntPtr pSourceVoice, IntPtr sourceFormat, VoiceFlags flags, float maxFrequencyRatio, IXAudio2VoiceCallback voiceCallback, VoiceSends? sendList, EffectChain? effectChain)
{
    VoiceSends value0 = sendList.HasValue ? sendList.Value : default(VoiceSends);
    EffectChain value = effectChain.HasValue ? effectChain.Value : default(EffectChain);
    return calli(System.Int32(System.Void*,System.Void*,System.IntPtr,CSCore.XAudio2.VoiceFlags,System.Single,CSCore.XAudio2.IXAudio2VoiceCallback,System.Void*,System.Void*), this._basePtr, &pSourceVoice, sourceFormat, flags, maxFrequencyRatio, voiceCallback, sendList.HasValue ? ((void*)(&value0)) : ((void*)IntPtr.Zero), effectChain.HasValue ? ((void*)(&value)) : ((void*)IntPtr.Zero), *(*(IntPtr*)this._basePtr + (IntPtr)5 * (IntPtr)sizeof(void*)));
}
Florian
  • 5,918
  • 3
  • 47
  • 86
  • 1
    It is not a .NET interface. Using the [ComImport] attribute to tell the CLR that this is a COM interface is a hard requirement. Similarly, your VoiceCallback class must be decorated with [ComVisible(true)] to be callable by other code. – Hans Passant Jun 10 '14 at 12:18
  • Well. I can't use [ComImport] because that would require the [Guid] attribute as well. Since this interface does not have a Guid I can't use that. I've also already tried to apply the [ComVisible(true)] attribute to both (the interface and the implementation of the interface). – Florian Jun 10 '14 at 12:21
  • Hmya, that's a problem, it was written to be used in a C++ program. Try using an arbitrary guid. Using C++/CLI would be advisable. – Hans Passant Jun 10 '14 at 12:30
  • Already tried to use a random guid (generated by visual studio) but still the same problem. I am gonna try out the advice of xoofx. – Florian Jun 10 '14 at 12:32
  • Pretty painful when you don't mention these details in your question btw. Good luck. – Hans Passant Jun 10 '14 at 12:32
  • Well I am sorry. But was trying out so many combinations of attributes for hours... Anyway. Just tried out a combination of your solution and the the one of xoofx. And it finally works. As always, thanks a lot @HansPassant. – Florian Jun 10 '14 at 12:40

2 Answers2

1

You need to implement it using the default COM interop marshalling provided by .NET. Using regular COM/.NET interop, it is a just matter of declaring the interface with the [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] like this:

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), SuppressUnmanagedCodeSecurity]
public interface IXAudio2VoiceCallback
{

and accessing the CreateSourceVoice function through a regular [DllImport] which would take directly the interface IXAudio2VoiceCallback

xoofx
  • 3,682
  • 1
  • 17
  • 32
  • Thanks a lot. Your solution works. BUT there is a new problem. When I start the playback the `OnVoiceProcessingPassStart` methods gets called. The method raises the event and everything works fine. After that the `OnVoiceProcessingPassEnd` method gets called. The methods raises the event BUT as soon as the method exits I get an Access Violation (0xc0000005). As always there is just an memory address but I don't really know what to do with that. Can't even take a look at the disassembly. – Florian Jun 10 '14 at 12:59
  • Just was able to take a look at the disassembly: pastebin.com/9thXpTdC. Do you have any idea? @xoofx – Florian Jun 11 '14 at 13:32
  • problem seems to be solved. see: http://social.msdn.microsoft.com/Forums/office/en-US/985c8f98-3f69-4081-ab14-0a01e58b7bff/xaudio2-access-violation-on-onvoiceprocessingpassend?forum=windowspro-audiodevelopment&prof=required – Florian Jun 11 '14 at 21:36
  • 1
    Ah sorry, forgot completely that `IAudio2VoiceCallback` was not a COM object (despite it is described on msdn http://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.ixaudio2voicecallback.ixaudio2voicecallback%28v=vs.85%29.aspx). Your hack about patching the vtable is a nice workaround. – xoofx Jun 12 '14 at 00:30
  • Anyway. Thanks a lot! But you're right. The documentation is by far not correct... there a quite a lot mistakes. – Florian Jun 12 '14 at 09:05
0

I've actually found a solution. The solution is to override the function pointers of the IUnknown interface in the vtalbe of the com object. I've written a quite ugly but working utils method which patches the vtable:

private static readonly List<IntPtr> _patchedVtables = new List<IntPtr>(); 

public unsafe static IntPtr GetComInterfaceForObjectWithAdjustedVtable(IntPtr ptr, int finalVtableLength, int replaceCount)
{
    var pp = (IntPtr*) (void*) ptr;
    pp = (IntPtr*)pp[0];

    IntPtr z = new IntPtr((void*)pp);

    //since the same vtable applies to all com objects of the same type -> make sure to only patch it once
    if (_patchedVtables.Contains(z))
    {
        return ptr;
    }

    _patchedVtables.Add(z);

    for (int i = 0; i < finalVtableLength; i++)
    {
        IntPtr prev = pp[i];

        pp[i] = pp[i + replaceCount];

        IntPtr after = pp[i];
        //Console.WriteLine("{0} -> {1}", prev, after); //just for debugging
    }
    return ptr;
}

And just use it like this:

p = Marshal.GetComInterfaceForObject(voiceCallback, typeof (IXAudio2VoiceCallback));
p = Utils.Utils.GetComInterfaceForObjectWithAdjustedVtable(p, 7, 3);

The second parameter (in this case 7) is the number of methods, the IXAudio2VoiceCallback contains and the third paramter (in this case 3) is the number of methods to override -> the number of methods of the IUnknown which is three (QueryInterface, AddRef, Release).


Again, big thanks to xoofx.

Florian
  • 5,918
  • 3
  • 47
  • 86