• C#调用C++系列一:简单传值


    因为去实习的时候有一个小任务是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也没有用。

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

    如果你跟我走,

    就会数我的脚印;

    如果我跟你走,

    就会看你的背影。

    上善若水,为而不争。
  • 相关阅读:
    python 基于gevent协程实现socket并发
    python asyncio
    python 池 协程
    python
    python 守护进程
    python 线程 threading模块
    python 安装Django失败处理
    python 队列
    python 锁
    继承,抽象类,多态,封装
  • 原文地址:https://www.cnblogs.com/Bearoom/p/11721768.html
Copyright © 2020-2023  润新知