欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 结合 PInvoke 对接 IP 摄像头的笔记

程序员文章站 2022-08-08 14:20:31
最近做项目的时候,需要对接厂商提供的 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# 版本的即可。

函数签名不正确

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 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