Question

I am wrapping a c++ dll which does high quality sample rate conversion in c# and I am not sure what kind of type I should use for the op0 parameter. A C++ wrapper would call it like this:

int _cdecl process(double* const ip0, int l, double*& op0)

The documentation says about the parameter:

"@param[out] op0 This variable receives the pointer to the resampled data. This pointer may point to the address within the "ip0" input buffer, or to *this object's internal buffer. In real-time applications it is suggested to pass this pointer to the next output audio block and consume any data left from the previous output audio block first before calling the process() function again. The buffer pointed to by the "op0" on return may be owned by the resampler, so it should not be freed by the caller."

What I would like to do is the following:

    [DllImport("r8bsrc.dll", EntryPoint="process", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Process([in] double[] ip0,
                                     int length,
                                     [out] double[] op0);

But I am pretty sure this would not work, since the marshaller cannot know how big the memory behind op1 is, or am I wrong?

So I guess I have to copy the values behind op1 back to a managed array myself. Maybe:

    [DllImport("r8bsrc.dll", EntryPoint="process", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Process([in] double[] ip0,
                                     int length,
                                     out IntPtr op0); //or do i need out double* ?

And then wrap it again with:

    private IntPtr FOutBufferPtr; //reuse it as recommeded
    public int Process(double[] input, out double[] output)
    {
        var outSamples = R8BrainDLLWrapper.Process(input, input.Length, out FOutBufferPtr);         
        output = new double[outSamples];
        Marshal.Copy(FOutBufferPtr, output, 0, outSamples);
    }

What is the optimal way which involves the least number of copies?

EDIT2:

This is the current code, it works perfectly:

    public int Process(double[] input, ref double[] output)
    {
        //pin the input during process
        var pinnedHandle = GCHandle.Alloc(input, GCHandleType.Pinned);

        //resample
        var outSamples = R8BrainDLLWrapper.Process(FUnmanagedInstance, pinnedHandle.AddrOfPinnedObject(), input.Length, out FOutBufferPtr);

        //copy to output array
        if(output.Length < outSamples)
            output = new double[outSamples];

        Marshal.Copy(FOutBufferPtr, output, 0, outSamples);

        //free pin
        pinnedHandle.Free();

        return outSamples;
    }

The signature is now:

    [DllImport("r8bsrc.dll", EntryPoint="r8b_process", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Process(IntPtr instance,
                                     IntPtr ip0,
                                     int length,
                                     out IntPtr op0);
Was it helpful?

Solution

@param[out] op0

This variable receives the pointer to the resampled data. This pointer may point to the address within the "ip0" input buffer, or to *this object's internal buffer. In real-time applications it is suggested to pass this pointer to the next output audio block and consume any data left from the previous output audio block first before calling the process() function again. The buffer pointed to by the "op0" on return may be owned by the resampler, so it should not be freed by the caller.

This immediately presents a constraint on ip0. You must arrange that the buffer that ip0 points to is stable beyond the end of the call to the function. That implies that you must pin it before calling the function. Which in turn implies that it must be declared as IntPtr.

For op0, this points to either memory owned by the resampler, or to a location within the ip0 input buffer. So, again you are going to have to use an IntPtr, this time an out parameter.

So, the declaration must be:

[DllImport("r8bsrc.dll", EntryPoint="process", 
    CallingConvention = CallingConvention.Cdecl)]
public static extern int Process(IntPtr ip0, int length, out IntPtr op0);

And as discussed above, the pointer you pass in ip0 must be obtained using the GCHandle class so that you can pin the array.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top