天天看點

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# 版本的即可。

函數簽名不正确

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

有的時候,你名字直接和頭檔案一樣還不行,得手動指定

EntryPoint

參數。你可以使用 DLL Export Viewer 工具來檢視 DLL 的所有開放函數簽名,将其複制下來,填寫到

EntryPoint

參數即可。

C# 結合 PInvoke 對接 IP 攝像頭的筆記
[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