c#調用c++動態庫的一些理解
c#調用c++動態庫一般我們這樣寫
[DllImport("UCamer.dll", CallingConvention = CallingConvention.Winapi)]
public extern static void Disp_Destroy(IntPtr hShow);
DllImport的第一個參數UCamer.dll是動態庫dll的路徑,此dll放在程序運行的根目錄或者c:windows/sytem32下
CallingConvention 參數是c#調用c++的方式 是個枚舉 msdn解釋如下
Cdecl 調用方清理堆棧。這使您能夠調用具有 varargs 的函數(如 Printf),使之可用於接受可變數目的參數的方法。
FastCall 不支持此調用約定。
StdCall 被調用方清理堆棧。這是使用平台 invoke 調用非托管函數的默認約定。
ThisCall 第一個參數是 this 指針,它存儲在寄存器 ECX 中。其他參數被推送到堆棧上。此調用約定用於對從非托管 DLL 導出的類調用方法。
Winapi 此成員實際上不是調用約定,而是使用了默認平台調用約定。例如,在 Windows 上默認為 StdCall,在 Windows CE.NET 上默認為 Cdecl。
從上面來看Winapi方式是根據系統自動選擇調用規約的。 而thisCall是對c++類的調用方法。 所以 一般情況下我們選擇Winapi就可以了。
c#調用dll另一個難點:數據類型轉換
http://wenku.baidu.com/link?url=SihlxtHC-HMcEhq3izpd2bux8rNaKOMTpu8NPqjdYlLSwYSV1CqNJdVbxkaZm7OqaaSTEK-KUJqX5jbtkdpnUZ_38No4tsrgqCsf7Th5dqK
百度文庫這篇文章基本把c++與c#的對應數據類型總結完了。但是為什麼這裡還要說呢
1,百度文庫這篇文章,包括大部分度娘的類型轉換的資料中 對c++中的返回值類型char[] 都轉換成了char[] 沒做任何改變。
c++中char占一個字節,assic編碼。而c#中的char占2個字節(我是在中文版的vs中測試的)。
如果這樣轉換就會出現問題,很容易發生越界,或者讀寫到受保護的內存等問題。
解決方法是 把char[] 變成c#中的byte[] ,再用Encoding.Assic.getstring方法轉換。
2,關於指針,c++所有的指針 在c#上用Intptr ,問題又來了。假如返回的Intptr是個數組指針,在c#中我們怎麼讀取數組裡面的元素呢?
Marshal.Copy();
Marshal類是c#中專門把非托管內存轉換成托管內存的神器,不需要unsafe。 Marshal中copy方法是最最常用的方法。裡面有16個重載。能解決目前你能遇到的大部分問題。
msdn中對各個重載解釋如下
名稱 說明
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(Byte[], Int32, IntPtr, Int32) 安全關鍵。將一維的托管 8 位無符號整數數組中的數據復制到非托管內存指針。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(Char[], Int32, IntPtr, Int32) 安全關鍵。將數據從一維的托管字符數組復制到非托管內存指針。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(Double[], Int32, IntPtr, Int32) 安全關鍵。將數據從一維的托管雙精度浮點數組復制到非托管內存指針。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(Int16[], Int32, IntPtr, Int32) 安全關鍵。將一維的托管 16 位有符號整數數組中的數據復制到非托管內存指針。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(Int32[], Int32, IntPtr, Int32) 安全關鍵。將數據從一維的托管 32 位有符號整數數組復制到非托管內存指針。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(Int64[], Int32, IntPtr, Int32) 安全關鍵。將一維的托管 64 位有符號整數數組中的數據復制到非托管內存指針。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(IntPtr, Byte[], Int32, Int32) 安全關鍵。將數據從非托管內存指針復制到托管 8 位無符號整數數組。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(IntPtr, Char[], Int32, Int32) 安全關鍵。將數據從非托管內存指針復制到托管字符數組。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(IntPtr, Double[], Int32, Int32) 安全關鍵。將數據從非托管內存指針復制到托管雙精度浮點數組。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(IntPtr, Int16[], Int32, Int32) 安全關鍵。將非托管內存指針中的數據復制到托管 16 位有符號整數數組。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(IntPtr, Int32[], Int32, Int32) 安全關鍵。將非托管內存指針中的數據復制到托管 32 位有符號整數數組。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(IntPtr, Int64[], Int32, Int32) 安全關鍵。將非托管內存指針中的數據復制到托管 64 位有符號整數數組。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(IntPtr, Single[], Int32, Int32) 安全關鍵。將數據從非托管內存指針復制到托管單精度浮點數組。
公共方法 靜態成員 受 Silverlight for Windows Phone 支持 受 Xbox 360 支持 Copy(Single[], Int32, IntPtr, Int32) 安全關鍵。將數據從一維的托管單精度浮點數組復制到非托管內存指針。
我們也可以使用共享內存的方式進行操作。部分代碼如下
復制代碼
//初始化返回圖片的大小等信息
myContext = new VlcControlWpfRendererContext(width, height, System.Windows.Media.PixelFormats.Bgr24);
//創建共享內存區域
myBitmapSectionPointer = Win32Interop.CreateFileMapping(new IntPtr(-1), IntPtr.Zero, Win32Interop.PageAccess.ReadWrite, 0, myContext.Size, null);
//獲取共享內存的首地址
map = Win32Interop.MapViewOfFile(myBitmapSectionPointer, Win32Interop.FileMapAccess.AllAccess, 0, 0, (uint)myContext.Size);
//把接收後的圖片拷入共享內存區域
Win32Interop.CopyMemory(map, data, myContext.Size);
//把共享內存中的數組轉換為圖片
myBitmap = (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(myBitmapSectionPointer, myContext.Width, myContext.Height, myContext.PixelFormat, myContext.Stride, 0);
復制代碼
這裡是共享內存的輔助類 主要是一些api函數
其實Intptr本質也是一個Int,Int在c#中和Int32是一樣的。所以基本上指針,Long,int在c#中都是int。只是這樣些 方便大家知道他是c++中的什麼類型,方便轉換而已。
3,c++中的函數指針 與c#中的委托
這是c++中對函數指針的定義
typedef VOID (WINAPI *PUSERCALL)( PUCHAR pData, ULONG Length, PVOID pUserData );
對應c#中的例子如下
public delegate void PUSERCALL(IntPtr pData, uint Length, UInt32 pUserData);
4,我們知道int是占4個字節的。
下面這個是c++的一個方法
U_CAMER LONG WINAPI CAMER_GetPropery( HANDLE hCamer, _CMRCTL Propery );
假如我們把此函數翻譯成c#中的下面這個函數
[DllImport("UCamer.dll", CallingConvention = CallingConvention.Winapi)]
public extern static Uint16 CAMER_GetPropery(IntPtr hCamer, CMRCTL Propery);
我們在c#調用此方法
uint16 m_HiWi_temp = (uint)BCamera.CAMER_GetPropery(m_hCamer, CMRCTL.OUT_SIZE);
發現一個很有趣的問題,此處調用沒有問題。也有值,但是他是取的int32中4個字節的2個字節。
我們看原本c++對此函數的調用
*((PULONG)m_HiWi) = *((PULONG)m_Display) = CAMER_GetPropery( m_hCamer, OUT_SIZE );
m_hShow = Disp_Create( m_hWnd, m_HiWi[1], m_HiWi[0], m_nColor, (USERDRAW)((m_ReDrawLine == TRUE) ? DrawLine : NULL), this );
本來CAMER_GetPropery函數只返回了一個long類型。c++中通過指針的轉換。把long類型轉換成了 pulong,也是ulong的數組。
那c#中怎麼我們該怎麼調用呢
int m_HiWi_temp = BCamera.CAMER_GetPropery(m_hCamer, CMRCTL.OUT_SIZE);
byte[] m_byte_HiWi = BitConverter.GetBytes(m_HiWi_temp);
byte[] temp1 = new byte[2] { m_byte_HiWi[0], m_byte_HiWi[1] };
byte[] temp2 = new byte[2] { m_byte_HiWi[2], m_byte_HiWi[3] };
int width = BitConverter.ToInt16(temp1, 0);
int high = BitConverter.ToInt16(temp2, 0);
這裡舉這個例子是說c++有時真的就是返回一個int類型,但是在c++中可以輕松把int類型通過指針輕松轉換成兩個uint16的數組。所以c#中我們再轉換的時候一定有注意了。
總結:其實數據類型的轉換主要是對數據存儲空間的轉換,c++中的數據類型占用多大的空間,只要轉換成c#中占同等空間的數據類型就可以了。只是c#看哪種數據類型在操作相應的操作方便些。