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

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

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

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

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

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

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

[cpp] view plain copy print ?
  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; 

c++dll中结构体定义为:

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

[csharp] view plain copy print ?
  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.     } 
这里特别说明的是,c++中结构体所占字节数一定要和c#相应结构体所占字节数相等。

c#结构体中的short(Int16)<-------->c++中的WORD(unsigned short),都是占2个字节。

c#中调用c++dll,使用代码如下:

[csharp] view plain copy print ?
  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); 

这里特别说明的是,一定要确保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中函数:
[cpp] view plain copy print ?
  1. DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion) 
c#调用:
[csharp] view plain copy print ?
  1. [DllImport("mydll.dll", CharSet = CharSet.Ansi)] 
  2. public static extern int testVersion(int dwVersion, ref WFSVERSION outInfo); 

<2> 使用IntPtr结构
c#中,IntPtr结构用于表示指针或者句柄的平台特定类型。
在调用的API函数中,一定有类似窗口句柄的参数,可以IntPtr声明。
     
      例子如下,c++dll函数:
[cpp] view plain copy print ?
  1. DLLEXPORT int WINAPI testPoint(LPVERSION &lpHandle) 
  2.     strcpy(lpHandle->szDescription,"test copy again"); 
  3.     lpHandle->wVersion = 0x30; 
  4.     return 0; 
   c#中可以采用IntPtr调用:
  
[csharp] view plain copy print ?
  1. [DllImport("mydll.dll",CharSet = CharSet.Ansi)] 
  2. public static extern int testPoint(IntPtr outHandle); 
调用函数:
[csharp] view plain copy print ?
  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.        } 

(3)c#调用c++dll中函数传递指针的指针

      目前我知道可以使用两种方法实现(这个传递指针的指针的参数不是结构体)。

      <1> 使用Byte[] 传递

       c++中函数声明:    

[cpp] view plain copy print ?
  1. HRESULT extern WINAPI WFSCreateAppHandle ( LPHAPP lphApp); 

[cpp] view plain copy print ?
  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); 
      这里关注HAPP这个类型,在c++中为void *类型。那么LPHAPP类型为void **类型。

     在我的测试程序中,c#引用:

[csharp] view plain copy print ?
  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); 
     程序测试函数:

[csharp] view plain copy print ?
  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); 
    <2> 使用IntPtr实现

    c#程序调用:

[csharp] view plain copy print ?
  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); 
[csharp] view plain copy print ?
  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); 

     最后,谈一下结构体(指针、指针的指针)采用IntPtr实现调用。

例如c++dll中有结构体指针或者结构体指针的指针函数调用,如int XXX(STRUCT **struct);

        c#可以 [DllImport("xxx.dll",ChaSet=CharSet.Ansi)] int XXX(IntPtr struct);

       c#中函数可以类似下边调用:

      

[csharp] view plain copy print ?
  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. }  

你可能感兴趣的:(c#学习相关)