salocinx
salocinx

Reputation: 3823

How to pass struct from C# to C++ DLL?

Ok my series of problems with a laser device controller goes on... I want to call the following C++ function within a DLL from my C# code:

extern "C" _declspec(dllimport) int SendFrame(DWORD deviceIndex, byte* pData, DWORD numOfPoints, DWORD scanrate);

The pointer pData points to an array of laser points that are defined in the C++ header file as follows:

#pragma pack (1)
struct LaserPoint {
    WORD x;
    WORD y;
    byte colors[6];
};

On the C# side I defined the function import as follows:

[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SendFrame(UInt32 deviceIndex, ref byte[] pData, UInt32 numOfPoints, UInt32 scanrate);

... and the LaserPoint struct like this:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct LaserPoint {
    public UInt16 x;
    public UInt16 y;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] colors;
}

Then I call the SendFrame function like that:

LaserPoint point = new LaserPoint();
point.x = 16384;
point.y = 32768;
point.colors = new byte[] {255, 0, 0, 0, 0, 0};

byte[] arr = point2array(points);
SendFrame(0, ref arr, 1, 30000);

This is my function to convert the LaserPoint struct instance to a byte array:

private static byte[] point2array(object obj) {
    int len = Marshal.SizeOf(obj);
    byte[] arr = new byte[len];
    IntPtr ptr = Marshal.AllocHGlobal(len);
    Marshal.StructureToPtr(obj, ptr, true);
    Marshal.Copy(ptr, arr, 0, len);
    Marshal.FreeHGlobal(ptr);
    return arr;
}

But my laser device does not receive correct input, since the laser behaves very weird. Using the same code within the C++ project works fine. So the bug is somewhere with this C# interoperability code.

Any ideas?

Upvotes: 2

Views: 3004

Answers (2)

Richard Matheson
Richard Matheson

Reputation: 1175

If you only intend to use LaserPoint structs here you can define the function as

[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SendFrame(UInt32 deviceIndex, LaserPoint[] pData, UInt32 numOfPoints, UInt32 scanrate);

And get rid of the copying and let the runtime do that for you.

Otherwise as ildjarn says, getting rid of ref should work, as byte arrays are already reference (pointer) types.

For examples of how to do this stuff, the PInvoke wiki has got structs and signatures for most of the windows API, so while your call won't be there, there are lots of similar examples.

Upvotes: 1

ildjarn
ildjarn

Reputation: 62995

Arrays are already reference types, so ref byte[] is a double indirection, akin to byte** — lose the ref.

Upvotes: 3

Related Questions