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


最近做項目的時候,需要對接廠商提供的 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 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM