Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass struct* from c# to c++ dll

Tags:

c++

c#

struct

dll

The struct in c++ dll is defined like this:

struct WAVE_INFO {
    int channel_num;
    int audio_type;
    char *wave_data;
    int wave_length;
};

And the calling method like this:

extern "C" STRUCTDLL_API int processStruct(WAVE_INFO *pIn, WAVE_INFO *pOut);

The wave_data in my c# struct has to be byte array (byte[])****, not char[] or string. How should I defind the struct and the method in c# where the dll is called? And the length of wave_date is fixed, let's say like 100.

like image 678
Yao Avatar asked Jan 04 '23 01:01

Yao


1 Answers

First of all, I would say that the C++ struct is declared incorrectly. The payload is binary data so the array should be unsigned char* rather than char*.

Leaving that aside, the struct is a little fiddly to marshal because of the array. It goes something like this:

[StructLayout(LayoutKind.Sequential)]
struct WAVE_INFO
{
    public int channel_num;
    public int audio_type;
    public IntPtr wave_data;
    public int wave_length;
}

We can't use byte[] in the struct to be marshalled. Instead we have to declare the array as IntPtr and handle the marshalling ourselves. The cleanest way is to declare byte[] arrays and pin them with GCHandle.

The imported function looks like this:

[DllImport(dllfilename, CallingConvention = CallingConvention.Cdecl)]
static extern int processStruct(ref WAVE_INFO infoIn, ref WAVE_INFO infoOut);

And the rather messy call to the function goes like this:

var dataIn = new byte[256];
// populate the input data array
var dataOut = new byte[256];

GCHandle dataInHandle = GCHandle.Alloc(dataIn, GCHandleType.Pinned);
try
{
    GCHandle dataOutHandle = GCHandle.Alloc(dataOut, GCHandleType.Pinned);
    try
    {
        WAVE_INFO infoIn;
        infoIn.audio_type = 1;
        infoIn.channel_num = 2;
        infoIn.wave_data = dataInHandle.AddrOfPinnedObject();
        infoIn.wave_length = dataIn.Length;

        WAVE_INFO infoOut = new WAVE_INFO();
        infoOut.wave_data = dataOutHandle.AddrOfPinnedObject();
        infoOut.wave_length = dataOut.Length;

        int retval = processStruct(ref infoIn, ref infoOut);
        // dataOut should have been populated by processStruct
    }
    finally
    {
        dataOutHandle.Free();
    }
}
finally
{
    dataInHandle.Free();
}

My assumption here is that the first parameter is used for input, and the second parameter for output. But that the onus is on the caller to allocate the wave data array for the output struct.

I've also assumed a calling convention, but you'd have to inspect the C++ macro STRUCTDLL_API to determine what the true calling convention is.

like image 105
David Heffernan Avatar answered Jan 15 '23 21:01

David Heffernan