C++/CLI 方案 是通过托管 C++ 做中间层,来转发 C# 和 C++ 之间的调用和数据传递。
这个写法,C# 不用做任何特殊的处理,正常写就可以。
新建一个 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();
// 另外,也可以考虑使用导出的 class 的方式,对导出的方法进行封装class MEDICALDBACCESSWRAPPER_API MedicalDBAccessWrapper{public: std::string RunAccess(std::string str, int value); PatientDto GetPatient(std::string uid); 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 /*MedicalDBAccessWrapper::*/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 /*MedicalDBAccessWrapper::*/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> /*MedicalDBAccessWrapper::*/GetPatients(){ MedicalDbAccessWrapper::DbAccessWraper wrapper; auto list = wrapper.GetPatients(); return ConvertToNativePatientList(list);}
原生 C++ 端,只要引用 MedicalDbAccessWrapper_Native.h
头文件,就可以直接调用里面导出的函数了。
为此,需要添加对 MedicalDbAccessWrapper
的引用,方式如下:
MedicalDbAccessWrapper_Native.h
所在的目录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
头文件来得方便。
P/Invoke各种总结(十、C++调用C#代码的几种方法总结) - zhaotianff - 博客园
使用PInvoke互操作,让C#和C++愉快的交互优势互补 - 一线码农 - 博客园
原文链接: https://cloud.tencent.com/developer/article/2481585
本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。