C++/CLI——1简介
如果你是.net程序员,不免会用到C++/C写的库。对于简单的调用,可以直接使用DllImport来完成就可以,详情可参考C#调用C/C++从零深入讲解。但是对于复杂的C++类和对象,尤其是类似于OCC的大型C++项目,DllImport可能不够方便,这就要引出C++/CLI方式来实现C#与C++/C库的交互。C++/CLI常用的5中场景有:
- 在.net中静态调用WindowApi或者DLL。
通常可经过 DllImport 属性包装出函数来调用
- 用托管C++包装现有的DLL,供C#调用
托管C++代码可以直接引用原有的头文件,直接调用非托管函数,而不需要声明。这样,既减少了工作量,又避免引入错误。缺点是,这种方法会增加一个DLL。要注意的是托管字符串和非托管字符串是有区别的,并需要转换(特别要注意的Unicode字符串和多字节字符串的转换)。
- 现有C++源代码,包装后供C#调用
C++的源代码,实际上可以直接编译成托管代码
- 在托管C++代码中混合托管和非托管代码
只要从#pragma unmanaged编译指示开始的程序,一率编译成非托管代码;要想恢复成托管代码,只要使用#pragma managed就可以了
-
不要DLL,直接把C++源代码与C#源代码一起编译成一个单独的Assembly
声明类
ref class Animal
{
public:int legs;void SetName(String^ name){this->name = name;}String^ GetName(){return name;}
private:String^ name;
};
- 声明类时,加上了
ref
关键字,该类就变成了托管类,是可以被gc来管理的。加上了ref的类被称为引用类型
,这是由于变量不实际包含对象,而是包含指向对象内存位置的指针,也可以称之为句柄
,引用对象必须分配在堆上。 - 可以使用
value
关键字来声明类,此时该类便是值类型,直接分配在栈上,变量本身包含对象本身。 - 如果句柄未指向任何对象,则将句柄赋值为
nullptr
,在使用句柄前必须检查句柄是否为空,否则会在运行时崩溃if(scheme == nullptr)
。
#include "pch.h"
using namespace System;ref class AnimalRef
{
public:int legs;void SetName(String^ name){this->name = name;}String^ GetName(){return name;}
private:String^ name;};value class AnimalValue
{
public:int legs;void SetName(String^ name){this->name = name;}String^ GetName(){return name;}
private:String^ name;};int main(array<System::String^>^ args)
{AnimalRef cat;cat.SetName("mm");cat.legs = 4;Console::Write("cat:");Console::WriteLine(cat.GetName());AnimalValue dog;dog.SetName("xx");dog.legs = 4;Console::Write("dog:");Console::WriteLine(dog.GetName());Console::WriteLine(sizeof(dog));return 0;
}
基本数据类型
类型 | 说明 |
---|---|
bool | |
char,__int8 | 单字节,一般用于容纳ASCII |
short,__int16 | 整数 |
int,__int32 | 整数 |
long | 整数,许多编译器中是int的两倍 |
long long ,__int64 | 整数 |
float | 浮点 |
double | 双精度 |
wchar_t | 宽字符或多字符 |
在标准C++中,基本数据类的大小是不固定的,如int可能是4字节,也可能是8,或者其他,这是根据运行平台来决定的,但是在c++/cli中基本数据类型的大小是固定的。
句柄和指针
在标准C++中,指针容纳的是一个变量的内存地址,通过指针可以间接引用变量,但是C++/cli中,运行时会管理内存,所以它会将内存的东西移来移去以最大化的利用内容空间,这就意味着对象不会总在一个位置待着,这时候,指针的地址会过期。所以C++/CLI中没有指针的概念而是使用句柄
来包含变量的地址,运行时会自动更新这个地址。
声明句柄的方式就是在变量名前面加上^
符号,而且一般使用gcnew
操作符号来创建对象并获取它的句柄。
int main(array<System::String^>^ args)
{AnimalValue^ cat = gcnew AnimalValue();cat->legs = 4;cat->SetName("mm");Console::WriteLine(cat->GetName());array<int>^ arr = gcnew array<int>(10);arr[0] = 12;Console::WriteLine(arr[0]);return 0;
}
强制类型转换
可以使用以下几种方式进行类型转换:
- (),例如:
(float)7
- static_cast<>:常用
- const_cast<>:配合指针使用,添加或删除变量的常量限定
- dynamic_cast<>:在继承层次中使用
- safe_cast<>:类似于dynamic_cast<>,转换失败会报异常
- reinterpret_cast<>:将指针转换成其他类型指针
尖括号中是目标类型
int a = 10;
double b;
b = static_cast<double>(a);