因为去实习的时候有一个小任务是C#想调用C++ opencv实现的一些处理,那我主要的想法就是将C++实现的OpenCV处理封装成dll库供C#调用,这里面还会涉及到一些托管和非托管的概念,我暂时的做法是非托管的方式,托管的方式好像是在编译C++的DLL库的时候打开托管的选项即可,这部分还不是很清楚,所以就记录下我暂时知道的一点做法。

先说下简单的调用吧,就是简单的调用C++的函数,传递一些常见的变量,不考虑将数据传回的情况。

情形一:C#调用C++封装的OpenCV加载显示图像

1、C++端:

C++端的任务是将传入的地址传给cv::imread()函数来加载图像,然后判断是否加载成功,成功就显示出来,这很简单的,就是新建一个C++的动态链接库即可。

C++端的头文件:

extern "C" __declspec(dllexport) int _stdcall load_cv_image(char* filename);

C++端的源文件:

#include "Cs_use_Cpp_ch1.h"
#include "opencv.hpp"
int load_cv_image(char *str)
{
cv::Mat src = cv::imread(str);
if (!src.data || src.empty())
{
return 0;
}
cv::imshow("src", src);
cv::waitKey(0);
return 1;
}

然后编译生成dll文件即可。

2、C#端:

C#端要先加载进dll文件,然后获取dll文件中的函数,然后调用该函数;这里我简单写了一个界面,就是一个按钮,点击后会弹出文件对话框,选择一个文件获取其地址,然后传给C++封装的函数即可,代码如下:

public partial class Form1 : Form
{
    //导入dll和函数
    [DllImport("Cs_use_Cpp_ch1.dll")]
    public static extern int load_cv_image(string a);
    public Form1()
    {
        InitializeComponent();
    }
    private void load_mat_Click(object sender, EventArgs e)
    {
        OpenFileDialog dialog = new OpenFileDialog();
        if (dialog.ShowDialog() == DialogResult.OK)
        {
            //获取文件路径
            string filename = dialog.FileName;
            //调用函数
            int result = load_cv_image(filename);
        }
    }
}

这里我发现有几个注意的地方,C++端的地址要用char*而不要用std::string,这样就对应了C#的string,然后就是在编译C#的时候要注意,C#的VS工程有个对于我而言很坑的地方在于C#的默认配置是any CPU,所以一定要记得把它改为跟C++的一样,x64要对应x64,x86对应x86,不然出错了都没反应过来是咋回事。

情形二:C#调用C++加载图像返回数据

这种情况的话是要用C++来加载图像然后将cv::Mat的数据传给C#。这个时候来分解下C++下的cv::Mat的数据结构,要构建一个cv::Mat的话,我们需要有一个数据指针,图像的长宽、图像步长和图像通道数,有了这几个变量就可以构建一幅图像,同样的,有这几个变量传回给C#的话,它也可以构造一幅图像了。

这种情况下有两种方式要注意的,第一种方式是将图像的数据指针通过参数传进来,然后直接对传进来的地址操作,另一种是以数据指针的方式返回,这两种方式的原理是一样的,但是实现起来有一点差别。但是不管是哪一种方式来使用都需要注意如果内存是在C++这边分配的话那一定要在堆上分配,不能在栈上分配,否则传回C#得时候内存已经被释放了,就无法完成数据传递了。

首先说将数据指针以函数返回值得形式传给指针吧。这种还比较好理解,首先像长宽、步长等参数要以引用得方式传进来,然后返回数据指针,这个是非常好理解的。我就直接上代码了,C++端是这样写的:

unsigned char* load_cv_Mat(char* filename, int &width, int &height, int &step, int channels)
{
IplImage *ptrSrc = NULL;
if (1 == channels)
{
ptrSrc = cvLoadImage(filename, CV_LOAD_IMAGE_GRAYSCALE);
}
else
{
ptrSrc = cvLoadImage(filename, CV_LOAD_IMAGE_COLOR);
} width = ptrSrc->width;
height = ptrSrc->height;
step = ptrSrc->widthStep; return (uchar*)ptrSrc->imageData;
}

然后C#端是这样调用的:

private void load_mat_cs_Click(object sender, EventArgs e)
{
    OpenFileDialog dialog = new OpenFileDialog();
    if (dialog.ShowDialog() == DialogResult.OK)
    {
        string filename = dialog.FileName;
        int width = 0;
        int height = 0;
        int step = 0;
        IntPtr dst = load_cv_Mat(filename, out width, 
            out height, out step, 3);
        Bitmap img = new Bitmap(width, height, step, 
            System.Drawing.Imaging.PixelFormat.Format24bppRgb,
            dst);
        pictureBox1.Image = img;
    }
}

这其实还是比较好理解的,接下来就来说下,数据指针如果是以指针参数的形式传进来的,这个时候C++端就写为:

int load_cv_Mat(char* filename, unsigned char* pInput, int &width, int &height, int &step, int channels)

如果是C++调用C++的话,那这样写是没有问题的,因为传进来的是一个数据指针,那就直接对传进来的地址操作,那么就直接改变了源数据,但可惜这是C#调用C++,如果C++端这么写的话,C#就不能简简单单的传IntPtr参数就好了,因为传进来之后实际上并没有改变C#原来的IntPtr,而是传进来一个中间变量,而对这个变量操作并不会直接作用于源数据。实际上,我在测试的时候,C++这边应该这样写:

int load_cv_Mat1(char* filename, unsigned char* &pInput, int &width, int &height, int &step, int channels)

区别就在于unsigned char* &pInput,加了这个符号,我的理解就是对于指针unsigned char*的引用,所以在C#端要使用的时候就要这样写:

[DllImport("Cs_use_Cpp_ch1.dll")]
public static extern int load_cv_Mat1(string path_str, 
    out IntPtr input, out int width, out int height, 
    out int step, int channels);

如果C++端不加上&的话,即使C#端加上了out也没有用。

好了,第一部分就写下这么两点吧,剩下的是后续的工作,后面再记录。

如果你跟我走,

就会数我的脚印;

如果我跟你走,

就会看你的背影。

C#调用C++系列一:简单传值的更多相关文章

  1. FrameBuffer系列 之 简单编程

    一.Linux的帧缓冲设备 帧缓冲(framebuffer)是 Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作.这种操作是抽象的 ...

  2. 调用链系列(3):如何从零开始捕获body和header

    拓展阅读:调用链系列(1):解读UAVStack中的贪吃蛇 调用链系列(2):轻调用链实现 在Java中,HTTP协议的请求/响应模型是由Servlet规范+Servlet容器(如Tomcat)实现的 ...

  3. RDLC报表系列(一) 简单的动态数据绑定和配置

    RDLC系列链接 RDLC报表系列(一) 简单的动态数据绑定和配置  RDLC报表系列(二) 行分组 RDLC报表系列(三) 总计和折叠 RDLC报表系列(四) 矩阵 RDLC报表系列(五) 简单的图 ...

  4. C#反射调用其它DLL的委托事件 传值

    C#反射调用其它DLL的委托事件 传值在插件式开发.我们要调用其它插件或模块的委托事件时.那么我们需要通过反射. 复制代码namespace Module2{ /// <summary> ...

  5. C#调用Python脚本的简单示例

    C#调用Python脚本的简单示例 分类:Python (2311)  (0)  举报  收藏 IronPython是一种在 .NET及 Mono上的 Python实现,由微软的 Jim Huguni ...

  6. Spring4.0系列9-websocket简单应用

    http://wiselyman.iteye.com/blog/2003336 ******************************************* Spring4.0系列1-新特性 ...

  7. JavaScript系列----数据类型以及传值和传引用

    1.简单数据类型 在JavaScript中简单数据类型分为5种.分别为 Undefined, Null,Boolean,Number,String. Undefined类型Undefined类型只有一 ...

  8. Java调用方法参数究竟是传值还是传址?

    之前阅读<Head First Java>的时候,记得里面有提到过,Java在调用方法,传递参数的时候,采用的是pass-by-copy的方法,传递一份内容的拷贝,即传值.举一个最简单的例 ...

  9. C#调用C++系列二:传结构体

    这一篇记录下C#调用C++的结构体的方式来使用OpenCV的数据格式,这里会有两种方式,第一种是C#传一个结构体和图像的路径给C++,然后C++将图像加载进来,再把传进来的结构体填满即可,第二种是C# ...

随机推荐

  1. Android的事件处理机制之基于监听的事件处理

    无论是桌面应用还是手机应用程序,面对用户的使用,经常需要处理的便是用户的各种动作,也就是需要为用户动作提供响应,这种为用户动作提供响应的机制就是事件处理. 而Android为我们提供了两套强大的响应机 ...

  2. Java基础学习总结(二)

    Java语言的特点: Java语言是简单的 Java语言是面向对象的 Java语言是跨平台(操作系统)的(即一次编写,到处运行) Java是高性能的 运行Java程序要安装和配置JDK jdk是什么? ...

  3. .NET配置问题

    Ext.NET MVC 配置问题总结       随着VS版本和.NET MVC版本.EF的版本的不断更新,虽然很多功能随着版本的提升而更完善,但对于旧版本开发的软件就有点悲催了,或许很多开发者都遇到 ...

  4. arduino中的serial .available()和serial.read()

    Serial.available() 的意思是:返回串口缓冲区中当前剩余的字符个数.一般用这个函数来判断串口的缓冲区有无数据,当Serial.available()>0时,说明串口接收到了数据, ...

  5. 会话控制——Cookie和Session

    Cookie简介 l  HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分中两次请求是否由一个客户端发出.这样的设计严重阻碍的Web程序的设计.如:在我们进行网购时,买了一条 ...

  6. 阿里云香港服务器IIS发布网站不成功解决方法

    刚刚弄好了一个阿里云上服务器,费老劲儿了.我买了一个香港的服务器,最低配置,专有网络,买着玩的,一个.win的域名,省的国内备案了. 遇到的问题是怎么也访问不了我IIS上发布的网站,我把我解决方法说下 ...

  7. WTM框架在开发过程中如何动态迁移表和创建表

    官方迁移方法:https://wtmdoc.walkingtec.cn/#/Data/Migration 但是在实际开发过程中使用Add-Migration 方法迁移会发现,把系统内置的表也全部带出来 ...

  8. 一百一十、SAP的OO-ALV之四,定义屏幕相关变量和逻辑流

    一.代码如下,定义相关变量 二.来带屏幕页面,双击STATUS_9000和USER_COMMAND_9000,自动生成相应代码 三.点击是 四.会自动生产关联的Includ文件 五.我们自己创建一个M ...

  9. 十三、JavaScript之跨多行的变量申明

    一.代码如下 二.运行效果 <!DOCTYPE html> <html> <meta http-equiv="Content-Type" conten ...

  10. 四十七、在SAP中,把功能区块整合成一个函数,通过调用函数的办法使代码简洁明了

    一.我们查看上一次的代码,非常之凌乱,大体可以分为以下这几个区块 二.我们把最后的2个部分,用函数的方式来写,写法如下: 三.执行程序,和之前一样 四.输出结果