winform中c#调用第三方、opencv原生dll库图像处理 您所在的位置:网站首页 windows开发常用的第三方dll winform中c#调用第三方、opencv原生dll库图像处理

winform中c#调用第三方、opencv原生dll库图像处理

2024-06-02 22:59| 来源: 网络整理| 查看: 265

winform使用相较于较MFC,拖拖拽拽实现基本的交互功能极其的简单。

这里不比较winform和mfc的其他优劣,单从编程语言来说,winform使用c#编程语言,使用c/c++的dll库时不能直接使用,需要使用P/Invoke来导入dll库。

本文内容包含以下内容实现,简单dll库导入使用,复杂dll库(简单dll又引用了其他dll库)导入使用, 利用c#封装c/++功能,图像数据传递处理等。

项目源代码 https://github.com/wanggao1990/LearningCodeLanguage/tree/c#

1、P/Invoke

P/Invoke全称是Platform Invoke (平台调用) ,是一种函数调用机制。通过P/Invoke我们就可以调用非托管DLL中的函数。只需要添加DllImport特性即可以导入C/C++的函数,但是问题是PInvoke不能简单的实现对C++类的调用。

这种机制类似于android中通过jni调用c/c++代码库,需要一个中间层,并且需要保证数据传递保持一致。

导入函数示例说明如下

[DllImport("NativeDll.dll", EntryPoint="?fnNativeDll@@YAHXZ")] static extern int func() ;

其中,NativeDll.dll是导入的库的路径,入口点?fnNativeDll@@YAHXZ是c++函数原型int fnNativeDll()的导出符号,导入函数别名为int func(),名称可以不一样。

注意两点:

函数返回值都是int,不需要做额外处理,当是c/c++使用指针时,这里对应的类型为 IntPtr。函数必须使用static extern说明。导出符号使用c++,所以看着像乱码,对与c代码可以使用extern c 的方式导出,保证符号简单唯一。c++符号导出可以用vs命令行 dumpbin /exports xxx.lib/dll 查看。 2、简单库导入

以上面的例子为例,这里的简单库只有一个函数。

2.1 生成一个dll简单库

使用vs新建一个dll库项目,修改仅保留部分代码,h和cpp如下

/* --- NativeDll.h --- */ #ifdef NATIVEDLL_EXPORTS #define NATIVEDLL_API __declspec(dllexport) #else #define NATIVEDLL_API __declspec(dllimport) #endif NATIVEDLL_API int fnNativeDll(); /* --- NativeDll.cpp --- */ #include "NativeDll.h" NATIVEDLL_API int fnNativeDll() { return 42; }

编译生成后,查看函数导出符号,为“?fnNativeDll@@YAHXZ”。 在这里插入图片描述

2.2 c#中测试

c#控制台程序,直接上代码

using System; using System.Runtime.InteropServices; namespace WinformTest { class NativeDllTest { [DllImport("NativeDll.dll", EntryPoint = "?fnNativeDll@@YAHXZ")] private static extern int fnNativeDll(); public static void Main() { System.Console.WriteLine("%d", fnNativeDll()); } } }

编译成功运行,直接会输出42。

导入库可以写绝对路径或相对路径,也可以直接放入执行目录不加路径(这里使用的方式)。平台使用Any Cpu、x84、x64, dll项目和测试项目保证一致即可。 3、复杂库

在上一节中的代码中,引入opencv库(64位,因此平台都要设置为64)添加一个处理图像的类,类中仅包含灰度化的一个函数,目前输入的rgb图像,不做额外的处理。

3.1 图像处理库导出

函数 grayScale(uint8_t* pSrc, int channel, int width, int height, uint8_t** pDst)传递的是图像的数据指针,通道,宽,高,输出是目标指针的指针。

#include "opencv2\opencv.hpp" class NATIVEDLL_API CNativeDll { public: CNativeDll(); static void grayScale(uint8_t* pSrc, int channel, int width, int height, uint8_t** pDst); }; #include "NativeDll.h" CNativeDll::CNativeDll() { return; } void CNativeDll::grayScale(uint8_t * pSrc, int channel, int width, int height, uint8_t ** pDst) { FILE *pFile = fopen("log.txt", "w"); fprintf(pFile, "input dat: \n" " pSrc %p, channle %d, width %d, height %d\n" " ppDst %p, pDst %p\n", pSrc, channel, width, height, pDst, *pDst ); fclose(pFile); int inStride = 0; //4字节对齐 int outStride = (width + 3) / 4 * 4; cv::Mat gray; if(pDst != nullptr) { gray = cv::Mat(height, outStride, CV_8UC1, *pDst); } else { gray = cv::Mat(height, outStride, CV_8UC1); pDst = &(gray.data); } if(channel == 3) { inStride = (width*channel + 3) / 4 * 4; for(int i = 0; i if (srcBitmap.PixelFormat == PixelFormat.Format8bppIndexed) { dstBitmap = null; return; } BitmapData srcBmpData = srcBitmap.LockBits(new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height), ImageLockMode.ReadOnly, srcBitmap.PixelFormat); // 分配目标bmp数据 Bitmap resBitmap = new Bitmap( srcBmpData.Width, srcBmpData.Height, PixelFormat.Format8bppIndexed); BitmapData resBmpData = resBitmap.LockBits(new Rectangle(0, 0, resBitmap.Width, resBitmap.Height), ImageLockMode.WriteOnly, resBitmap.PixelFormat); // bmp 需要4字节对齐 System.IntPtr dstPtr = resBmpData.Scan0; NativeDllTest.grayScale(srcBmpData.Scan0, 3, srcBitmap.Width, srcBmpData.Height, ref dstPtr); srcBitmap.UnlockBits(srcBmpData); resBitmap.UnlockBits(resBmpData); // 创建索引表 ColorPalette palette = resBitmap.Palette; for (int i = 0; i private NativeDllTest instance; public OpencvForm() { InitializeComponent(); instance = new NativeDllTest(); } private void grayScaleToolStripMenuItem_Click(object sender, EventArgs e) { Bitmap res; instance.grayScale((Bitmap)image, out res); if(null == res) { return; } Form grayForm = new Form(); grayForm.BackgroundImage = res; grayForm.MdiParent = this; grayForm.Show(); } 3.3 测试结果

在这里插入图片描述 可能失败的提示处理, 当前NativeDll.dll库需要依赖opencv_worldxxx.dll库,运行前必须要将所有依赖库放入运行目录下,否则只会提示无法加载NativeDll.dll的错误,并且在项目大时不好排查。

4、修改c++类使用

在上面c++代码中,灰度化函数 static void grayScale(uint8_t* pSrc, int channel, int width, int height, uint8_t** pDst);是用static声明的,调用时实际是加了一个命名空间.

当我们去掉static作为一个成员函数时,导出符号会发生改变,重新修改入口点,之后运行会报错。 在这里插入图片描述 出错原因是我们使用了 NativeDllTest.grayScale()调用静态方法,而这里已经修改为了成员函数。因此这里必须要先实例化一个c++对象,再调用成员函数。

这里可以借用前面博客Android安卓中封装opencv jni代码为Java类安卓中的思想,在c#中线导入构造函数,返回实例化的对象指针,之后调用功能函数时加上传入指针对象即可。

这样的场景适合一个类初始化之后,加载一次资源,之后需要反复调用其私有或保护成员函数的情况。简单情况直接导出函数即可。

4.1 重写c++代码

我们修改c++代码,添加的2个外部函数直接在头文件中,简单期间使用懒加载形式,如下

class NATIVEDLL_API CNativeDll { public: CNativeDll(); // 外部方法 static void* GetInstance() { static CNativeDll instace = CNativeDll(); return &instace; } // 外部方法 static void GrayScale(void *instance, uint8_t* pSrc, int channel, int width, int height, uint8_t** pDst) { ((CNativeDll*)instance)->grayScale(pSrc, channel, width, height, pDst); } //内部方法 static void grayScale(uint8_t* pSrc, int channel, int width, int height, uint8_t** pDst); };

导出符号如下,仅关注我们需要的两个函数。 在这里插入图片描述

4.2 c#中导入函数测试 [DllImport("x64/Debug/NativeDll.dll", EntryPoint = "?GetInstance@CNativeDll@@SAPEAXXZ")] private static extern IntPtr GetInstance(); [DllImport("x64/Debug/NativeDll.dll", EntryPoint = "?GrayScale@CNativeDll@@SAXPEAXPEAEHHHPEAPEAE@Z")] private static extern void GrayScale(IntPtr instance, IntPtr pSrc, int channel, int width, int height, ref IntPtr pDst);

测试时,将

NativeDllTest.grayScale(srcBmpData.Scan0, 3, srcBitmap.Width, srcBmpData.Height, ref dstPtr); // static 方法调用

修改为

IntPtr instance = GetInstance(); // 可以作为全局对象,避免c++中反复构造、释放 GrayScale(instance, srcBmpData.Scan0, 3, srcBitmap.Width, srcBmpData.Height, ref dstPtr);

实测,可以正常运行。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有