C#pinvoke结构使用union和数组

我没有定义正确的C#代码来使用C ++库,它定义了一个带有union和数组的复杂结构,我在执行C ++代码时不断得到一些内存exception,我很确定它是由于这个原因。

c ++结构如下,忽略枚举和其他结构定义(如果需要,我会发布它们,但它们非常简洁)

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; 

而C#代码是这样的:

 [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; } 

代表和函数导入

 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); 

更新:asnwer之后的新代码,仍然无法正常工作:错误是:“尝试读取或写入受保护的内存这通常表示其他内存已损坏”

有点奇怪的是,我可以在第一个事件调用中使用结构,我打印所有字段并且它们似乎是正确的(lData和iData具有相同的值,cData具有我发送的字符串)但是在事件结束之后程序死亡。 好像我的C#代码破坏了结构或其他东西……

 [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 ++声明:

 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); 

注意爸爸DGCCTALK_PREFIX被定义为__declspec(dllexport)

那些C#代码:

 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#代码调用它们

 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#处理程序头

 private void _handler(ref DG_CCTALK_APP_EVT pEVT) 

正如您所发现的那样,封送者不喜欢您尝试在您的联合翻译中使用其他字段覆盖数组。 您已通过将数组的偏移量更改为4而不是0来抑制此问题。但这无济于事。

现在,由于编组人员不会为你处理联盟,你必须手动完成。 我会通过省略union的非数组成员来处理这个问题。

 [StructLayout(LayoutKind.Sequential)] public struct DG_CCTALK_APP_EVT { public DG_CCTALK_APP_EVT_CODE eEventCode; public DG_CCTALK_APP_EVT_TYPE eEventType; public int iTime; public int iHandle; public byte uchAddress; public DG_CCTALK_BILL_INFO sBillInfo; public int iBillAmount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] uData; public IntPtr _private; } 

请注意,我删除了您的声明的大部分冗余。 我怀疑该类型是由工具自动生成的。 但所有这些冗长都让人很难阅读。

这将为结构生成正确的布局,但是将为您提供访问联合中其他字段的工作。

那么,我们如何阅读这些领域呢? 好吧,数据包含在字节数组uData ,所以只需从那里读取值即可。 例如,您可以将以下属性添加到DG_CCTALK_APP_EVT结构:

 public int lData { get { return BitConverter.ToInt32(uData, 0); } set { uData = BitConverter.GetBytes(value); } } public int iData { get { return BitConverter.ToInt32(uData, 0); } set { uData = BitConverter.GetBytes(value); } } // etc. 

请注意,setter将删除数组的所有前4个字节,因为它会覆盖uData 。 我不太了解本机代码的协议,以确保这是你想要的。 如果它不是您想要的,那么您可以像这样编写setter:

 Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int)); 

您现在已经添加了互操作边界的C ++端。 最明显的问题是您的C#委托似乎有错误的调用约定。 它使用stdcall但C ++代码需要cdecl 。 这肯定足以解释回调返回后的灾难性崩溃。 您需要确保您的委托指定调用约定。

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT); 

您对cctalk_app_add_link翻译也是错误的。 它应该是:

 [DllImport("cctalk.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int cctalk_app_add_link(string szPortName); 

完成这样的操作就可以简单地传递一个字符串,从而避免当前实现所带来的内存泄漏。

检查你的int和long字段。 我认为C int是16位而C#int是32:

  • 如果这是真的,那么iTime,iHandle和iBilAmount在C#和C定义中都被定义为int。
  • 如果我错了 ,那么lData在C中被定义为long而在C#中被定义为int(如果这是问题,那么不要忘记修复lData之后出现的字段的偏移)。