开发者社区> zting科技> 正文

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

简介:
+关注继续查看
原文地址 
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所带来的额外开销。


本文转自Work Hard Work Smart博客园博客,原文链接:http://www.cnblogs.com/linlf03/archive/2011/11/20/2255937.html,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
.Net网络编程——服务端获取客户端连接
一,客户端服务端连接图示          其中,在客户端和服务端的socket中,都报存这连接的信息;客户端通过connect连接,服务端可以调用AcceptTcpClient来获取链接到服务端的客户端,每一个客户端和服务端的连接都对应这唯一一个Socket.
787 0
.NET中的异步编程- IO完“.NET技术”成端口以及FileStream.BeginRead
  写这个系列原本的想法是讨论一下.NET中异步编程风格的变化,特别是F#中的异步工作流以及未来的.NET 5.0中的基于任务的异步编程模型。但经过三篇文章后很多人对IO异步背后实现的原理以及为什么这样能提高性能很感兴趣。
670 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
30006 0
对C#.NET编程规范的个人见解
我们应该知道编程规范对于项目的生命周期多么重要,如果每个程序员写的代码都令其他人难以阅读、或者一个团队项目的代码却五花八门,这样的项目将会是怎么样的噩梦。
687 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
21143 0
098_《Delphi5高级编程丛书之四:COM、CORBA与Internet编程》
《Delphi5高级编程丛书之四:COM、CORBA与Internet编程》 Delphi 教程 系列书籍 (098) 《Delphi5高级编程丛书之四:COM、CORBA与Internet编程》 网友(邦)整理 EMail: shuaihj@163.
1329 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
23622 0
+关注
3550
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载