Search code examples
c#c++cpinvokedllimport

C# Marshaling an ushort/ulong array


I have a C-DLL + header file and try to p/invoke a function from C#. I also have some example C++ code of how to use the function. Here is the function definition:

int GetData(unsigned char* buffer, long bufferSize);

The more interesting part is the example code and how the function can be called:

if(dataSize == 16)
{
  unsigned short* usData = new unsigned short[m_numX * m_numY * 3 / 2];
  GetData( (unsigned char*)usData, m_numX * m_numY * sizeof(unsigned short)  );
} 
else if (dataSize == 32) 
{
  unsigned long* ulData = new unsigned long[m_numX * m_numY];
  GetData( (unsigned char*)ulData, m_numX * m_numY * sizeof(unsigned long)  );
}

So, depending on the dataSize variable, the actual data array can be an ushort or an ulong. However, it is always passed as an unsigned char pointer. For the sake of simpleness I just tried to get at least one of the variants to work. Here's the code I tried for dataSize = 16

[DllImport("External.dll", EntryPoint = "GetData", CharSet = CharSet.Unicode, CallingConvention =      CallingConvention.Cdecl)]
public static extern int GetData(ref ushort[] pBuffer, long lBufferSize);

long bufferSize1 = numX * numY * 3 / 2;
long bufferSize2 = numX * numY * sizeof(ushort);
ushort[] data = new ushort[bufferSize1];

GetData(ref data, bufferSize2)

If I run above code, the application quits with an 'Access Violation' Exception. That usually means, that the unsafe code tried to write over the buffer limits or that the p/invoke declaration has an error. I tried huge buffers (which would be able to hold any kind of data I'm expecting) but my guess would be, that my mistake is in the declaration.

I also tried to declare the buffer as byte[] (since the example casts it as unsigned char*) and ulong in the p/invoke declaration. same for the actual buffer I pass as reference. the error remains the same.

How can I make this work?


Solution

  • A couple of mistakes:

    1. The array must not be passed as ref. That is because ref ushort[] matches unsigned short**.
    2. C++ long does not match C# long on Windows. The former is 32 bits, the latter 64 bits.

    You need to import like this:

    [DllImport("External.dll", CallingConvention =      CallingConvention.Cdecl)]
    public static extern int GetData(
        [Out] ushort[] pBuffer, 
        int lBufferSize
    );
    

    It would be perfectly reasonable, for convenience, to use an overload for the 32 bit data variant:

    [DllImport("External.dll", CallingConvention =      CallingConvention.Cdecl)]
    public static extern int GetData(
        [Out] uint[] pBuffer, 
        int lBufferSize
    );
    

    Likewise, and overload for an array of byte would also be valid should you need it.