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另一個難點:數據類型轉換
百度文庫這篇文章基本把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中對各個重載解釋如下
名稱 | 說明 | |
---|---|---|
![]() ![]() ![]() ![]() |
Copy(Byte[], Int32, IntPtr, Int32) | 安全關鍵。將一維的托管 8 位無符號整數數組中的數據復制到非托管內存指針。 |
![]() ![]() ![]() ![]() |
Copy(Char[], Int32, IntPtr, Int32) | 安全關鍵。將數據從一維的托管字符數組復制到非托管內存指針。 |
![]() ![]() ![]() ![]() |
Copy(Double[], Int32, IntPtr, Int32) | 安全關鍵。將數據從一維的托管雙精度浮點數組復制到非托管內存指針。 |
![]() ![]() ![]() ![]() |
Copy(Int16[], Int32, IntPtr, Int32) | 安全關鍵。將一維的托管 16 位有符號整數數組中的數據復制到非托管內存指針。 |
![]() ![]() ![]() ![]() |
Copy(Int32[], Int32, IntPtr, Int32) | 安全關鍵。將數據從一維的托管 32 位有符號整數數組復制到非托管內存指針。 |
![]() ![]() ![]() ![]() |
Copy(Int64[], Int32, IntPtr, Int32) | 安全關鍵。將一維的托管 64 位有符號整數數組中的數據復制到非托管內存指針。 |
![]() ![]() ![]() ![]() |
Copy(IntPtr, Byte[], Int32, Int32) | 安全關鍵。將數據從非托管內存指針復制到托管 8 位無符號整數數組。 |
![]() ![]() ![]() ![]() |
Copy(IntPtr, Char[], Int32, Int32) | 安全關鍵。將數據從非托管內存指針復制到托管字符數組。 |
![]() ![]() ![]() ![]() |
Copy(IntPtr, Double[], Int32, Int32) | 安全關鍵。將數據從非托管內存指針復制到托管雙精度浮點數組。 |
![]() ![]() ![]() ![]() |
Copy(IntPtr, Int16[], Int32, Int32) | 安全關鍵。將非托管內存指針中的數據復制到托管 16 位有符號整數數組。 |
![]() ![]() ![]() ![]() |
Copy(IntPtr, Int32[], Int32, Int32) | 安全關鍵。將非托管內存指針中的數據復制到托管 32 位有符號整數數組。 |
![]() ![]() ![]() ![]() |
Copy(IntPtr, Int64[], Int32, Int32) | 安全關鍵。將非托管內存指針中的數據復制到托管 64 位有符號整數數組。 |
![]() ![]() ![]() ![]() |
Copy(IntPtr, Single[], Int32, Int32) | 安全關鍵。將數據從非托管內存指針復制到托管單精度浮點數組。 |
![]() ![]() ![]() ![]() |
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);
其實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#看哪種數據類型在操作相應的操作方便些。