Visual C++ .NET编程:托管C++概述

 
                                      
2000 6 月, Microsoft 推出了 "Microsoft.NET 下一代互联网软件和服务战略 " ,引起 IT 行业的广泛关注。 2000 9 月, Microsoft 在旧金山发布了 Enterprise 2000 。同月, Microsoft 原总裁兼首席执行官鲍尔默来到中国就 " 下一代互联网 " 的主题进行演讲,在中国掀起了一股 ".NET 旋风 " 2000 11 月, Microsoft Comdex 计算机大展上发布了 Visual Studio.NET 软件,并展示了其 .NET 发展战略的框架体系和开发工具的相关特性,全面加速了 Microsoft .NET 技术进军市场的步伐。

   Microsoft .NET 战略意味着: Microsoft 以及在 Microsoft 平台上的开发者将会制造服务,而不是制造软件。在未来几年之内, Microsoft 将陆续发布有关 .NET 的平台和工具,用于在因特网上开发 Web 服务。那时,工作在 .NET 上的用户、开发人员和 IT 工作人员都不再购买软件、安装软件和维护软件。取而代之的是,他们将定制服务,软件会自动安装,所有的维护和升级也会通过互联网进行。 "Microsoft.NET 代表了一个集合、一个环境、一个可以作为平台支持下一代 Internet 的可编程结构。 " 这就是鲍尔默对 .NET 的描述。

  作为 .NET 的最新特性组成部分, Microsoft .NET Framework 是一个用于构建,部署和运行 Web 服务及应用程序的平台。它为将现有投资与下一代应用程序和服务的集成提供了高产的,基于标准的,多语言环境,同时它还用于解决 Internet 级应用程序的部署和操作问题。 .NET 框架包含三个主要部分:通用语言运行时,一组层次化的统一的类库,及组件化版本的动态服务器主页 ( 称为 ASP.NET)

  用于开发 .NET Framework 的语言有 Visual C# VB.NET C++ 托管扩展 (Managed Extensions for C++) 。其中 C# 是开发 .NET 的元语言,而 C++ 托管扩展是在 C++ 基础上建立起来的,用来为 Visual C++ 程序员开发 .NET 框架应用程序而设计。为叙述方便,我们将 C++ 托管扩展就称之为 " 托管 C++"
为了帮助 C/C++ 以及 Visual C++ 程序员或爱好者快速使用托管 C++ 开发 .NET Framework 程序,我们将陆续推出相关的一系列文章。

  本篇 " 托管 C++ 概述 " 主要讲述了什么是托管 C++ 、开发 .NET Framework( 框架 ) 的项目类型以及与标准 C++ 之间的区别。

   1 、什么是托管 C++

  在回答这个问题,首先要搞清楚什么是 " 托管 "(Managed) 。托管是 .NET 的一个专门概念,它是融于通用语言运行时 (CLR) 中的一种新的编程理念,因此我们完全可以把 " 托管 " 视为 ".NET" 。那么什么是 " 通用语言运行时 " ?通用语言运行时是 .NET 框架应用程序的执行引挚。它提供了许多服务,其中包括:代码管理 ( 装入和执行 ) 、类型安全性验证、元数据 ( 高级类型信息 ) 访问、为管理对象管理内存、管理代码, COM 对象和预生成的 DLLs( 非管理代码和数据 ) 的交互操作性、对开发人员服务的支持等等。

  也就是说,使用托管 C++ 意味着,我们的代码可以被 CLR 所管理,并能开发出具有最新特性如垃圾自动收集、程序间相互访问等的 .NET 框架应用程序。

  由托管概念所引发的 C++ 应用程序包括托管代码、托管数据和托管类三个组成部分。  

   (1) 托管代码 . Net 环境提供了许多核心的运行 (RUNTIME) 服务,比如异常处理和安全策略。为了能使用这些服务,必须要给运行环境提供一些信息代码 ( 元数据 ) ,这种代码就是托管代码。所有的 C# VB.NET JScript.NET 默认时都是托管的,但 Visual C++ 默认时不是托管的,必须在编译器中使用命令行选项 (/CLR) 才能产生托管代码。

   (2) 托管数据 :与托管代码密切相关的是托管数据。托管数据是由公共语言运行的垃圾回收器进行分配和释放的数据。默认情况下, C# Visual Basic JScript.NET 数据是托管数据。不过,通过使用特殊的关键字, C# 数据可以被标记为非托管数据。 Visual C++ 数据在默认情况下是非托管数据,即使在使用 /CLR 开关时也不是托管的。

   (3) 托管类 :尽管 Visual C++ 数据在默认情况下是非托管数据,但是在使用 C++ 的托管扩展时,可以使用 "__gc" 关键字将类标记为托管类。就像该名称所显示的那样,它表示类实例的内存由垃圾回收器管理。另外,一个托管类也完全可以成为 .NET 框架的成员,由此可以带来的好处是,它可以与其他语言编写的类正确地进行相互操作,如托管的 C++ 类可以从 Visual Basic 类继承等。但同时也有一些限制,如托管类只能从一个基类继承等。需要说明的是,在托管 C++ 应用程序中既可使用托管类也可以使用非托管类。这里的非托管类不是指标准 C++ 类,而是使用托管 C++ 语言中的 __nogc 关键字的类。
2 、用托管 C++ 可以开发 .NET 框架的项目类型

  使用托管 C++ 应该是 C++ 程序员编写 .NET 框架应用程序最好的一种选择,通过集成在 Visual Studio.NET 开发环境的托管 C++ 向导,我们可以创建以下几种开发 .NET 框架的项目类型:

   (1) 托管 C++ 应用程序 : 用来创建一个支持托管扩展的单独 C++ 应用程序,使用它还可创建任何类型的应用程序,包括 .NET 框架客户应用程序。

   (2) 托管 C++ 类库 : 用来创建一个支持托管扩展的 C++DLL ,使用它可以生成一个能被 .NET 框架应用程序调用的托管类型的组件。

   (3) 托管 C++ 空项目:用来创建一个空的托管项目,该项目只含有支持托管扩展的正确编译和链接的开关选项。使用它能将一个已有的 C++ 源文件进入到一个托管环境中。

   (4) 托管 C++ Web 服务:用于创建两个项目,一个是 C++ 托管扩展项目,另一个是部署项目。

   3 、托管 C++ 与标准 C++ 的主要区别

  尽管托管 C++ 是从标准 C++ 建立而来的,但它与标准 C++ 有着本质上的区别,这主要体现在以下几个方面:

   (1) 广泛采用 " 名称空间 "(namespace)

  名称空间是类型的一种逻辑命名方案, .NET 使用该命名方案用于将类型按相关功能的逻辑类别进行分组,利用名称空间可以使开发人员更容易在代码中浏览和引用类型。当然,我们也可将名称空间理解成是一个 " 类库名 "

  尽管很早 Microsoft 就在 Visual C++ 中支持名称空间的编程方式,但是很少引起 Visual C++ 程序员的普遍关注。现在在托管 C++ 程序中,我们必须使用这一方式,即使用 #using using 关键字。例如下面的简单程序代码是在控制台上输出 "Hello World"
#using
using namespace System;

int main(void)
{
    Console::WriteLine(S"Hello World");
    return 0;
}

  代码中, #using 是用来将一个元数据文件输入到托管 C++ 程序中,这些文件可以是包含托管数据和结构的 MSIL (Microsoft intermediate language ,微软中间语言 ) 文件,如 DLL EXE OBJ 文件等。 mscorlib.dll .NET 框架的一个核心类库,包含主要的名称空间 System 。程序的第二行代码 "using namespace System;" 用来使用 System 名称空间。 System .NET 框架根名称空间,包含最基本的类型,如用于数据流的输入 / 输出的 System:: IO 等。

  在对托管 C++ 程序开发的不断深入,我们不久就会发现,许多类型的引用都要在程序的前面使用 #using using 来进行。

   (2) 基本数据类型的变化

  我们知道,标准 C++ 语言的数据类型是非常丰富的。而托管 C++ 的数据类型更加丰富,不仅包含了标准 C++ 中的数据类型,而且新增了 __int64 (64 位整型 ) Decimal(96 位十进制数 ) String*( 字符串类型 ) Object*( 对象类型 ) 等类型,表 1-1 列出它们各自数据类型。
类型描述
标准C++类型名
托管C++类型名
长度(位)
布尔型
bool
bool
8
字符型
char
signed char
8
无符号字符型
unsigned char
char
8
短整型
short [int]
short
16
无符号短整型
unsigned short [int]
unsigned short
16
整型
int
int 或 long
32
无符号整型
unsigned [int]
unsigned int 或 long
32
长整型
long [int]
long
32
无符号长整型
unsigned long [int]
unsigned long
32
单精度浮点型
float
float
32
双精度浮点型
double
double
64
长双精度浮点型
long double
--
64
Unicode 字符
--
wchar_t
16
64 位整型
--
__int64
64
无符号64位整型
--
unsigned __int64
64
96 位十进制值
--
Decimal
96
对象类型
--
Object*
32
字符串类型
--
String*
--


  需要注意的是, String Object 在定义一个变量时,注意要有星号 ("*") ,但这个变量不是指针变量,这与标准 C++ 的含义是不一样的。例如上面的代码可以改为:
#using
using namespace System;
int main(void)
{
String* hello = S"Hello World";
Console::WriteLine(hello);
return 0;
}
(3) 新增三个托管 C++ 类型: __gc class __value class __gc interface

  一个 __gc 类或结构意味着该类或结构的生命周期是由 .NET 开发平台自动管理及垃圾自动收集,用户不必自已去调用 delete 来删除。定义一个 __gc 类或结构和标准 C++ 基本相似,所不同的是在 class struct 前加上 __gc ,例如下面的代码:
__gc class G
{
public:
int k;
int sum(int);
};

G::sum(int i)
{
return i*(i + 1)/2;
}

int main()
{
G * g = new G;
Console::WriteLine(g->sum(4)); //
结果输出 10
return 0;
}

   但要注意:

   A. 一个 __gc 类不能从一个非托管类中继承,且不能包含从它派生的非托管类。但一个 __gc 类最多可以从一个托管类中继承。

   B. 一个 __gc 类不能定义成一个友元类或包含一个友元成员函数。所谓友元函数,是用来让外部函数访问类中的私有和保护类型成员。

   C. 一个 __gc 类不能声明或定义以及重载 new delete 操作以及不能包含 using 等声明。

   __value 类是用来使用具有短生命期的小型数据项,它不同于 __gc 类。 __gc 类数据分配在 CLR 堆中,而 __value 类对象是在运行栈或称为 NDP(.NET Developer Platform .NET 开发者平台 ) 堆中创建的,从而避免了垃圾回收器不断分配和释放空间而带来的开销。一个 __value 类可以声明成为一个局部变量、参数和返回值,也可嵌入到一个 __gc 类中或是作为一个静态变量或在 C++ 堆中分配的变量。例如下面的代码:
#using
using namespace System;
__value struct V { int i; };
__gc struct G { V v; }; //
嵌入到__gc类中
V f(V v) { // 定义一个全局函数,其值存储在运行栈中
v.i += 1; // 不影响原来形参v的值
return v; // 返回V结构类型的值
}
int main(void)
{
V v1 = {10}; // 在运行栈中声明并初始化
V v2 = f(v1); // 调用f函数,此时v1中的i为10,而v2中的i为11
G *pG = new G; // 为G实例分配堆空间
pG->v = v1; // pG的v中的i为10
pG->v.i += v2.i; // pG的v中的i为10+11=21
Console::WriteLine(v1.i); // 输出结果为10
Console::WriteLine(v2.i); // 输出结果为11
Console::WriteLine(pG->v.i); // 输出结果为21
return 0;
}

  除此之外,所有的 __gc 对象都是从类 System::Object 派生而来,因而能够很容易使用作用在 __gc 类中的集合和映射功能。然而 __value 类型并没有与这个基类所共享,因而不能直接将 __value 作为函数中的 Object* 实参。为了解决这个问题, .NET 允许我们使用 __box 关键字将一个 __value 类型视为一个 __gc 对象。此时 __value 类型被封装成一个 __gc 类桩子 (Stub) ,并被复制到 NDP 堆中。由于在托管 C++ 中, box 不具备隐式转换的功能,因此在转换时必须指明转换的类型。

  托管 C++ 中的 __gc 接口最能体现 COM 接口的思想,它的定义和声明是非常简单的,它除了关键字不同外,与一个 __gc 类的声明极为相似。例如下面的代码定义了一个接口 IMyBase ,其中包含了一个 f 的方法:
__gc __interface Ibase {
void f();
};

  需要说明的是,接口中所有的方法默认时都是纯虚的且都是公有的,我们不需要在方法之前使用 virtual 关键字或在方法之后加上 "= 0" 。其次,在一个 __gc 接口中不能包含数据成员以及静态成员,也不能包含任何类的声明。下面举一个示例来说明 __gc 接口的使用:
#using
using namespace System;

__gc __interface Ibase1 {
int f(int);
};
__gc __interface Ibase2 {
int f(int);
};
__gc struct C: Ibase1, Ibase2 {
int f(int i) { //
接口方法的实现
return 2*i-1;
};
};

int main(void){
C* c = new C;
Console::WriteLine((c -> f(1)).ToString()); // 输出结果为1
Console::WriteLine((__try_cast (c)->f(2)).ToString());
// 输出结果为3

Console::WriteLine((__try_cast (c)->f(3)).ToString());
// 输出结果为5

return 0;
}

  代码中, __try_cast 用来将某个对象转换成一个指定类型,并当类型转换失败时自动处理由此产生的异常。 ToString 用来将对象描述成一个字符串。
(4) 简化属性操作

  在 __gc 类中可以使用 .NET 的属性,这个属性简化了属性函数的调用操作,这与标准 C++ 中的属性不一样。在标准 C++ 中分别通过 get_ put_ 成员函数来设置或获取相关属性的值。现在,托管 C++ 中的属性操作就好比是对一个属性变量进行操作,例如下列代码:
#using
using namespace System;

__gc class G {
public:
__property int get_Size() {
Console::WriteLine(S"get_
属性");
return nSize;
};
__property void set_Size(int i) {
Console::WriteLine(S"set_属性");
nSize = i;
};
private:
int nSize;
};

int main() {
G * pG = new G;
pG->Size = 10; // 调用set_Size
int i = pG->Size; // 调用get_Size
Console::WriteLine(i);
}

  程序结果为:

    set_ 属性

    get_ 属性

    10

  需要说明的是,托管 C++ 使用 __property 关键字来定义一个属性的成员函数。从代码中可以看出设置和获取属性的成员函数名称中分别使用了 set_ get_ ,这样编译器会自动生成一个伪成员变量 Size ,这个变量名是 set_ get_ 成员函数后面的名称。注意不要再在 get_ 成员函数代码中使用这个伪成员变量 Size ,它会引起该函数的递归调用。

   (5) 托管 C++ 的委派

  在 C/C++ 中,一个函数的地址就是内存地址。这个地址不会带有任何其它附加信息,如函数的参数个数、参数类型、函数的返回值类型以及这个函数的调用规范等。总之, C/C++ 的回调函数不具备类型安全性。而 .NET 框架在回调函数的基础增加了提供类型安全的机制,称为委派。

  托管 C++ 的委派方法不像 C# 那么复杂,它简化了委派绝大部分的内部机制,因而使得它的使用变成非常简单容易。例如下面的代码:
#using
using namespace System;

__delegate int GetDayOfWeek(); //
委派方法的声明
__gc class MyCalendar
{
public:
MyCalendar() : m_nDayOfWeek(4) {}
int MyGetDayOfWeek() {
Console::WriteLine("非静态方法");
return m_nDayOfWeek;
}
static int MyStaticGetDayOfWeek() {
Console::WriteLine("静态方法");
return 6;
}
private:
int m_nDayOfWeek;
};

int main(void)
{
GetDayOfWeek * pGetDayOfWeek; // 声明委派类型变量
int nDayOfWeek;

// 将类的静态方法MyStaticGetDayOfWeek绑定成委派
pGetDayOfWeek = new GetDayOfWeek(0, &MyCalendar::MyStaticGetDayOfWeek);
nDayOfWeek = pGetDayOfWeek->Invoke(); // 委派的调用
Console::WriteLine(nDayOfWeek);

// 将一个类的实例绑定成委派
MyCalendar * pcal = new MyCalendar();
pGetDayOfWeek =
static_cast(Delegate::Combine(pGetDayOfWeek,
new GetDayOfWeek(pcal, &MyCalendar::MyGetDayOfWeek)));
nDayOfWeek = pGetDayOfWeek->Invoke();
Console::WriteLine(nDayOfWeek);

// 删除绑定委派的类实例
pGetDayOfWeek =
static_cast(Delegate::Remove(pGetDayOfWeek,
new GetDayOfWeek(pcal, &MyCalendar::MyGetDayOfWeek)));

return 0;
}

  输出结果是:

   静态方法
  
    6

   静态方法

   非静态方法

    4

   4 、结速语

  总之,使用托管 C++ C++ 程序员编写 .NET 框架应用程序最好的一种选择,在充分理解 .NET 框架基础上,避免了使用其他语言如 C# VB.NET 所带来的额外开销。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值