C++ 调用 C# - C++/CLI 方案
C++/CLI 方案 是通过托管 C++ 做中间层,来转发 C# 和 C++ 之间的调用和数据传递。
这个写法,C# 不用做任何特殊的处理,正常写就可以。
C++/CLI 层
新建一个 C++/CLI 项目,e.g. MedicalDbAccessWrapper,添加对 C# 项目的引用,
注意,输出目录最好就是原生 C++ 项目的输出目录,原生 C++ 项目可以直接调用。
托管 C++ 头文件,里面处理对 C# 的调用。
托管 C++ 中,引用类型后面会跟一个 ^
,命名空间和静态方法的调用使用 ::
MedicalDbAccessWrapper.h
// MedicalDbAccessWrapper.h
#pragma once
// 里面有很多测试代码
using namespace System;
using namespace System::Collections::Generic;
using namespace MedicalDbAccess;
using namespace MedicalDbAccess::Models;
using namespace MedicalDbAccess::Wrapper;
namespace MedicalDbAccessWrapper {
public ref class DbAccessWraper
{
public:
String^ Combine(String^ str, int num)
{
return Class1::Run();
}
String^ Run()
{
return Class1::Run();
}
Patient^ GetPatient(String^ uid)
{
return PatientWrapper::GetPatient(uid);
}
IList<Patient^>^ GetPatients()
{
return PatientWrapper::GetPatients();
}
};
}
MedicalDbAccessWrapper_Native.h
原生 C++ 头文件,用于处理 C++ 的导出
// MedicalDbAccessWrapper_Native.h
#pragma once
#ifdef MEDICALDBACCESSWRAPPER_EXPORTS
#define MEDICALDBACCESSWRAPPER_API __declspec(dllexport)
#else
#define MEDICALDBACCESSWRAPPER_API __declspec(dllimport)
#endif
#include <string>
#include <vector>
// CPP 端定义的业务数据结构,对应 C# 的数据定义
struct PatientDto
{
std::string Uid;
std::string Name;
int Age;
};
MEDICALDBACCESSWRAPPER_API std::string RunAccess(std::string str, int value);
MEDICALDBACCESSWRAPPER_API PatientDto GetPatient(std::string uid);
MEDICALDBACCESSWRAPPER_API std::vector<PatientDto> GetPatients();
MedicalDbAccessWrapper.cpp
CPP 源文件,用于实现要导出的函数,里面完成中托管数据类型对原生 C++ 类型的转换
// MedicalDbAccessWrapper.cpp
#include "pch.h"
#define MEDICALDBACCESSWRAPPER_EXPORTS // 定义为导出逻辑,而不是导入
#include <msclr/marshal_cppstd.h> // 必须在自己的 Wrapper.h 之前定义
#include "MedicalDbAccessWrapper.h"
#include "MedicalDbAccessWrapper_Native.h"
PatientDto ConvertToNativePatient(Patient^ patient)
{
PatientDto nativePatient;
// nativePatient.Id = patient->Id;
nativePatient.Uid = msclr::interop::marshal_as<std::string>(patient->Uid);
nativePatient.Name = msclr::interop::marshal_as<std::string>(patient->Name);
nativePatient.Age = patient->Age;
return nativePatient;
}
std::vector<PatientDto> ConvertToNativePatientList(IList<Patient^>^ patientList)
{
std::vector<PatientDto> nativePatients;
// 遍历每个 Patient 并转换为 NativePatient
for each (Patient ^ patient in patientList)
{
PatientDto nativePatient;
// nativePatient.Id = patient->Id;
nativePatient.Uid = msclr::interop::marshal_as<std::string>(patient->Uid);
nativePatient.Name = msclr::interop::marshal_as<std::string>(patient->Name);
nativePatient.Age = patient->Age;
// 将转换后的对象添加到原生列表
nativePatients.push_back(nativePatient);
}
return nativePatients;
}
std::string RunAccess(std::string str, int value)
{
MedicalDbAccessWrapper::DbAccessWraper wrapper;
String^ result = wrapper.Run();
std::string stdstr = msclr::interop::marshal_as<std::string>(result);
// return stdstr.c_str();
return stdstr;
}
PatientDto GetPatient(std::string uid)
{
MedicalDbAccessWrapper::DbAccessWraper wrapper;
String^ uid2 = msclr::interop::marshal_as<String^>(uid);
Patient^ patient = wrapper.GetPatient(uid2);
return ConvertToNativePatient(patient);
}
std::vector<PatientDto> GetPatients()
{
MedicalDbAccessWrapper::DbAccessWraper wrapper;
auto list = wrapper.GetPatients();
return ConvertToNativePatientList(list);
}
原生 C++ 端调用
原生 C++ 端,只要引用 MedicalDbAccessWrapper_Native.h
头文件,就可以直接调用里面导出的函数了。
为此,需要添加对 MedicalDbAccessWrapper
的引用,方式如下:
-
项目->属性->配置属性->VC++ 目录-> 在 "包含目录" 里添加头文件
MedicalDbAccessWrapper_Native.h
所在的目录 -
项目->属性->配置属性->VC++ 目录-> 在 "库目录" 里添加
MedicalDbAccessWrapper.lib
所在的目录 -
项目->属性->配置属性->链接器->输入-> 在 "附加依赖项" 里添加
MedicalDbAccessWrapper.lib
(若有多个 lib 则以空格隔开)
调用就比较简单了,引入头文件之后,就可以直接调用了。
// VisitByCli.h
#pragma once
#include <windows.h>
#include <iostream>
#include "MedicalDbAccessWrapper_Native.h"
class VisitByCli
{
public:
int run();
};
// VisitByCli.cpp
#include "VisitByCli.h"
int VisitByCli::run()
{
std::cout << "Hello CLI!\n";
auto result = RunAccess("", 1);
auto patient = GetPatient("P_112827");
auto patients = GetPatients();
return EXIT_SUCCESS;
}
遗留问题
Wrapper 中间层和 C# 所有的 DLL,都要放在和原生 C++ 执行文件一个目录,dll 会显得非常混乱。
想把这堆 DLL,或者至少 C# 的所有 DLL,都一个放在一个子文件夹中,还没有找到方法。
当时,使用 LoadLibrary 动态加载的方式是可以的,但是这样就必须手动获取函数地址,然后还要定义函数签名。
不如直接引用MedicalDbAccessWrapper_Native.h
头文件来得方便。