C# 結合 PInvoke 對接 IP 攝像頭的筆記

最近作項目的時候,須要對接廠商提供的 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() 方法便可。函數

對接 DLL 時的問題記錄

通常來講,提供 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);
    }
}

附錄 1:經常使用數據類型對照表

C/C++ C# 備註
WORD ushort
DWORD uint
UCHAR intbyte
UCHAR* stringIntPtr
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

相關文章
相關標籤/搜索