现在在写c#调用c++dll的例子,dll中某一个函数需要一个结构体地址作为参数传递。
但是在传递结构体的时候,程序一直返回错误,估计原因在c#写的结构体和c++中的结构体之间有些不一致。
下面以例子说明-----c#程序在调用c++dll的时候需要注意问题。
(1) c++和c#中对应的数据结构大小一致
简单的c++dll程序如下:
// mydll.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" #define DLLEXPORT extern "C" __declspec(dllexport) BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } typedef struct _wfsversion { WORD wVersion; WORD wLowVersion; WORD wHighVersion; CHAR szDescription[257]; CHAR szSystemStatus[257]; }VERSION, *LPVERSION; DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion) { lpVersion->wHighVersion = 0x20; strcpy(lpVersion->szDescription, "test struct copy"); return 0; }
c++dll中结构体定义为:
typedef struct _wfsversion { WORD wVersion; WORD wLowVersion; WORD wHighVersion; CHAR szDescription[257]; CHAR szSystemStatus[257]; }VERSION, *LPVERSION;
可以用c++关键字sizeof计算出此数据结构的字节大小:sizeof(VERSION)=520。
这520个字节的组成:3*(sizeof(WORD))+2*257*(sizeof(CHAR)) = 3*2 +2*257*1 = 520。可见c++中WORD占2个字节,CHAR占1个字节。
c#中结构体定义:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct WFSVERSION { public short wVersion; public short wLowVersion; public short wHighVersion; [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 257)] public string szDescription; [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 257)] public string szSystemStatus; }
这里特别说明的是,c++中结构体所占字节数一定要和c#相应结构体所占字节数相等。
c#结构体中的short(Int16)<-------->c++中的WORD(unsigned short),都是占2个字节。
c#中调用c++dll,使用代码如下:
[DllImport("mydll.dll", CharSet = CharSet.Ansi)] public static extern int testVersion(int dwVersion, ref WFSVERSION outInfo); [DllImport("mydll.dll",CharSet = CharSet.Ansi)] public static extern int testPoint(IntPtr outHandle);
这里特别说明的是,一定要确保c#引用函数的参数类型和c++函数中的参数类型一致。在测试工程中,由于一个参数在c#应该为int类型写成short,导致找了一天不能调用成功!
(2)c#向c++dll中函数传递数据结构及对象指针
<1>使用c# ref或者out
ref关键字使参数按照引用传递,如上文中的testVersion函数中的第二个参数传值。当使用ref关键字修饰的参数被函数(方法)调用,在函数(方法)中对参数的任何修改都反映在该参数中,这类似于c++中的引用或者指针传参。如果要使用ref参数,则必须显示使用ref关键字。
out关键字同样也会通过引用传递,此关键字也必须显示调用。当希望方法返回多个值时,可以声明使用out 参数,加有out关键字的参数可以将便利用作返回类型。
ref和out的不同之处:
- 使用ref型参数时,传入的参数必须先被初始化;out则必须在方法中对其初始化。
- out适合需要返回多个返回值的地方,而ref则需要被调用的方法修改调用者的引用的地方。
DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion)
c#调用:
[DllImport("mydll.dll", CharSet = CharSet.Ansi)] public static extern int testVersion(int dwVersion, ref WFSVERSION outInfo);
c#中,IntPtr结构用于表示指针或者句柄的平台特定类型。
DLLEXPORT int WINAPI testPoint(LPVERSION &lpHandle) { strcpy(lpHandle->szDescription,"test copy again"); lpHandle->wVersion = 0x30; return 0; }
c#中可以采用IntPtr调用:
[DllImport("mydll.dll",CharSet = CharSet.Ansi)] public static extern int testPoint(IntPtr outHandle);
调用函数:
private void button1_Click(object sender, EventArgs e) { IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(WFSVERSION)); try { int ret = testVersion(0x30303, ref tempVersion); int ret1 = testPoint(pnt); IntPtr ptr = Marshal.ReadIntPtr(pnt,0);//,Marshal.SizeOf(IntPtr)); WFSVERSION version = (WFSVERSION)Marshal.PtrToStructure(ptr, typeof(WFSVERSION)); } finally { Marshal.FreeHGlobal(pnt); } }
(3)c#调用c++dll中函数传递指针的指针
目前我知道可以使用两种方法实现(这个传递指针的指针的参数不是结构体)。
<1> 使用Byte[] 传递
c++中函数声明:
HRESULT extern WINAPI WFSCreateAppHandle ( LPHAPP lphApp);
HRESULT extern WINAPI WFSAsyncOpen ( LPSTR lpszLogicalName, HAPP hApp, LPSTR lpszAppID, DWORD dwTraceLevel, DWORD dwTimeOut, LPHSERVICE lphService, HWND hWnd, DWORD dwSrvcVersionsRequired, LPWFSVERSION lpSrvcVersion, LPWFSVERSION lpSPIVersion, LPREQUESTID lpRequestID);
这里关注HAPP这个类型,在c++中为void *类型。那么LPHAPP类型为void **类型。
在我的测试程序中,c#引用:
[DllImport("msxfs.dll", CharSet = CharSet.Ansi)] public static extern int WFSCreateAppHandle([Out] Byte[] b); [DllImport("msxfs.dll", CharSet = CharSet.Ansi)] public static extern int WFSAsyncOpen(string sLogicalName, int hApp, string sAppID, int lTraceLevel, int lTimeOut, ref int iService, IntPtr hWnd, int iSrvVersionRequired, ref WFSVERSION srvcVersion, ref WFSVERSION SpiVersion, ref int iRequestID);
程序测试函数:
//byte类型表示 byte[] b = new byte[10]; int ret1 = WFSCreateAppHandle(b); int b1 = b[0]; int ret2 = WFSAsyncOpen("IDC30", b1, "test", m_dwTraceLevel, 2000, ref m_service, this.Handle, 0x00000303, ref tempVersion, ref version, ref m_requset);
<2> 使用IntPtr实现
c#程序调用:
IntPtr pnt = IntPtr.Zero; try { pnt = Marshal.AllocHGlobal(Marshal.SizeOf(pnt)); IntPtr[] newArray = new IntPtr[10]; int ret = WFSStartUp(0x30303, ref tempVersion); int ret1 = WFSCreateAppHandle(pnt); IntPtr ptr = Marshal.ReadIntPtr(pnt, 0);
WFSVERSION version = new WFSVERSION(); int ret2 = WFSAsyncOpen("IDC30", ptr, "test", m_dwTraceLevel, 2000, ref m_service, this.Handle, 0x00000303, ref tempVersion, ref version, ref m_requset); } finally { Marshal.FreeHGlobal(pnt); }
最后,谈一下结构体(指针、指针的指针)采用IntPtr实现调用。
例如c++dll中有结构体指针或者结构体指针的指针函数调用,如int XXX(STRUCT **struct);
c#可以 [DllImport("xxx.dll",ChaSet=CharSet.Ansi)] int XXX(IntPtr struct);
c#中函数可以类似下边调用:
for(int i = 0; i < plStreamCount; i++) { IntPtr ptr = Marshal.ReadIntPtr(struct, i); testsize[i] = (STREAM_STATE_ITEM)Marshal.PtrToStructure(ptr,typeof(STRUCT )); }