im failing to define the correct C# code to work with a C++ library which defines a complex struct with union and arrays, i keep getting some memory exception while executing the C++ code, and im pretty sure its due to this.
The c++ struct is as follows, ignoring enums and other struct definition (i will post them if needed but they are pretty extense)
typedef struct
{
DG_CCTALK_APP_EVT_CODE eEventCode;
DG_CCTALK_APP_EVT_TYPE eEventType;
int iTime;
int iHandle;
unsigned char uchAddress;
DG_CCTALK_BILL_INFO sBillInfo;
int iBillAmount;
union
{
long lData;
int iData;
unsigned char ucArray[128];
char *cString;
void *pvoid;
} uData;
void *_private; // for use by cctalk app layer
} DG_CCTALK_APP_EVT, *PDG_CCTALK_APP_EVT;
And the C# code is this:
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
public struct Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40
{
//deberian llevar todos 0, pero el array genera problemas al ponerle 0, y cambiandolo explota en otro lado...
/// int
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public int lData;
/// int
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public int iData;
/// unsigned char[128]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 128, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I1)]
[System.Runtime.InteropServices.FieldOffsetAttribute(4)]
public byte[] ucArray;
/// char*
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public System.IntPtr cString;
/// void*
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public System.IntPtr pvoid;
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct DG_CCTALK_APP_EVT
{
/// DG_CCTALK_APP_EVT_CODE->_DG_CCTALK_APP_EVT_CODE
public DG_CCTALK_APP_EVT_CODE eEventCode;
/// DG_CCTALK_APP_EVT_TYPE->_DG_CCTALK_APP_EVT_TYPE
public DG_CCTALK_APP_EVT_TYPE eEventType;
/// int
public int iTime;
/// int
public int iHandle;
/// unsigned char
public byte uchAddress;
/// DG_CCTALK_BILL_INFO->_DG_CCTALK_BILL_INFO
public DG_CCTALK_BILL_INFO sBillInfo;
/// int
public int iBillAmount;
/// Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40
public Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40 uData;
/// void*
public System.IntPtr _private;
}
Delegates and a function import
public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);
[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_enable_device", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_enable_device(ref DG_CCTALK_APP_EVT param0);
UPDATE: new code after an asnwer, still not working: The error is: "Attempt to read or write Protected Memory This is often an indicating that other memory is corrupt"
What is weird, is that i can use the struct on the first event call, i print all fields and they seem to be right (lData and iData have the same values, cData has a string I sent) but after the event ends the program dies. Seems like my C# code is breaking the struct or something...
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public struct DG_CCTALK_APP_EVT
{
/// DG_CCTALK_APP_EVT_CODE->_DG_CCTALK_APP_EVT_CODE
public DG_CCTALK_APP_EVT_CODE eEventCode;
/// DG_CCTALK_APP_EVT_TYPE->_DG_CCTALK_APP_EVT_TYPE
public DG_CCTALK_APP_EVT_TYPE eEventType;
/// int
public int iTime;
/// int
public int iHandle;
/// unsigned char
public byte uchAddress;
/// DG_CCTALK_BILL_INFO->_DG_CCTALK_BILL_INFO
public DG_CCTALK_BILL_INFO sBillInfo;
/// int
public int iBillAmount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public byte[] uData;
#region setters y getters para mapeo
public int lData
{
set
{
Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));
}
get
{
return BitConverter.ToInt32(uData, 0);
}
}
public int iData
{
set
{
Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));
}
get
{
return BitConverter.ToInt32(uData, 0);
}
}
public byte[] ucArray
{
set
{
Array.Copy(value, uData, 128);
}
get
{
return uData;
}
}
public IntPtr cString
{
set
{
Array.Copy(BitConverter.GetBytes(value.ToInt32()), uData, 32);
}
get
{
return (IntPtr)BitConverter.ToInt32(uData, 0);
}
}
public IntPtr pvoid
{
set
{
Array.Copy(BitConverter.GetBytes(value.ToInt32()), uData, 32);
}
get
{
return (IntPtr)BitConverter.ToInt32(uData, 0);
}
}
#endregion
/// void*
public System.IntPtr _private;
}
C++ declarations:
typedef void (*DG_CCTALK_APP_EVT_HANDLER)(PDG_CCTALK_APP_EVT pEVT);
DGCCTALK_PREFIX DG_ERROR cctalk_app_init(DG_CCTALK_APP_EVT_HANDLER pfnHandler);
DGCCTALK_PREFIX DG_ERROR cctalk_app_add_link(char *szPortName);
Note dad DGCCTALK_PREFIX is defined as __declspec(dllexport)
C# code for those:
public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);
[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_init", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_init(DG_CCTALK_APP_EVT_HANDLER pfnHandler);
[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_add_link", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_add_link(System.IntPtr szPortName);
C# code invoking them
var handler = new DG_CCTALK_APP_EVT_HANDLER(this._handler);
var resultado = cctalk_app_init(handler);
cctalk_app_add_link(Marshal.StringToHGlobalAnsi("port=" + port));
C# handler header
private void _handler(ref DG_CCTALK_APP_EVT pEVT)
Check your int and long fields. I think that C int is 16 bit while C# int is 32:
As you have discovered, the marshaler does not like you trying to overlay the array with other fields in your translation of the union. You've suppressed the problem by changing the offset for the array to be 4 rather than 0. But that doesn't help.
Now, since the marshaler won't deal with the union for you, you'll have to do it manually. I would handle this by omitting the non-array members of the union.
Note that I have removed much of the verbosity of your declaration. I suspect that the type was automatically generated by a tool. But all that verbosity makes it very hard to read.
This will produce the correct layout for the struct, but will leave you with work to do to access the other fields in the union.
So, how can we read those fields? Well, the data is contained in the byte array
uData
, so it is just a matter of reading the values from there. For instance, you could add the following property to theDG_CCTALK_APP_EVT
struct:Note that the setter will obliterate all the but first 4 bytes of the array, since it overwrites
uData
. I don't know enough about the protocol of the native code to be sure that this is what you want. If it's not what you want, then you might write the setter like this:You've now added the C++ side of the interop boundary. The most obvious problem is that your C# delegate appears to have the wrong calling convention. It uses
stdcall
but the C++ code expectscdecl
. That's certainly enough to explain a catastrophic crash after your callback returns. You'll need to make sure that your delegate specifies the calling convention.Your translation of
cctalk_app_add_link
is wrong too. It should be:Done like this you can simply pass a string and thus avoid the memory leak that your current implementation has.