C#/C++ 混合编程

使用C++开发算法(全景拼接、人脸识别、超分辨率重建)/使用C#开发服务端业务逻辑和UI

简介

现工作中作由于 C++ 的 UI(MFCQT)开发界面比较难看,定制用户控件复杂且样式一般。而 C# 又不擅长于开发算法逻辑,效率不如 C++。所以现在大部分公司都会选用 C#/C++ 混合编程。

性能分析

使用 C# 做界面要比 C++ 高效的多,但是存在算法逻辑的时候由于性能问题不得不把部分模块交给 C++ 处理,C++ 可以使用高效的栈内存对象(CCalc),而 C# 所有对象只能放在托管堆中。测试 C# 调用 C++ 类库使用托管方式性能得到了一定程度的提升,但比起单纯的 C++ 项目,还是差了很多;测试 C# 调用 C++ 类库使用 DllImport Attribute 混合方式由非托管动态链接库效率与单独运行 C++ 相差无几。

基础代码

.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once

#ifdef TEST_EXPORTS
#define Test_API __declspec(dllexport)
#else
#define Test_API __declspec(dllimport)
#endif // TEST_RXPORTS

#ifdef __cplusplus
# define CEXTERN extern "C"
#else
# define CEXTERN
#endif

#define EXPORT_DLL CEXTERN Test_API

EXPORT_DLL int Add(int a, int b);

EXPORT_DLL char* FilePath(char* filePath);

.cpp

1
2
3
4
5
6
7
8
9
10
11
int Add(int a, int b)
{
return a + b;
}

char* FilePath(char* filePath)
{
char* resurlt;
resurlt = filePath;
return resurlt;
}

.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
private const string dllName = @"Dll.dll";

[DllImport(dllName, EntryPoint = "Add", CallingConvention = CallingConvention.Cdecl)]
public static extern int Add(int a, int b);

[DllImport(dllName, EntryPoint = "FilePath", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr FilePath(IntPtr filePath);

static void Main(string[] args)
{
int iAdd = Add(8, 12);
string strFile = Marshal.PtrToStringAnsi(FilePath(Marshal.StringToHGlobalAnsi(@"1.JPG")));
}

注意事项

引用方法乱码

使用 C++ 声明方法会导致引用是乱码,引用名称可以通过 Depends 查询。改为 C 声明后可正常引用方法,建议所有方法使用 C 声明,例如使用 C++ 需要引用方法:”?Add@@YAHHH@Z”,而使用 C 声明只需引用:”Add”。

报错:无法加载 DLL“xxx.dll”: 找不到指定的模块

推荐使用 Depends 工具检测缺少引用类库

报错:调用 Dll “试图加载格式不正确的程序。(异常来自 HRESULT:0x8007000B)

调用64位类库需要把项目改为64位
桌面端修改:属性 → 生成 → 目标平台设为:”Any Cpu”,取消勾选”首选32位”。
Web 端修改:工具 → 选项 → 项目和解决方案 → Web 项目 → 勾选:”对网站和项目使用 IIS Express 的 64 位版”

无法分配内存空间

报错:引发的异常: 0xC0000005: 执行位置 0x0000000000027DD4 时发生访问冲突。
c++ 以:应用程序(.exe) 的方式同样可以导出函数,最选择这样使用的原因是想核心函数即可以被其他程序以非动态链接库的方式调用,也可以单独使用控制台程序调用,导出的简单方法调用没有太大区别,但是在申请内存空间时会报错,代码不变,仅修改项目类型为:动态库(.dll),报错消失,经测试,目前没有办法解决,能搜到的文章都已没人会使用应用程序导出函数结束。
报错的分配内存方法例如:

1
2
char* chars = new char[1024];
char* chars = (char*)malloc(sizeof(char) * 1024);

回调函数

c# 以非动态链接库调用 c++ 后,c++ 以回调函数的方式返回给 c#。

.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LogCALLBACK(string log);

[DllImport(dllName, EntryPoint = "CallbackTest")]
public static extern void CallbackTest(LogCALLBACK log);

static void Main(string[] args)
{
logCALLBACK = Action_logCALLBACK;
CallbackTest(logCALLBACK);
}

private static void Action_logCALLBACK(string log)
{
Console.WriteLine(log);
}
.h
1
2
3
typedef void(*LogCALLBACK)(char* log);

EXPORT_DLL void CallbackTest(LogCALLBACK logCALLBACK);
.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CallbackTest(LogCALLBACK logCALLBACK)
{
for (size_t i = 0; i < 1000; i++)
{
string strLog = "hello";
char* log = new char[1024];
strcpy(log, strLog.c_str());
if (logCALLBACK != NULL)
{
logCALLBACK(log);
}
delete log;
}
}

函数中传递结构体与数组

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct MyStruct
{
int id;
float values[10];
};

EXPORT_DLL void ProcessArray(MyStruct* arr)
{
arr.id = 1;

for (int i = 0; i < 10; i++)
{
arr.values[i] *= 2.0f;
}
}
c
1
2
3
4
5
6
7
8
9
10
11
12
using System.Runtime.InteropServices;

struct MyStruct
{
public int id;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public float[] values;
}

[DllImport(dllName, EntryPoint = "ProcessArray")]
public static extern void ProcessArray(ref MyStruct arr);