当前位置: 首页 > news >正文

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

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

1、简单库导入

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

1.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”。
在这里插入图片描述

1.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项目和测试项目保证一致即可。

2、复杂库

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

2.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 < height; i++) {
            cv::Mat rgbRow(1, width, CV_8UC3, pSrc);
            cv::Mat grayRow(1, width, CV_8UC1, gray.ptr<char>(i));
            cv::cvtColor(rgbRow, grayRow, cv::COLOR_BGR2GRAY);
            pSrc += inStride; // 越过间隙部分
        }
    }
}

BitMap的内存数据要求四字节对齐。因此,不论彩色图还是灰度图,对于图像的一行,其stride并不等于width*channel, 图像处理时要越过后面的间隙部分。

导出符号如下,其中有c++类生成的两个默认赋值函数。
在这里插入图片描述

2.2 winform 测试代码

首先c#封装导入的库函数,对应导入函数为 void grayScale(IntPtr pSrc, int channel, int width, int height, ref IntPtr pDst),c/c++指针对应c#为IntPtr指针的指针可对应ref IntPtr.

灰度化函数原型为void grayScale(Bitmap srcBitmap, out Bitmap dstBitmap),代码如下:

[DllImport("x64/Debug/NativeDll.dll", EntryPoint = "?grayScale@CNativeDll@@SAXPEAEHHHPEAPEAE@Z")]
private static extern void grayScale(IntPtr pSrc, int channel, int width, int height, ref IntPtr pDst);

public void grayScale(Bitmap srcBitmap, out Bitmap dstBitmap)
{
    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 < palette.Entries.Length; i++) {
        palette.Entries[i] = Color.FromArgb(i, i, i);
    }
    resBitmap.Palette = palette;

    dstBitmap = resBitmap;
}

灰度化按钮测试部分代码

public partial class OpencvForm : Form
{
    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();
    }  

2.3 测试结果

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

3、修改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#中线导入构造函数,返回实例化的对象指针,之后调用功能函数时加上传入指针对象即可。

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

3.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);
};

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

3.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); 

实测,可以正常运行。

相关文章:

  • DS1302 / DS1307 不起振可能是寄存器配置原因
  • 大数据讲课笔记1.7 软件包管理器RPM与yum
  • Python数据类型:序列(列表list、元组tuple、字符串str)
  • 解决VueCropper导致的后端接收文件后缀名为blob的问题
  • [Codeforces] number theory (R1600) Part.11
  • 基于JAVA火车订票系统计算机毕业设计源码+数据库+lw文档+系统+部署
  • 【CSDN:国庆活动】——blink动态里的学习成长
  • SpringBoot+Vue项目计算机等级考试报名系统
  • 【Flink 实战系列】Flink 消费多个 Topic 数据利用侧流输出完成分流功能
  • 【前端工程化】理解和配置process.env.NODE_ENV,项目中的环境变量到底是个啥
  • CVPR 2022 Oral 大连理工提出的SCI 快速、超强的低光照图像增强方法 亲测效果
  • cuda remove
  • CSS进阶篇——更多选择器 (selectors)
  • 嵌入式-ESP32
  • matplotlib绘制直方图,饼图,散点图,气泡图,箱型图,雷达图
  • CentOS 7 防火墙操作
  • MySQL的数据类型
  • Python中eval与exec的使用及区别
  • springboot_database项目介绍
  • Vue全家桶实现一个Web App
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 前端面试总结(at, md)
  • 原生js练习题---第五课
  • 正则表达式小结
  • Prometheus VS InfluxDB
  • ​Linux·i2c驱动架构​
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (六)激光线扫描-三维重建
  • (区间dp) (经典例题) 石子合并
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (五)MySQL的备份及恢复
  • (一)kafka实战——kafka源码编译启动
  • (一)Thymeleaf用法——Thymeleaf简介
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • .NET BackgroundWorker
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .Net程序帮助文档制作
  • .net用HTML开发怎么调试,如何使用ASP.NET MVC在调试中查看控制器生成的html?
  • @ComponentScan比较
  • @RequestParam,@RequestBody和@PathVariable 区别
  • [c#基础]DataTable的Select方法
  • [C/C++] -- 二叉树
  • [C++参考]拷贝构造函数的参数必须是引用类型
  • [CareerCup] 2.1 Remove Duplicates from Unsorted List 移除无序链表中的重复项
  • [Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated c
  • [halcon案例2] 足球场的提取和射影变换
  • [IDF]摩斯密码
  • [javaee基础] 常见的javaweb笔试选择题含答案
  • [LeetCode] Wildcard Matching
  • [Python进阶] 消息框、弹窗:pywin32
  • [Python设计模式] 第27章 正则表达式——解释器模式
  • [respberry pi3][suse] 配置docker
  • [SoftGrid 系列] Microsoft SoftGrid Server 安装篇
  • [Spring Boot 3] 整合NoSQL与构建RESTful服务