1、最近做项目遇到,C#调用C++dll里的函数需要传递结构体参数,发现这个并不是简单的在C#里定义相应的结构体就可以的,下面以一个例子来说明解决的办法,C++中的函数和结构体如下:
uint msec_set_igr_gen_cfg(int port, IGR_GEN_T *igr_gen)
{
return 0;
}
typedef struct {
int aa_disable; /*/< authentiation adjust checking disable */
int badtag_rej; /*/< reject packet if it is bypassed due to badtag */
int pad_en; /*/< pad non-rejected packets up to 64B */
int byp_ctl_sl; /*/< bypass packet if SL field does not correspond to packet len */
int byp_ctl_v; /*/< bypass packet if V bit is set */
int byp_ctl_sc; /*/< bypass packet if SC bit and either ES or SCB bits are set */
int byp_ctl_ec; /*/< bypass packet if DC bits are not 00 or 11 */
int sectag_flag; /*/< select which flag bit indicates that a SEC tag was present in pkt */
} IGR_GEN_T;
在C#中 首先需要使用Dllimport将相应的C++dll load进来,然后定义相应的结构体,具体如下:
[DllImport("..\\debug\\mgd_MacSec.dll")]
private static extern UInt32 msec_set_igr_gen_cfg(int port, IntPtr igr_gen);
结构体定义:
[StructLayout(LayoutKind.Sequential)]
public class IGR_GEN_T
{
int aa_disable; /*/< authentiation adjust checking disable */
int badtag_rej; /*/< reject packet if it is bypassed due to badtag */
int pad_en; /*/< pad non-rejected packets up to 64B */
int byp_ctl_sl; /*/< bypass packet if SL field does not correspond to packet len */
int byp_ctl_v; /*/< bypass packet if V bit is set */
int byp_ctl_sc; /*/< bypass packet if SC bit and either ES or SCB bits are set */
int byp_ctl_ec; /*/< bypass packet if DC bits are not 00 or 11 */
int sectag_flag; /*/< select which flag bit indicates that a SEC tag was present in pkt */
public IGR_GEN_T()
{
aa_disable = 0;
badtag_rej = 0;
pad_en = 0;
byp_ctl_ec = 0;
byp_ctl_sc = 0;
byp_ctl_sl = 0;
byp_ctl_v = 0;
sectag_flag = 0;
}
} ;
在代码中具体引用函数时如下所示,
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(igr_gen));
Marshal.StructureToPtr(igr_gen, ptr, false);
UInt32 ret = _msec_set_igr_gen_cfg(port, ptr);
igr_gen = (IGR_GEN_T)Marshal.PtrToStructure(ptr, typeof(IGR_GEN_T));
Marshal.FreeHGlobal(ptr);
return ret;
从以上步骤可以看出,结构体参数的传递需要marshal做辅助做相应的转化,以intptr的方式传输结构体参数。
2、还存在另外一种情况,是结构体中嵌套有结构体时需要做一些特殊处理,具体如下:
结构体
typedef struct {
int ctx_num; /*/< Index to the context to use */
int sec_level; /* < Security Level to validate frames per context based */
int drop_maxpn; /* < Enable packet drop when max pn is reached for this context>*/
int drop; /*/< Drop this Packet */
int redir; /*/< For Egress, redirect the packet to ingress path (NDL). For Ingress, redirect the packet to alternate destination. */
int auth_en; /*/< Encapsulate and authenticate this packet. */
int enc_en; /*/< Encrypt this packet. auth_en must also be set when this bit is set. (Valid only for egress path). */
} ACT_FLD;
typedef struct {
ACT_FLD *lk_act_fld; /*/< Action to take for an entry within a port */
} LKUP_T;
C++ 函数
uint msec_port_set_egr_entry (IN int port, IN int ent_num, IN LKUP_T *egr_lkup)
{
//
}
C#在调用时首先将相应dll import进来,进行相应结构体的定义和相应函数的声明,具体如下:
[DllImport("..\\debug\\mgd_MacSec.dll")]
private static extern UInt32 msec_set_igr_gen_cfg(int port, IntPtr igr_gen);
[StructLayout(LayoutKind.Sequential)]
public class ACT_FLD
{
public int ctx_num; /*/< Index to the context to use */
public int sec_level; /* < Security Level to validate frames per context based */
public int drop_maxpn; /* < Enable packet drop when max pn is reached for this context>*/
public int drop; /*/< Drop this Packet */
public int redir; /*/< For Egress, redirect the packet to ingress path (NDL). For Ingress, redirect the packet to alternate destination. */
public int auth_en; /*/< Encapsulate and authenticate this packet. */
public int enc_en; /*/< Encrypt this packet. auth_en must also be set when this bit is set. (Valid only for egress path). */
public ACT_FLD()
{
ctx_num = 0;
sec_level = 0;
drop_maxpn = 0;
drop = 0;
redir = 0;
auth_en = 0;
enc_en = 0;
}
}
[StructLayout(LayoutKind.Sequential)]
public class LKUP_T
{
public IntPtr lk_act_fld;
public LKUP_T()
{
ACT_FLD lk_act_fld_s = new ACT_FLD();
lk_act_fld = Marshal.AllocHGlobal(Marshal.SizeOf(lk_act_fld_s));
Marshal.StructureToPtr(lk_act_fld_s, lk_act_fld, false);
}
}
具体在代码中引用时如下所示:
IntPtr egr_lkup_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(egr_lkup));
Marshal.StructureToPtr(egr_lkup, egr_lkup_ptr, false);
uint ret = _msec_port_set_egr_entry(port, ent_num, egr_lkup_ptr);
egr_lkup = (LKUP_T)Marshal.PtrToStructure(egr_lkup_ptr, typeof(LKUP_T));
Marshal.FreeHGlobal(egr_lkup_ptr);
另外枚举(enum)参数传递时类似于int型,只需在C#里定义相应的枚举提即可,不需做相应转化,在此不再给出具体方法。
另,C++ dll要想被C#所使用,需要进行设置,支持通用语言,具体如下图所示:(Common Language Runtime support选项)
关于跨语言之间的结构体参数传输问题,不知是否还有其他比较简洁的办法,欢迎大家讨论,小弟初学C#和.NET,还请大家多多指教。
文献来源:http://blog.csdn.net/sundk911/article/details/7766954
C#调用c++dll文件是一件很麻烦的事情,首先面临的是数据类型转换的问题,相信经常做c#开发的都和我一样把学校的那点c++底子都忘光了吧(语言特性类)。
网上有一大堆得转换对应表,也有一大堆的转换实例,但是都没有强调一个更重要的问题,就是c#数据类型和c++数据类型占内存长度的对应关系。
如果dll文件中只包含一些基础类型,那这个问题可能可以被忽略,但是如果是组合类型(这个叫法也许不妥),如结构体、类类型等,在其中的成员变量的长度的申明正确与否将决定你对dll文件调用的成败。
如有以下代码,其实不是dll文件的源码,而是厂商给的c++例子代码
c++中的结构体申明
typedef struct
{
unsigned char Port;
unsigned long Id;
unsigned char Ctrl;
unsigned char pData[8];
}HSCAN_MSG;
c++中的函数申明(一个c++程序引用另一个c++的dll文件)
extern "C" int _stdcall HSCAN_SendCANMessage(unsigned char nDevice,unsigned char nPort,HSCAN_MSG *msg,int nLength);
c++中的调用:
....
HSCAN_MSG msg[100];
.....
HSCAN_SendCANMessage(m_nDevice,m_nPort,msg,nFrames);
由上述代码可见,msg是个结构体的数组。
下面是我的c#的代码
c#结构体申明:(申明成)
[StructLayout(LayoutKind.Sequential)]
public struct HSCAN_MSG
{
// UnmanagedType.ByValArray, [MarshalAs(UnmanagedType.U1)]这个非常重要,就是申明对应类型和长度的
[MarshalAs(UnmanagedType.U1)]
public byte Port;
[MarshalAs(UnmanagedType.U4)]
public uint nId;
[MarshalAs(UnmanagedType.U1)]
public byte nCtrl;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] pData;
};
c#函数申明
[DllImport("HS2106API.dll")]
public static extern int HSCAN_SendCANMessage(
byte nDevice, byte nPort, HSCAN_MSG[] pMsg, int nLength);
C#函数调用
HSCAN_MSG[] msg = new HSCAN_MSG[1]; //发送缓冲区大小可根据需要设置;
for (int yy = 0; yy < msg.Length; yy++)
{
msg[yy] = new HSCAN_MSG();
}
//...结构体中的成员的实例化略
HSCAN_SendCANMessage(0x0, 0x0, msg, 1)
那些只能用指针不能用结构体和类的地方
c++中的结构体申明
typedef struct
{
unsigned char Port;
unsigned long Id;
unsigned char Ctrl;
unsigned char pData[8];
}HSCAN_MSG;
c++中的函数申明(一个c++程序引用另一个c++的dll文件)
extern "C" int _stdcall HSCAN_SendCANMessage(unsigned char nDevice,unsigned char nPort,HSCAN_MSG *msg,int nLength);
c#中的结构体申明:
[StructLayout(LayoutKind.Sequential)]
public struct HSCAN_MSG
{
[MarshalAs(UnmanagedType.U1)]
public byte Port;
/// <summary>
/// 节点标识,nEFF=1 时(扩展帧),为29 位nEFF=0(标准帧)时,为11 位;
/// </summary>
[MarshalAs(UnmanagedType.U4)]
public uint nId;
[MarshalAs(UnmanagedType.U1)]
public byte nCtrl;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] pData;
};
c#函数的调用:包含使用指针IntPtr替代结构体数组和读取IntPtr的方法
HSCAN_MSG[] msg1 = new HSCAN_MSG[10];
for (int i = 0; i < msg1.Length; i++)
{
msg1[i] = new HSCAN_MSG();
msg1[i].pData = new byte[8];
}
IntPtr[] ptArray = new IntPtr[1];
ptArray[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HSCAN_MSG)) * 10);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HSCAN_MSG)));
Marshal.Copy(ptArray, 0, pt, 1);
int count = HSCAN_ReadCANMessage(0x0, 0,pt, 10);
textBoxStatus.Text += "\r\n" + "读取0口:" + count.ToString() + "帧数据";
for (int j = 0; j < 10; j++)
{
msg1[j] =
(HSCAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)pt+ j * Marshal.SizeOf(typeof(HSCAN_MSG)))
, typeof(HSCAN_MSG));
textBoxStatus.Text += "\r\n收到0口" + Convert.ToByte(msg1[j].pData[0]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[1]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[2]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[3]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[4]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[5]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[6]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[7]).ToString();
}
文献来源:http://topic.csdn.net/u/20090716/10/1388ad60-4968-400f-ab65-8a1fffe72e16.html?1983796426
##########################################################
C 的原型结构体
typedef struct _wfs_result
{
REQUESTID RequestID;
HSERVICE hService;
SYSTEMTIME tsTimestamp;//这个对应下面的SYSTEMTIME 结构
HRESULT hResult;
union {
DWORD dwCommandCode;
DWORD dwEventID;
} u;
LPVOID lpBuffer; //返回的指针,指向WFSIDCSTATUS 这个结构
} WFSRESULT, * LPWFSRESULT;
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
typedef struct _wfs_idc_status
{
WORD fwDevice;
WORD fwMedia;
WORD fwRetainBin;
WORD fwSecurity;
USHORT usCards;
WORD fwChipPower;
LPSTR lpszExtra;
} WFSIDCSTATUS, * LPWFSIDCSTATUS;
C#对应定义的结构体:
[StructLayout(LayoutKind.Sequential)]
public unsafe struct _wfs_result
{
public UInt32 RequestID;
public ushort hService;
public _SYSTEMTIME tsTimestamp;//对应下面的 _SYSTEMTIME
public int hResult;
public AnonymousStruct U;//对应下面的 AnonymousStruct 结构
public IntPtr lpBuffer;//返回的指针,指向_wfs_idc_status 这个结构
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct _SYSTEMTIME
{
public UInt16 wYear;
public UInt16 wMonth;
public UInt16 wDayOfWeek;
public UInt16 wDay;
public UInt16 wHour;
public UInt16 wMinute;
public UInt16 wSecond;
public UInt16 wMilliseconds;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct AnonymousStruct
{
[FieldOffset(0)]
public UInt32 dwCommandCode;
[FieldOffset(0)]
public UInt32 dwEventID;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct _wfs_idc_status
{
public UInt16 fwDevice;
public UInt16 fwMedia;
public UInt16 fwRetainBin;
public UInt16 fwSecurity;
public ushort usCards;
public UInt16 fwChipPower;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string lpszExtra;
}
通过Windows消息机制处理,我从WinForm的Message.LParam中获得了结构体(_wfs_result)的指针并将其转化为了_wfs_result的一个变量,通过这个变量可以正常访问其中定义的字段,唯有:“public IntPtr lpBuffer; //指向结构体(_wfs_idc_status)的指针”使用失败,该如何定义和操作结构体才能正确使用该指针呢?
补充说明:我用WinForm窗体来接收C++编写的DLL发送过来的消息,DLL导出函数中将WinForm窗体句柄传给导出函数,其会将消息传送给WinForm。经测试证明:DLL传过来的消息Message.LParam指针可以转化为对应的结构体,就是该结构体内的指向另结构体的指针出错,不能转化为对于的结构体,但值有的。为什么呢?该如何解决这类问题?
产生错误的代码: Status = (STATUS)Marshal.PtrToStructure(Result.lpBuffer, typeof(STATUS)); 其中Status为STATUS类型的结构体,Result为RESULT类型的结构体变量。
错误提示: 检测到FatalExecutionEngineError 运行时遇到了错误。此错误的地址为 0x661ffc37,在线程 0xe0c 上。错误代码为 0xc0000005。此错误可能是 CLR 中的 bug,或者是用户代码的不安全部分或不可验证部分中的 bug。此 bug 的常见来源包括用户对 COM-interop 或 PInvoke 的封送处理错误,这些错误可能会损坏堆栈。
谢谢你 “sdl2005lyx” 我的C#调用C 动态库DLL 是第三方的是一个准标的,不能被修改。
这个问题我通过5天的攻关,终于解决了。关键是 C# 中 public struct WFSRESULT
这个结构的定义要改变一下:
将原来的
[StructLayout(LayoutKind.Sequential)]
public struct WFSRESULT
{
改为:(关键)
[StructLayout(LayoutKind.Explicit)]
public unsafe struct _wfs_result
{
这种方式的定义就可以了。
文献来源:http://topic.csdn.net/u/20110714/12/5affa2f1-3c47-4817-a73d-7cf29ec75605.html