最近作項目的時候,須要對接廠商提供的 IP 攝像頭。可是他們只提供了 C++ 的 SDK,沒辦法,只能開始擼 C# 的 SDK Helper 類。本篇文章主要記錄了對接 C++ DLL 須要注意的幾個地方,以及常見類型的轉換。html
要對接 C++ 的 DLL,首先得知道如何引用 DLL 內的方法。在 C# 當中,只須要編寫符合 C++ 的函數簽名,再使用 [DllImport]
特性指定 DLL 文件路徑和入口點等參數便可。c++
假如你須要使用 Win32 API 提供的方法,這裏我以 SetProcessDPIAware
函數爲例:api
public static class Win32Helper { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); }
接下來你只須要像使用靜態方法同樣,調用 Win32Helper.SetProcessDPIAware()
方法便可。函數
通常來講,提供 SDK 的廠商都會給你一份 DEMO 項目,或者是包含有函數定義的頭文件 (*.h
)。你只須要按照轉換規則,將頭文件裏面的函數簽名翻譯成 C# 版本的便可。工具
有的時候,你名字直接和頭文件同樣還不行,得手動指定 EntryPoint
參數。你可使用 DLL Export Viewer 工具來查看 DLL 的全部開放函數簽名,將其複製下來,填寫到 EntryPoint
參數便可。ui
[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# 如何將委託傳遞給該參數做爲回調函數呢?spa
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++ 的函數簽名以下:code
//////////////////////////////////////////////////////////////////////////////////////////// //捕獲一張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); } }
C/C++ | C# | 備註 |
---|---|---|
WORD |
ushort |
|
DWORD |
uint |
|
UCHAR |
int 或 byte |
|
UCHAR* |
string 或 IntPtr |
|
unsigned char* |
[MarshalAs(UnmanagedType.LPArray)]byte[] |
|
char* |
string |
|
LPCTSTR |
string |
|
LPTSTR |
[MarshalAs(UnmanagedType.LPTStr)] string |
|
long |
int |
|
ulong |
uint |
|
HANDLE |
IntPtr |
|
HWND |
IntPtr |
|
void* |
IntPtr |
|
int |
int |
|
int* |
ref int |
|
*int |
IntPtr |
|
unsigned int |
uint |
|
COLORREF |
uint |
|
CHAR |
char |
|
HDC |
int |
|
HGDIOBJ |
int |
|
BOOL |
bool |
|
LPSTR |
string |
|
LPCSTR |
string |
|
BYTE |
byte |
參考文章:C# 與 C++ 數據類型對照
DLL Export Viewer v1.66:https://files.cnblogs.com/files/myzony/DLL_Export_Viewer_v1.66.zip