C++/CLI 初探






4.83/5 (106投票s)
2004年4月28日
5分钟阅读

591789
简要介绍 C++/CLI 的新语法,以及它相比旧 MC++ 语法有何改进。
引言
当微软在 VS.NET 7 中推出托管 C++ 扩展时,C++ 程序员的反应不一。虽然大多数人都很高兴能够继续使用 C++,但几乎所有人都对托管 C++ 所提供的丑陋而扭曲的语法感到不满。微软显然非常认真地对待收到的反馈,并决定 MC++ 语法不会取得巨大成功。
2003 年 10 月 6 日,ECMA 宣布成立一个新工作组,负责开发一套标准的语言扩展,以在 ISO 标准 C++ 编程语言和通用语言基础设施 (CLI) 之间建立绑定。同时还公布,这套新的语言扩展将被称为 C++/CLI 标准,VC++ 编译器将从 Whidbey 版本(VS.NET 2005)开始支持该标准。
旧语法的弊端
- 丑陋扭曲的语法和文法 - 那些双下划线并不赏心悦目。
- 二等 CLI 支持 - 与 C# 和 VB.NET 相比,MC++ 使用了 contorted 的变通方法来提供 CLI 支持,例如,它没有 for-each 构造来枚举 .NET 集合。
- C++ 和 .NET 集成不佳 - 你无法在 CLI 类型上使用 C++ 特性(如模板),也无法在 C++ 类型上使用 CLI 特性(如垃圾回收)。
- 令人困惑的指针用法 - 非托管 C++ 指针和托管引用指针都使用了相同的以
*
为基础的语法,这非常令人困惑,因为__gc
指针在性质和行为上与非托管指针完全不同。 - MC++ 编译器无法生成可验证的代码
C++/CLI 为我们带来了什么?
- 优雅的语法和文法 - 这让 C++ 开发者在编写托管代码时感到自然,并允许从非托管编码平滑过渡到托管编码。那些丑陋的双下划线现在不见了。
- 一流的 CLI 支持 - 属性、垃圾回收和泛型等 CLI 特性都得到直接支持。更重要的是,C++/CLI 允许我们对本地非托管类也使用这些特性。
- 一流的 C++ 支持 - 模板和确定性析构函数等 C++ 特性可用于托管和非托管类。实际上,C++/CLI 是唯一一个你可以*似乎*在堆栈或本地 C++ 堆上声明 .NET 类型的 .NET 语言。
- 弥合 .NET 和 C++ 之间的鸿沟 - C++ 程序员在处理 BCL 时不会感到无所适从。
- 由 C++/CLI 编译器生成的可执行文件现在完全可验证。
Hello World
using namespace System;
void _tmain()
{
Console::WriteLine("Hello World");
}
嗯,这看起来与旧语法差别不大,只是现在你不需要添加对 mscorlib.dll 的引用,因为 Whidbey 编译器在用 /clr(现在默认为 /clr:newSyntax)编译时会隐式引用它。
句柄
旧语法中一个主要的困惑是我们对非托管指针和托管引用都使用了 * 标点符号。在 C++/CLI 中,微软引入了句柄的概念。
void _tmain()
{
//The ^ punctuator represents a handle
String^ str = "Hello World";
Console::WriteLine(str);
}
^
标点符号(发音为 cap)表示对托管对象的句柄。根据 CLI 规范,句柄是托管对象的引用。句柄是 MC++ 语法中 __gc
指针的新语法等价物。句柄不应与指针混淆,其性质与指针完全不同。
句柄与指针有何不同?
- 指针使用
*
标点符号表示,而句柄使用^
标点符号表示。 - 句柄是对托管堆上对象的托管引用,指针只是指向内存地址。
- 指针是稳定的,GC 循环不会影响它们,句柄可能会根据 GC 和内存压缩指向不同的内存位置。
- 对于指针,程序员必须显式
delete
,否则就会出现内存泄漏。对于句柄,delete
是可选的。 - 句柄是类型安全的,而指针绝对不是。你不能将句柄强制转换为
void^
。 - 就像
new
返回一个指针一样,gcnew
返回一个句柄。
实例化 CLR 对象
void _tmain()
{
String^ str = gcnew String("Hello World");
Object^ o1 = gcnew Object();
Console::WriteLine(str);
}
gcnew
关键字用于实例化 CLR 对象,它返回对 CLR 堆上对象的句柄。关于 gcnew
的好处是,它让我们能够轻松区分托管和非托管的实例化。
基本上,gcnew
关键字和 ^
运算符提供了访问 BCL 所需的一切。但显然你需要创建和声明自己的托管类和接口。
声明类型
CLR 类型前面有一个形容词,描述了该类型的种类。以下是 C++/CLI 中类型声明的示例:
- CLR 类型
- 引用类型
ref class RefClass{...};
ref struct RefClass{...};
- 值类型
value class ValClass{...};
value struct ValClass{...};
- 接口
interface class IType{...};
interface struct IType{...};
- 枚举
enum class Color{...};
enum struct Color{...};
- 引用类型
- 原生类型
class Native{...};
struct Native{...};
using namespace System;
interface class IDog
{
void Bark();
};
ref class Dog : IDog
{
public:
void Bark()
{
Console::WriteLine("Bow wow wow");
}
};
void _tmain()
{
Dog^ d = gcnew Dog();
d->Bark();
}
这样,现在的语法就比旧语法整洁多了,旧语法中上述代码会充斥着诸如 __gc
和 __interface
等双下划线关键字。
装箱/拆箱
装箱是隐式的(太棒了!)且类型安全的。执行位复制,并在 CLR 堆上创建一个 Object
。拆箱是显式的——只需进行 reinterpret_cast
然后解引用。
void _tmain()
{
int z = 44;
Object^ o = z; //implicit boxing
int y = *reinterpret_cast<int^>(o); //unboxing
Console::WriteLine("{0} {1} {2}",o,z,y);
z = 66;
Console::WriteLine("{0} {1} {2}",o,z,y);
}
// Output
// 44 44 44
// 44 66 44
Object
o
是一个装箱副本,实际上并不引用值类型 int
,这从第二个 Console::WriteLine
的输出可以明显看出。
当你装箱一个值类型时,返回的对象会记住原始值类型。
void _tmain()
{
int z = 44;
float f = 33.567;
Object^ o1 = z;
Object^ o2 = f;
Console::WriteLine(o1->GetType());
Console::WriteLine(o2->GetType());
}
// Output
// System.Int32
// System.Single
因此,你不能尝试拆箱为不同的类型。
void _tmain()
{
int z = 44;
float f = 33.567;
Object^ o1 = z;
Object^ o2 = f;
int y = *reinterpret_cast<int^>(o2);//System.InvalidCastException
float g = *reinterpret_cast<float^>(o1);//System.InvalidCastException
}
如果你尝试这样做,你将得到一个 System.InvalidCastException
。简直是完美的类型安全!如果你查看生成的 IL,你会看到 MSIL box
指令正在起作用。例如:
void Box2()
{
float y=45;
Object^ o1 = y;
}
被编译为:
.maxstack 1
.locals (float32 V_0, object V_1)
ldnull
stloc.1
ldc.r4 45.
stloc.0
ldloc.0
box [mscorlib]System.Single
stloc.1
ret
根据 MSIL 文档,“box 指令将 ‘原始’ valueType(未装箱的值类型)转换为 Object 类型(类型 O)的实例。这是通过创建一个新对象并将数据从 valueType 复制到新分配的对象来完成的。”
深入阅读
- 使用 Visual C++ 2005 的现代语言功能编写更快的代码 [来自 MSDN]
- 选择 C++ 还是不 C++,这是个问题! [来自我的博客]
结论
好吧,那么当人们可以使用 C#、J# 和那个 VB 来编写 .NET 代码时,为什么还有人想使用 C++/CLI 呢?以下是我在 2003 年 12 月 Trivandrum 的 DevCon 2003 演讲中给出的四个原因。
- 将现有 C++ 代码编译为 IL (/clr 神奇)
- 确定性析构
- 任何其他 CLI 语言都无法比拟的原生互操作支持
- MC++ 中那些下划线都消失了 ;-)