c#和c++互操作(平台調用相關)


        [DllImport("ScreenCaptureLib.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void Init();
        [DllImport("ScreenCaptureLib.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int ScreenCapture(IntPtr img);

C#調用應該盡量避免用引用型的數組傳遞,因為c#里的數組不是連續的內存空間,c#里如果數組類型是簡單類型那內存就是連續的。如果內存是引用類型那內存不是連續的,只是一個引用地址的數組,傳給c++后c++操作連續內存空間c#里是得不到的。

C#的簡單值類型數組傳給c++的時候可以在c++端直接寫指針,在c#端寫數組,如下是成立的

int ScreenCapture(Pixcel (* image)[20][240][240], int (* xyDataP)[40], double (* scaleDataP)[20]){
}
[DllImport("ScreenCaptureLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ScreenCapture(IntPtr img, int[] xyData, double[] scaleData);

但是只能做到單項傳遞,即c#數組傳給c++,如果在c++中改變c#數組中的值,c#中的值是不會跟着變得。

如果想在c++改c#中的數組,只能用IntPtr p = Marshal.AllocHGlobal(3 * 20 * 240 * 240);申請空間,然后把IntPtr傳給c++,ScreenCapture(p,xyData,scaleData);,然后用Marshal.Copy(p, imageData, 0, 20 * 240 * 240 * 3);,把值拷貝給c#數組。

參照最后一個項目:http://www.cnblogs.com/wangjixianyun/archive/2013/04/10/3012556.html

 

 

傳參:

轉自:http://blog.csdn.net/Mittermeyer/article/details/1586867

 

0 、前言
從VB到C#,被人詬病比較多的就是交互性比較差,又集中表現在調用Win32 API上。如果說C/C++調用API只是調用函數這類輕松的活,在C#下卻成了阻擋入門者的技術活。之所以產生這么大區別在於數據類型的差異,就是因為C#這類采用了“安全”的類型,我們避免了內存釋放和內存訪問錯誤的一些困擾,但是不得不面對調用API時的繁瑣。有得必有失,關鍵看你選擇了什么。
在調用API時,對於值類型的數據,不存在什么轉換問題,只要搞清楚到底是Byte、Int16、Int32 還是Int64就可以了,比較麻煩的地方是指針,因為C#中沒有辦法顯性的使用指針,有時需要借助unsafe code達到這個目的。如果都“unsafe”了,那還用C#干嗎,本文的目的就是總結一下,怎樣用“safe”的方式解決Win32 API中指針類型參數的問題。
 
1、  基本原則
在我們在調用API時,如果發現參數中有指針類型的時候,不要簡單的用IntPtr去替換,或者直接就是用*來定義。雖然C#中能夠使用指針,但是這樣做就違背了C#設計時的初衷,此外DotNET Framework平台下使用unsafe代碼多少會影響應用程序的效率。
當我們拿到一個API,閱讀API的說明時,一定要關注以下幾點:
l 每一個參數的數據類型是什么?如果是指針,指針指向的是一個什么數據結構,基本數據類型、字符串、結構還就是一塊內存。不同的類型在C#下處理的模式是不同的。
l 指針所指向的數據結構是誰創建,該由誰釋放?這也非常重要,它兩層含義:一個是我們怎么定義接口,並且准備調用參數;另一個就是資源釋放的問題,某些調用這申請,被調用這釋放的資源,需要約定的方法申請或釋放資源,反之亦然。
只要花點時間分析一下,就會發現即便是在復雜的結構,不用“unsafe code”也能夠完成調用,只不過有時候過程有點繁瑣,不如C/C++調用API那么暢快淋漓。但是我想說的是,如果選擇了C#,那么就是C#的思想去解決問題,這樣才能夠發揮出C#所有的潛力。
 
2 、實例分析
了解了基本原則,下面就逐一分析一下怎樣雅致並且“安全”地解決不同類型指針的調用問題。
2.1、           字符串
字符串應該是我們接觸到最多的情況,一般在API定義中被描述為“LPSTR/LPTSTR/LPCTSTR/LPWSTR”之類,我們在申明API接口的時候,如果是傳入類型的參數,直接用String類型申明即可,例如:
         ///<summary>
        
///原型是:HMODULE LoadLibrary(LPCTSTR lpFileName); 
        
///</summary>
        
///<param name="lpFileName">DLL 文件名</param>
        
///<returns>函數庫模塊的句柄</returns>

        [DllImport( " kernel32.dll " )]
        
public   static   extern  IntPtr LoadLibrary( string  lpFileName);
但是如果是傳出類型的字符串參數,簡單的這么寫就不行了。我的理解是String變成LPSTR,是DotNET Framework的交互接口幫我們做了一次轉換,創建了一個字符數組,將我們提供的String復制了一次,再傳遞給API,並非簡單的指針傳遞,所以當我們要求在我們設定的一個地址區域去寫數據時,就不能夠直接申明為String,而應該是Byte或者Char數組,可以參考下面的例子:
函數聲明:
 
         ///<summary>
        
/// int GetClassName(HWND hWnd, LPTSTR lpClassName, int nMaxCount); 
        
///</summary>

        [DllImport( " user32 " ,CharSet = CharSet.Ansi)]
        
public   static   extern  Int32 GetClassName(IntPtr hwnd, Byte[] lpClassName, Int32 nMaxCount);
         調用事例:
         String sClassName  =   null ;
         Byte[] abClassName 
=   null ;
         Int32 dwRet 
=   0 ;
 
         abClassName 
=   new  Byte[ 100 ];
         dwRet 
=  GetClassName( this .Handle, abClassName,  100 );
         sClassName 
=  System.Text.ASCIIEncoding.ASCII.GetString(abClassName, 0 ,dwRet);
         MessageBox.Show(sClassName);
還需要注意一點的就是Ansi還是Unicode的字符集了,申明的是什么就用什么轉換。
 
2.2、           句柄—Handle
句柄嚴格意義上來說不能歸在指針這一類,句柄是本宏定義掩蓋了的一種數據結構,不過行為上和指針有些類似。最常見的有窗口句柄、Socket句柄還有內核對象的句柄等。總之H開頭的一些定義基本都是句柄。
對於句柄來說我們通常無法直接訪問句柄所代表的那個數據結構,只要記錄句柄值就可以了,而且我們並不關心句柄這個值的內容,只要他有效就行了,所以句柄最容易處理。一般Win32下,句柄就是一個32位的整型,所以用Int32/UInt32或者IntPtr申明即可。還是上面那個例子,HMODULE就是一個句柄。
 
2.3、           基本類型的指針
兩種情況下會出現基本類型的指針:一種是基本類型的地址,表示返回類型的參數;一種是表示傳遞一個基本類型的數組,這兩種情況需要分別對待。
返回類型,C#中有專門的修飾符ref,表示參數傳遞按地址傳送。缺省情況下參數都是按值傳遞的,如果希望按照地址傳遞,只要在參數前添加ref的修飾符即可。例如:
         ///<summary>
        
///原形:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); 
        
///</summary>

        [DllImport( " kernel32.dll " )]
        
public   extern   static  Int32 ReadFile(IntPtr hFile, Byte[] buffer,Int32 nNumberOfBytesToRead,  ref  Int32 lpNumberOfBytesRead,  ref  OVERLAPPED lpOverlapped);
 
對於C/C++中數組參數,就是一塊連續內存的首地址。在C#中的數組默認都是從Array派生的類,雖然結構復雜了,但是內存布局應該是相同的,所以只要把參數定義為基本類型的數組就可以了。例如:
         ///<summary>
        
/// BOOL GetCharWidth(HDC hdc,UINT iFirstChar,UINT iLastChar,LPINT lpBuffer);
        
///</summary>

        [DllImport( " gdi32 " )]
        
public   static   extern  Int32 GetCharWidth(HDC hdc, Int32 wFirstChar, Int32 wLastChar, int32[] lpBuffer);
 
 
2.4、           結構
說到結構,先要解釋一下C#中數據類型的分類。C#中的數據類型一般有兩種,一種是值類型,就是Byte、Int32之流,出於反射的需要,值類型都是從ValueType派生而得;一種是引用類型,從Object派生出來的類都是引用類型。所謂值類型,就是賦值和傳遞了傳的是數據本身,引用類型傳遞的是數據所對應實例的引用,C#中結構(以struct定義的)是值類型的,類(以class定義的)是引用類型的。
實際調用API時,API參數如果是一個自定義結構指針的話,通常把數據結構定義為struct,在申明時函數接口時用ref修飾。例如Guid就是DotNET類庫中內建的一個結構,具體用法如下:
         /// <summary>
        
/// 原形:HRESULT WINAPI GetDeviceID(LPCGUID pGuidSrc, LPGUID pGuidDest);
        
/// </summary>
        
/// <param name="pGuidSrc"></param>
        
/// <param name="pGuidDest"></param>
        
/// <returns></returns>
        [DllImport( " Dsound.dll " )]
        
private   static   extern  Int32 GetDeviceID( ref  Guid pGuidSrc,  ref  Guid pGuidDest);
 
如果自定義結構的話,結構在內存中占據的字節數務必要匹配,當結構中包含數組的時候需要用MarshalAsAttribute屬性進行修飾,設定數組長度。具體可以參考下面的例子:
         ///<summary>
        
///原形:
        
/// typedef struct tagPAINTSTRUCT { 
        
///     HDC hdc; 
        
///     BOOL fErase; 
        
///     RECT rcPaint; 
        
///     BOOL fRestore; 
        
///     BOOL fIncUpdate; 
        
///     BYTE rgbReserved[32]; 
        
/// } PAINTSTRUCT;
        
///</summary>

         public   struct  PAINTSTRUCT
        
{
            
public IntPtr hdc;
            
public Boolean fErase;
            
public RECT rcPaint;
            
public Boolean fRestore;
            
public Boolean fIncUpdate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst 
= 32)]public Byte[] rgbReserved;
        }

 
        
///<summary>
        
///原形:HDC BeginPaint( HWND hwnd, LPPAINTSTRUCT lpPaint);
        
///</summary>

        [DllImport( " user32 " )]
        
public   static   extern  IntPtr BeginPaint(IntPtr hwnd,  ref  PAINTSTRUCT lpPaint);
 
2.5、           結構數組
結構數組就比較復雜了,就我個人的經驗,不同廠商提供的API,實現不同,需要采用的處理方式也不相同的。
一般情況下,參照基本類型數組的調用方式即可,例如:
         /// <summary>
        
/// 原形:
        
///  typedef struct tagACCEL {
        
///      BYTE fVirt;
        
///      WORD key;
        
///      WORD cmd;
        
///  } ACCEL, *LPACCEL;
        
/// </summary>
         public   struct  ACCEL
        {
            
public  Byte fVirt;
            
public  UInt16 key;
            
public  UInt16 cmd;
        }
        
/// <summary>
        
/// 原形:int CopyAcceleratorTable(HACCEL hAccelSrc,LPACCEL lpAccelDst,int cAccelEntries);
        
/// </summary>
        
/// <returns></returns>
        [DllImport( " user32 " )]
        
public   static   extern  Int32 CopyAcceleratorTable(IntPtr hAccelSrc, ACCEL[] lpAccelDst, Int32 cAccelEntries);
 
但是也有特殊情況,對些廠商提供的API中,不知是否和內存復制的方式有關,類似的函數,如果采用上面相同的定義方法調用的話,調用正確,但是應該返回的數據沒有被改寫。這個時候就需要另一種方法來解決了。
眾所周知,在邏輯上結構是一段連續的內存,數組也是一段連續內存,我們可以從堆中直接申請一段內存,調用API,然后將返回的數據再轉換成結構即可。具體可以參看下面的例子。
結構定義以及API聲明:
        [StructLayout(LayoutKind.Sequential, Pack  =   8 )]
        
private   struct  CmBoxInfo
        {
            
public   static  CmBoxInfo Empty  =   new  CmBoxInfo();
 
            
public   byte  MajorVersion;
            
public   byte  MinorVersion;
            
public   ushort  BoxMask;
            
public   uint  SerialNumber;
            
public   ushort  BoxKeyId;
            
public   ushort  UserKeyId;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst 
=  CM_PUBLIC_KEY_LEN)]  public   byte [] BoxPublicKey;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst 
=  CM_PUBLIC_KEY_LEN)]  public   byte [] SerialPublicKey;
            
public   uint  Reserve;
 
            
public   void  Init()
            {
                BoxPublicKey 
=   new   byte [CM_PUBLIC_KEY_LEN];
                Debug.Assert(BoxPublicKey 
!=   null );
                SerialPublicKey 
=   new   byte [CM_PUBLIC_KEY_LEN];
                Debug.Assert(SerialPublicKey 
!=   null );
            }
        }
        
/// <summary>
        
/// 原型:int CMAPIENTRY CmGetBoxes(HCMSysEntry hcmse, unsigned long idPort, CMBOXINFO *pcmBoxInfo, unsigned int cbBoxInfo) 
        
/// </summary>
        [DllImport( " xyz.dll " )]
        
private   static   extern  Int32 CmGetBoxes(IntPtr hcmse, CmGetBoxesOption idPort,IntPtr pcmBoxInfo, Int32 cbBoxInfo);
 
調用示例
            IntPtr hcmBoxes  =  IntPtr.Zero;
            CmAccess cma 
=   new  CmAccess();
            CmBoxInfo[] aBoxList 
=   null ;
            Int32 dwBoxNum 
=   0 , dwLoop  =   0 ,dwBoxInfoSize  =   0 ;
            IntPtr pBoxInfo 
=  IntPtr.Zero;
 
            dwBoxNum 
=  m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, IntPtr.Zero,  0 );
            
if  (dwBoxNum  >   0 )
            {
                aBoxList 
=   new  CmBoxInfo[dwBoxNum];
                
if  (aBoxList  !=   null )
                {
                    dwBoxInfoSize 
=  Marshal.SizeOf(aBoxList[ 0 ]);
                    pBoxInfo 
=  Marshal.AllocHGlobal(dwBoxInfoSize  *  dwBoxNum);
                    
if  (pBoxInfo  !=  IntPtr.Zero)
                    {
                        dwBoxNum 
=  m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, pBoxInfo, dwBoxNum);
                        
for  (dwLoop  =   0 ; dwLoop  <  dwBoxNum; dwLoop ++ )
                        {
                            aBoxList[dwLoop] 
=  (CmBoxInfo)Marshal.PtrToStructure((IntPtr)((UInt32)pBoxInfo  +  dwBoxInfoSize  *  dwLoop), CmBoxInfo.Empty.GetType());
                        }
                        Marshal.FreeHGlobal(pBoxInfo);
                        pBoxInfo 
=  IntPtr.Zero;
                    }
                    
else
                    {
                        aBoxList 
=   null ;
                    }
                }
            }
 
       最后提一句,Marshal類非常有用,其中包括了大量內存申請、復制和類型轉換的函數,靈活運用的話,基本上可以避免unsafe code。
 
2.6、           函數指針(回調函數)
C#中采用委托(delegate)和函數指針等同的功能,當API函數的參數為回調函數時,我們通常使用委托來替代。與C和C++ 中的函數指針相比,委托實際上是具體一個Delegate派生類的實例,它還包括了對參數和返回值,類型安全的檢查。
先看一下下面的例子:
         /// <summary>
        
/// 原形:typedef BOOL (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, LPCSTR, LPCSTR, LPVOID);
        
/// </summary>
         public   delegate  Boolean LPDSENUMCALLBACK(IntPtr guid, String sDesc, String sDevName,  ref  Int32 dwFlag);
        
/// <summary>
        
/// 原形:HRESULT WINAPI DirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
        
/// </summary>
        [DllImport( " Dsound.dll " )]
        
public   static   extern  Int32 DirectSoundCaptureEnumerate(LPDSENUMCALLBACK pDSEnumCallBack,  ref  Int32 dwFlag);
具體調用方法如下:
          dwRet  =  DirectSoundEnumerate( new  LPDSENUMCALLBACK(DSoundEnumCallback), ref  dwFlag);
 
       這里需要特別注意的就是委托實際上是一個實例,和普通的類實例一樣,是被DotNET Framework垃圾收集機制所管理,有生存周期的。上文例子的定義方式其實函數級別的局部變量,當函數結束時,將被釋放,如果回調仍然在繼續的話,就會產生諸如非法訪問的錯誤。所以在使用回調函數的時候一定要比較清楚的了解,回調的作用周期是多大,如果回調是全局的,那么定義一個全局的委托變量作為參數。
 
2.7、           表示多種類型的指針—LPVOID以及其它
指針是C/C++的精髓所在,一個void能夠應付所有的問題,我們遇到最多的可能就是LPVOID這樣的參數。LPVOID最常用的有兩種情況,一種就是表示一個內存塊,另一種情況可能是根據其它參數的定義指向不同的數據結構。
第一種情況很好處理,如果是一個內存塊,我們可以他當作一個Byte數組就可以了,例如:
 
         ///<summary>
        
///原形:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); 
        
///</summary>

        [DllImport( " kernel32.dll " )]
        
public   extern   static  Int32 ReadFile(IntPtr hFile, Byte[] buffer,Int32 nNumberOfBytesToRead,  ref  Int32 lpNumberOfBytesRead,  ref  OVERLAPPED lpOverlapped);
第二種情況比較復雜,C#中類型轉換是有限制的,一個Int32是沒法直接轉換成為Point的,這個時候之能夠根據不同的參數類型定義不同的重載函數了。例如GetProcAddress函數的lpProcName既可以是一個字符串表示函數名,又可以是一個高字為0的Int32類型,表示函數的序號,我們可以這樣分別定義:
         ///<summary>
        
///原型是: FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
        
///</summary>

        [DllImport( " kernel32.dll " , EntryPoint  =   " GetProcAddress " )]
        
private   extern   static  IntPtr GetProcAddress(IntPtr hModule, String sFuncName);
        [DllImport(
" kernel32.dll " , EntryPoint  =   " GetProcAddress " )]
        
private   extern   static  IntPtr GetProcAddressByIndex(IntPtr hModule, Int32 dwIndex);
 
 
在這里總結了調用API時有關指針的一些常見問題,你會發現大多數情況下C#依靠自身的能力就能解決問題,希望對大家有幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM