现在在写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;
- }
// 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;
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;
- }
[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);
[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)
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);
[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;
- }
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);
[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);
- }
- }
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 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);
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);
[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);
//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);
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);
- }
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 ));
- }