zoukankan      html  css  js  c++  java
  • 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(@"ThirdFilesAlprSDK.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(@"ThirdFilesAlprSDK.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(@"ThirdFilesAlprSDK.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:itmap.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

  • 相关阅读:
    JavaScript ECMAScript版本介绍
    Webpack
    路由
    组件(重难点)
    npm包管理工具
    过滤器
    Vue实例生命周期
    数据双向绑定的原理
    MVC + MVVM
    vue事件修饰符
  • 原文地址:https://www.cnblogs.com/myzony/p/11944867.html
Copyright © 2011-2022 走看看