以一个实例详细介绍C+ +/CLI封装原生C+ +库供.NET项目调用的步骤。
已得结论
在前面的文章《C+ +/CLI基本语法和最佳实践》中,已经得到了以下重要结论:
(1)托管类型(value struct / value class 和 ref struct / ref class)
- 不能定义非托管类型的字段。
- 可以定义非托管类型的C+ +普通指针类型。
- 可以使用C+ +的基本数据类型,因为在这些类型可以和C#数据类型自动转换。
- 托管类型的方法参数和返回值可以为非托管类型,但是无法传参。
(2)封装程序库供C#使用:
- 凡是要暴露给C#项目的接口,统一使用托管类型。
- 托管类型的指针成员变量、参数或返回值为非托管类型的方法,只能充当函数逻辑实现的辅助,无法暴露给C#项目(避免出错,最好不要使用!)
- 封装原生C+ +代码给C#项目使用:可以定义一个C+ +类指针成员变量,作为原生C+ +代码的统一访问入口;(推荐方法)
- 封装原生C+ +代码给C#项目使用:可在C+ +/CLI的方法内部使用原生C+ +代码中的静态方法,或实例化原生C+ +代码里面的类,从而使用既有原生C+ +的代码。
下面以一个完整实例详细介绍如何使用C+ +/CLI封装原生C+ +库供.NET项目调用。
制作原生C+ +库
这里使用动态链接库,设置预编译器定义MYDLL_EXPORTS,确保预编译阶段MYDLL_EXPORTS宏被替换为__declspec(dllexport)。
头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #pragma once #ifdef MYDLL_EXPORTS #define MYDLL_API __declspec(dllexport) #else #define MYDLL_API __declspec(dllimport) #endif
#include <vector> #include <string>
class MYDLL_API MyComputer { public: double Add(double a, double b); bool Divide(double a, double b, double& res); double Sum(const std::vector<double>& data); bool IsStringRight(std::string text); };
|
注意:MYDLL_API必须放在class关键字后面,易错!
源文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include "MyComputer.h"
double MyComputer::Add(double a, double b) { return a + b; }
bool MyComputer::Divide(double a, double b, double& res) { if (b == 0) return false; res = a / b; return true; }
double MyComputer::Sum(const std::vector<double>& data) { double res = 0.0; for (size_t i = 0; i < data.size(); i++) res += data[i]; return res; }
bool MyComputer::IsStringRight(std::string text) { return text == "LH"; }
|
使用C+ +/CLI封装
与原生C+ +项目一样,先设置附加包含路径(或者直接使用相对路径进行引用)、lib附加库目录以及lib输入库,确保项目可以找到原生C+ +的头文件和lib文件。
头文件
以下是C+ +/CLI封装库的头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #pragma once #include <msclr/marshal.h> #include <msclr/marshal_cppstd.h>
#include "MyComputer.h"
using namespace System; using namespace System::Collections::Generic;
namespace lh { public ref class MyComputerWrapper { private: MyComputer* pComputer;
public: MyComputerWrapper(); ~MyComputerWrapper();
Double Add(Double a, Double b); Boolean Divide(Double a, Double b, Double% res); Double Sum(List<Double>^ data); Boolean IsStringRight(String^ text); }; }
|
结合前面的重要结论:
- 暴露的接口只采用托管类型,不包含任何非托管类型成员,也没有参数或返回值为非托管类型的方法。
- 有一个MyComputer指针,MyComputer来自原生C+ +库,设置为私有,在构造函数中初始化,在析构函数中释放。在其他成员函数中通过该指针来访问原生C+ +代码。
- 这里的Double、Boolean也可以用double、bool替换,因为它们之间可以自动转换,但是不建议,大脑里的规则越少才越深刻!
源文件
以下是C+ +/CLI封装库的源文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <vector> #include "MyComputerWrapper.h"
using namespace lh;
MyComputerWrapper::MyComputerWrapper() { pComputer = new MyComputer(); }
MyComputerWrapper::~MyComputerWrapper() { delete pComputer; }
Double MyComputerWrapper::Add(Double a, Double b) { return pComputer->Add(a, b); }
Boolean MyComputerWrapper::Divide(Double a, Double b, Double% res) { double temp = 0.0; bool result = pComputer->Divide(a, b, temp); res = temp; return result; }
Double MyComputerWrapper::Sum(List<Double>^ data) { std::vector<double> vec; for (size_t i = 0; i < data->Count; i++) vec.push_back(data[i]); return pComputer->Sum(vec); }
Boolean MyComputerWrapper::IsStringRight(String^ text) { return pComputer->IsStringRight(msclr::interop::marshal_as<std::string>(text)); return true; }
|
几点需要重点说明:
- Double是值类型,通过Double%实现引用传参,可以修改这个值,在C#项目里会变成ref double类型参数。
- List^转std::vector类型,通过遍历赋值的方法实现。
- 方法的实现逻辑可以混用托管和非托管代码。
重点:数据转换
自动转换
以下类型可以相互转换:
在这个例子中,原生C+ +里面的类型是double,在C+ +/CLI方法实现中,我们直接把Double类型的值传给原生C+ +方法,不用转换。
msclr命名空间下的转换函数
- 注意:将#include<msclr/marshal.h>尽量前置,防止会引发IServiceProvider相关报错。
- std::string & System::String相互转换
marshal_cppstd.h头文件只提供std::string、std::wstring和System::String^相互转换的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <msclr/marshal_cppstd.h> #include <iostream> using namespace System; using namespace System::Collections::Generic;
int main() { std::string str1 = "Hello CLI"; String^ str2 = msclr::interop::marshal_as<String^>(str1); Console::WriteLine(str2);
String^ str3 = gcnew String("Hello C+ +"); std::string str4 = msclr::interop::marshal_as<std::string>(str3); std::cout << str4 << std::endl;
std::system("pause"); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <msclr/marshal.h> #include <iostream> using namespace System; using namespace System::Collections::Generic;
int main() { char* str5 = "Test String to Marshal"; String^ str6 = msclr::interop::marshal_as<String^>(str5); Console::WriteLine(str6);
std::system("pause"); return 0; }
|
手动转换
例如C#中的List与C+ +中的vector,可以通过遍历赋值的方式相互转换。
枚举转换时,也用同样的方式。
在C#项目中使用
最后再C#项目中使用C+ +/CLR库,与引用普通C#库别无二致!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using lh; using System; using System.Collections.Generic;
namespace UseInCS { internal class Program { static void Main(string[] args) { MyComputerWrapper computer = new MyComputerWrapper();
Console.WriteLine(computer.Add(12, 34));
double res = 0; if (computer.Divide(24, 12, ref res)) Console.WriteLine(res);
List<double> datas = new List<double>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Console.WriteLine(computer.Sum(datas));
Console.WriteLine(computer.IsStringRight("LH")); } } }
|