最近做項目的時候,需要對接廠商提供的 IP 攝像頭。但是他們只提供了 C++ 的 SDK,沒辦法,只能開始擼 C 的 SDK Helper 類。本篇文章主要記錄了對接 C++ DLL 需要註意的幾個地方,以及常見類型的轉換。 要對接 C++ 的 DLL,首先得知道如何引用 DLL 內的方法。在 C ...
最近做項目的時候,需要對接廠商提供的 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# | 備註 |
---|---|---|
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++ 數據類型對照
附錄 2:相關工具軟體下載
DLL Export Viewer v1.66:https://files.cnblogs.com/files/myzony/DLL_Export_Viewer_v1.66.zip