[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類型申明即可,例如:








但是如果是傳出類型的字符串參數,簡單的這么寫就不行了。我的理解是String變成LPSTR,是DotNET Framework的交互接口幫我們做了一次轉換,創建了一個字符數組,將我們提供的String復制了一次,再傳遞給API,並非簡單的指針傳遞,所以當我們要求在我們設定的一個地址區域去寫數據時,就不能夠直接申明為String,而應該是Byte或者Char數組,可以參考下面的例子:
函數聲明:





調用事例:









還需要注意一點的就是Ansi還是Unicode的字符集了,申明的是什么就用什么轉換。
2.2、
句柄—Handle
句柄嚴格意義上來說不能歸在指針這一類,句柄是本宏定義掩蓋了的一種數據結構,不過行為上和指針有些類似。最常見的有窗口句柄、Socket句柄還有內核對象的句柄等。總之H開頭的一些定義基本都是句柄。
對於句柄來說我們通常無法直接訪問句柄所代表的那個數據結構,只要記錄句柄值就可以了,而且我們並不關心句柄這個值的內容,只要他有效就行了,所以句柄最容易處理。一般Win32下,句柄就是一個32位的整型,所以用Int32/UInt32或者IntPtr申明即可。還是上面那個例子,HMODULE就是一個句柄。
2.3、
基本類型的指針
兩種情況下會出現基本類型的指針:一種是基本類型的地址,表示返回類型的參數;一種是表示傳遞一個基本類型的數組,這兩種情況需要分別對待。
返回類型,C#中有專門的修飾符ref,表示參數傳遞按地址傳送。缺省情況下參數都是按值傳遞的,如果希望按照地址傳遞,只要在參數前添加ref的修飾符即可。例如:






對於C/C++中數組參數,就是一塊連續內存的首地址。在C#中的數組默認都是從Array派生的類,雖然結構復雜了,但是內存布局應該是相同的,所以只要把參數定義為基本類型的數組就可以了。例如:






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);
/// 原形: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屬性進行修飾,設定數組長度。具體可以參考下面的例子:



























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);
/// 原形:
/// 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);
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 ;
}
}
}
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);
/// 原形: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);
具體調用方法如下:


這里需要特別注意的就是委托實際上是一個實例,和普通的類實例一樣,是被DotNET Framework垃圾收集機制所管理,有生存周期的。上文例子的定義方式其實函數級別的局部變量,當函數結束時,將被釋放,如果回調仍然在繼續的話,就會產生諸如非法訪問的錯誤。所以在使用回調函數的時候一定要比較清楚的了解,回調的作用周期是多大,如果回調是全局的,那么定義一個全局的委托變量作為參數。
2.7、
表示多種類型的指針—LPVOID以及其它
指針是C/C++的精髓所在,一個void能夠應付所有的問題,我們遇到最多的可能就是LPVOID這樣的參數。LPVOID最常用的有兩種情況,一種就是表示一個內存塊,另一種情況可能是根據其它參數的定義指向不同的數據結構。
第一種情況很好處理,如果是一個內存塊,我們可以他當作一個Byte數組就可以了,例如:






第二種情況比較復雜,C#中類型轉換是有限制的,一個Int32是沒法直接轉換成為Point的,這個時候之能夠根據不同的參數類型定義不同的重載函數了。例如GetProcAddress函數的lpProcName既可以是一個字符串表示函數名,又可以是一個高字為0的Int32類型,表示函數的序號,我們可以這樣分別定義:








在這里總結了調用API時有關指針的一些常見問題,你會發現大多數情況下C#依靠自身的能力就能解決問題,希望對大家有幫助。