C#调用C++导出的dll之非托管的方式
1、宏定义导出关键字
#ifndef dll_import
#define dll_export __declspec(dllexport)
#else
#define dll_export __declspec(dllimport)
#endif
__declspec 是 Microsoft C++ 编译器特有的关键字,用来为变量、函数、类等添加 存储类属性 或 编译器指令,从而控制它们的行为或链接方式。我们上述的操作就是给它一个更显示的指令名称,方便我们去理解和使用。
2、定义导出的方法
我们在需要被导出的函数的声明前面加上dll_export:
'''demo.hpp'''
extern "C" dll_export int __stdcall Add(int a, int b);
'''demo.cpp'''
dll_export int __stdcall Add(int a, int b)
{
...
}
这里的__stdcall是一种调用的约定方式,这里我们指定了__stdcall的方式,因此在C#调用时,需要指定好__stdcall的方式:
[DllImport("demo.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int Add(int a, int b);
这里需要注意的是,如果使用的是P/Invoke的方式来对接,那么我们不能导出类,只能导出方法,如果我们写的是类,那么需要再封装一下,变成一个独立的方法给c#调用。(clr托管的方式,即可通过导出类的方式来给c#调用。)
3、关于对接中的一些变量问题
C#中的图像可以使用byte*,byte[] 来传,c++则用unsigned char* 来接。其中需要注意的是,接收byte*的数据时,我们需要在C++中手动进行对齐,我们可以计算step,计算方式如下:
int step = width * 3;
if(step % 4 != 0)
{
step = ((width * 3) / 4 ) + 1) * 4;
}
cv::Mat src(height, width, CV_8UC3, ImgPtr, step);
这样构建出来的mat才是正常的mat。其中这里的 × 3指的是三通道,如果接收的单通道的图,那么 × 1即可。
c++给c#传字符串时,可以使用const char *,c#那边接收可以用IntPtr来接收,然后使用Marshal.PtrToStringAnsi 将其转成string。
4、关于一些细节问题
c++的接口如果要返回字符串的话,尽可能的返回char* 类型,而不是string类型,因为在返回string的时候,有可能会导致c#与c++的入参不一致而出现乱码入参(虽然不知道为什么会这样)。
尽可能的指定__stdcall或者常见的还有__cdecl,然后我们就可以在C#中声明调用的方法时指定好同样的调用约定。
如果我们在方法中用了new char创建的变量,一定要记得写一个接口单独释放char* 指针,这个方法将会在c#中获得string后调用。
5、设置visual studio
将.exe全部设置成.dll