c#程序调用c++编写dll需要注意问题

   http://blog.csdn.net/richerg85/article/details/7492195
        分类:            c#学习相关 2392人阅读 评论(0) 收藏 举报

     现在在写c#调用c++dll的例子,dll中某一个函数需要一个结构体地址作为参数传递。

     但是在传递结构体的时候,程序一直返回错误,估计原因在c#写的结构体和c++中的结构体之间有些不一致。

    下面以例子说明-----c#程序在调用c++dll的时候需要注意问题。

(1) c++和c#中对应的数据结构大小一致

    简单的c++dll程序如下:

  1. // mydll.cpp : Defines the entry point for the DLL application. 
  2. // 
  3.  
  4. #include "stdafx.h" 
  5.  
  6. #define DLLEXPORT extern "C" __declspec(dllexport) 
  7. BOOL APIENTRY DllMain( HANDLE hModule,  
  8.                        DWORD  ul_reason_for_call,  
  9.                        LPVOID lpReserved 
  10.                      ) 
  11.     return TRUE; 
  12.  
  13. typedef struct _wfsversion  
  14.     WORD    wVersion; 
  15.     WORD    wLowVersion; 
  16.     WORD    wHighVersion; 
  17.     CHAR    szDescription[257]; 
  18.         CHAR    szSystemStatus[257];  
  19. }VERSION, *LPVERSION; 
  20.  
  21.  
  22. DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion) 
  23.     lpVersion->wHighVersion = 0x20; 
  24.     strcpy(lpVersion->szDescription, "test struct copy"); 
  25.     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中结构体定义为:

  1. typedef struct _wfsversion  
  2.     WORD    wVersion; 
  3.     WORD    wLowVersion; 
  4.     WORD    wHighVersion; 
  5.     CHAR    szDescription[257]; 
  6.         CHAR    szSystemStatus[257];  
  7. }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#中结构体定义:

  1. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
  2.     public struct WFSVERSION 
  3.     { 
  4.         public short wVersion; 
  5.         public short wLowVersion; 
  6.         public short wHighVersion; 
  7.         [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 257)] 
  8.         public string szDescription; 
  9.         [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 257)] 
  10.         public string szSystemStatus; 
  11.     } 
[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,使用代码如下:

  1. [DllImport("mydll.dll", CharSet = CharSet.Ansi)] 
  2. public static extern int testVersion(int dwVersion, ref WFSVERSION outInfo); 
  3. [DllImport("mydll.dll",CharSet = CharSet.Ansi)] 
  4. 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则需要被调用的方法修改调用者的引用的地方。
通俗的说:ref在函数中可进可出,out的只能出。
言归正传,当函数参数需要引用时,在c#中使用ref传递是没错的。
c++dll中函数:
  1. DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion) 
    DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion)
c#调用:
  1. [DllImport("mydll.dll", CharSet = CharSet.Ansi)] 
  2. 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);

<2> 使用IntPtr结构
c#中,IntPtr结构用于表示指针或者句柄的平台特定类型。
在调用的API函数中,一定有类似窗口句柄的参数,可以IntPtr声明。
     
      例子如下,c++dll函数:
  1. DLLEXPORT int WINAPI testPoint(LPVERSION &lpHandle) 
  2.     strcpy(lpHandle->szDescription,"test copy again"); 
  3.     lpHandle->wVersion = 0x30; 
  4.     return 0; 
DLLEXPORT int WINAPI testPoint(LPVERSION &lpHandle)
{
	strcpy(lpHandle->szDescription,"test copy again");
	lpHandle->wVersion = 0x30;
	return 0;
}
   c#中可以采用IntPtr调用:
  
  1. [DllImport("mydll.dll",CharSet = CharSet.Ansi)] 
  2. public static extern int testPoint(IntPtr outHandle); 
[DllImport("mydll.dll",CharSet = CharSet.Ansi)]
public static extern int testPoint(IntPtr outHandle);
调用函数:
  1. private void button1_Click(object sender, EventArgs e) 
  2.        { 
  3.            IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(WFSVERSION)); 
  4.            try 
  5.            { 
  6.                int ret = testVersion(0x30303, ref tempVersion);  
  7.                int ret1 = testPoint(pnt); 
  8.                IntPtr ptr = Marshal.ReadIntPtr(pnt,0);//,Marshal.SizeOf(IntPtr)); 
  9.                WFSVERSION version = (WFSVERSION)Marshal.PtrToStructure(ptr, typeof(WFSVERSION)); 
  10.            } 
  11.            finally 
  12.            { 
  13.                Marshal.FreeHGlobal(pnt); 
  14.            } 
  15.        } 
 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++中函数声明:    

  1. HRESULT extern WINAPI WFSCreateAppHandle ( LPHAPP lphApp); 
HRESULT extern WINAPI WFSCreateAppHandle ( LPHAPP lphApp);

  1. 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#引用:

  1. [DllImport("msxfs.dll", CharSet = CharSet.Ansi)] 
  2. public static extern int WFSCreateAppHandle([Out] Byte[] b); 
  3. [DllImport("msxfs.dll", CharSet = CharSet.Ansi)] 
  4. public static extern int WFSAsyncOpen(string sLogicalName, int hApp, string sAppID, int lTraceLevel, int lTimeOut, ref int iService, IntPtr hWnd, 
  5.                                       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);
     程序测试函数:

  1. //byte类型表示 
  2. byte[] b = new byte[10]; 
  3. int ret1 = WFSCreateAppHandle(b); 
  4. int b1 = b[0]; 
  5. 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#程序调用:

  1. IntPtr pnt = IntPtr.Zero; 
  2.              
  3.             try 
  4.             { 
  5.                 pnt = Marshal.AllocHGlobal(Marshal.SizeOf(pnt)); 
  6.                 IntPtr[] newArray = new IntPtr[10];  
  7.                 int ret = WFSStartUp(0x30303, ref tempVersion); 
  8.  
  9.                 int ret1 = WFSCreateAppHandle(pnt); 
  10.                 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);
  1.     WFSVERSION version = new WFSVERSION(); 
  2.     int ret2 = WFSAsyncOpen("IDC30", ptr, "test", m_dwTraceLevel, 2000, ref m_service, this.Handle, 0x00000303, ref tempVersion, ref version, ref m_requset); 
  3. finally 
  4.     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#中函数可以类似下边调用:

      

  1. for(int   i   =   0;   i   <   plStreamCount;   i++)  
  2. {  
  3.    IntPtr ptr   =   Marshal.ReadIntPtr(struct,   i);  
  4.    testsize[i]   =   (STREAM_STATE_ITEM)Marshal.PtrToStructure(ptr,typeof(STRUCT ));      
  5. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值