现在比较流行C#与C++融合:C#做GUI,开发效率高,C++做算法运算,运行效率高,二者兼得。但是C++与C#必然存在数据交互,C#与C++dll的数据交互从来都是一个让人头疼的问题。C#传图像、数据给C++ dll使用的教程网上已经比较多了,但是C++ dll传图像给C#使用的例程较少,本文介绍一种通过回调的方式来实现C++ dll传图像给C#使用的方法。
OpenCVLib.h
enum ImageType
{
Mono8 = 0,
RGB8 = 1
};
typedef int(_stdcall *CallbackFunc)(unsigned char* ImgData, int Width, int Height, ImageType eType);
CallbackFunc g_callbackfunc = NULL;
void RegisterCallBack(CallbackFunc func);
void ReadImage(const char* ImagePath);
OpenCVLib.cpp
#include "pch.h"
#include "framework.h"
#include <exception>
#include <vector>
#include "OpenCVLib.h"
#include "opencv.hpp"
using namespace std;
using namespace cv;
void _stdcall RegisterCallBack(CallbackFunc func)
{
if (NULL != func)
{
g_callbackfunc = func;
}
}
void _stdcall ReadImage(const char* ImagePath)
{
try
{
Mat src = imread(ImagePath);
ImageType eType = Mono8;
int iChannels = src.channels();
switch (iChannels)
{
case 1:
eType = Mono8;
break;
case 3:
eType = RGB8;
break;
default:
break;
}
if (NULL != g_callbackfunc)
{
g_callbackfunc(src.data, src.cols, src.rows, eType);
}
}
catch (Exception* e)
{
throw(e->what());
}
}
Form1.cs
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ImageCallback
{
public partial class Form1 : Form
{
public enum ImageType
{
Mono8 = 0,
RGB8 = 1
};
public delegate int CallbackFunc(IntPtr ImgData, int Width, int Height, ImageType eType);
private CallbackFunc _callbackfunc;
[DllImport("OpenCVLib.dll", CallingConvention = CallingConvention.StdCall)]
private extern static void RegisterCallBack(CallbackFunc func);
[DllImport("OpenCVLib.dll", CallingConvention = CallingConvention.StdCall)]
private extern static void ReadImage(string ImagePath);
public Form1()
{
InitializeComponent();
}
private int ShowImage(IntPtr ImgData, int Width, int Height, ImageType eType)
{
try
{
switch (eType)
{
case ImageType.Mono8:
Mat src = new Mat(Height, Width, MatType.CV_8UC1, ImgData);
ImgpictureBox.Image = BitmapConverter.ToBitmap(src);
break;
case ImageType.RGB8:
Mat SrcRGB = new Mat(Height, Width, MatType.CV_8UC3, ImgData);
ImgpictureBox.Image = BitmapConverter.ToBitmap(SrcRGB);
break;
default:
throw new Exception("Image type error.");
}
return 0;
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
return -1;
}
}
private void Form1_Load(object sender, EventArgs e)
{
_callbackfunc = new CallbackFunc(ShowImage);
RegisterCallBack(_callbackfunc);
}
private void PathSelectbtn_Click(object sender, EventArgs e)
{
OpenFileDialog file = new OpenFileDialog();
file.InitialDirectory = ".";
file.Filter = "所有文件(*.*)|*.*";
file.ShowDialog();
if (string.Empty != file.FileName)
{
ImgPathtextBox.Text = file.FileName;
}
}
private void ReadImgbtn_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(ImgPathtextBox.Text))
{
MessageBox.Show("请先正确选择图像路径.");
}
else
{
ReadImage(ImgPathtextBox.Text);
}
}
}
}
C#界面运行效果如下: 以下来自网络的一段_cdecl和__stdcall的解释,必须牢记:
- __cdecl
即所谓的C调用规则,按从右至左的顺序压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的。返回值在EAX中。因此,对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname。 2. __stdcall
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,切记:函数自己在退出时清空堆栈,返回值在EAX中。 __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。如函数int func(int a, double b)的修饰名是_func@12
所以,从C++ dll中回调函数给C#传递数据,必须由C#函数在使用完数据后(退出函数时)自己清空堆栈!所C++中的回调函数指针应该如下定义:
typedef int(_stdcall *CallbackFunc)(unsigned char* ImgData, int Width, int Height, ImageType eType);
|