最近做項目的時候,需要對接廠商提供的 IP 攝像頭。但是他們隻提供了 C++ 的 SDK,沒辦法,隻能開始撸 C# 的 SDK Helper 類。本篇文章主要記錄了對接 C++ DLL 需要注意的幾個地方,以及常見類型的轉換。
要對接 C++ 的 DLL,首先得知道如何引用 DLL 内的方法。在 C# 當中,隻需要編寫符合 C++ 的函數簽名,再使用
[DllImport]
特性指定 DLL 檔案路徑和入口點等參數即可。
假如你需要使用 Win32 API 提供的方法,這裡我以
SetProcessDPIAware
函數為例: public static class Win32Helper
{
[DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
}
接下來你隻需要像使用靜态方法一樣,調用
Win32Helper.SetProcessDPIAware()
方法即可。
對接 DLL 時的問題記錄
一般來說,提供 SDK 的廠商都會給你一份 DEMO 項目,或者是包含有函數定義的頭檔案 (
*.h
)。你隻需要按照轉換規則,将頭檔案裡面的函數簽名翻譯成 C# 版本的即可。
函數簽名不正确
有的時候,你名字直接和頭檔案一樣還不行,得手動指定
EntryPoint
參數。你可以使用 DLL Export Viewer 工具來檢視 DLL 的所有開放函數簽名,将其複制下來,填寫到
EntryPoint
參數即可。
[DllImport(@"ThirdFiles\AlprSDK.dll", EntryPoint = "AlprSDK_Startup@12", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi)]
public static extern int AlprSDK_Startup(IntPtr hNotifyWnd, uint nCommandId, string pLocalAddress);
傳遞回調函數
有時第三方 SDK 需要你傳遞回調函數,一般都隻提供了一個
void*
定義,也就是一個函數指針。那我們在 C# 如何将委托傳遞給該參數作為回調函數呢?
ALPRSDK_API OS_Error WINAPI AlprSDK_SearchAllCameras(unsigned int nTimeout,void* callback, char *pLocalAddr = NULL);
這個時候就需要使用到
[UnmanagedFunctionPointer]
特性來指定函數指針了,隻需要将其标注到委托定義上,指定函數的調用方式即可。
最後我在 C# 裡面編寫的方法簽名如下:
[UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)]
public delegate void SearchAllCamerasCallback(uint deviceType, string deviceName, string deviceIp,
byte[] macAddress, ushort wPortWeb, ushort wPortListen, string pSubMask, string pGateway,
string pMultiAddress, string pDnsAddress, ushort wMultiPort, int nChannelNum, int nFindCount,
uint dwDeviceId);
[DllImport(@"ThirdFiles\AlprSDK.dll", EntryPoint = "_AlprSDK_SearchAllCameras@12", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi)]
public static extern int AlprSDK_SearchAllCameras(uint nTimeout, SearchAllCamerasCallback callback, string pLocalAddress);
擷取攝像頭傳遞的位圖
原始 C++ 的函數簽名如下:
////////////////////////////////////////////////////////////////////////////////////////////
//捕獲一張bmp圖檔.
//pBmpBuf:存放資料的緩沖區,傳入參數時應該為NULL,記憶體由SDK自行管理.外面的應用程式不用去釋放記憶體
//len: 資料的長度
ALPRSDK_API OS_Error WINAPI AlprSDK_CaptureBmp(int nHandleID, void **pBmpBuf, int *len);
主要的難點在于參數
void** pbmp
的翻譯,這裡參數 xx 就是指針的指針。因為這個位圖是 SDK 來生成的,是以它會在記憶體空間開辟一段區域用于位圖的存儲。是以
void*
指向的是這個位圖的起始位址,而我傳遞
void**
就是讓 SDK 将這個起始位址傳遞給我。
是以
void*
可以翻譯為
IntPtr
,而這個位址不是我指派的,而是 SDK 給我的位址,是以我們需要加上按引用傳遞關鍵字
ref
。
如此,我們便獲得了位圖在記憶體空間的起始位址,而且方法也将這個位圖的大小給了我們。我們隻需要從起始位址讀取 N 個位元組的資料,将其轉儲到
byte[]
即可。有了
byte[]
對象,你就可以進行其他的操作了,例如加載,儲存等。
在 C# 内部,我是這樣定義方法簽名,并進行使用的:
[DllImport(@"ThirdFiles\AlprSDK.dll", EntryPoint = "_AlprSDK_CaptureBmp@12", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi)]
public static extern uint AlprSDK_CaptureBmp(int nHandleId, ref IntPtr pBmpBuf, ref int len);
讀取位圖資料,并将其存儲到磁盤當中。
var bitmapPtr = IntPtr.Zero;
var length = 0;
var result = AlprSdk.AlprSDK_CaptureBmp(0, ref bitmapPtr, ref length);
ThrowIfResultNotZero("無法從攝像頭擷取位圖",result);
var bytes = new byte[length];
Marshal.Copy(bitmapPtr, bytes, 0, length);
using (var ms = File.Create(@"D:\bitmap.bmp"))
{
using (var writer = new StreamWriter(ms))
{
writer.Write(bytes);
}
}
附錄 1:常用資料類型對照表
C/C++ | C# | 備注 |
---|---|---|
| | |
| | |
| 或 | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
參考文章:C# 與 C++ 資料類型對照
附錄 2:相關工具軟體下載下傳
DLL Export Viewer v1.66:https://files.cnblogs.com/files/myzony/DLL_Export_Viewer_v1.66.zip