托管C++、C++/CLI、CLR

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++还有下列好处:

  (1) 由于在同一个应用程序中,甚至是同一个文件中,我们可以同时使用托管C++和传统的非托管C++来编写,因而我们可以充分利用两种C++所带来的好处,并且可将代码和组件快速移植到.NET框架中。

  (2) 使用托管可以从任何一个.NET框架兼容语言中调用一个C++组件,也可调用非托管DLL、其它库以及类等。

  (3) 可以直接从非托管代码中访问.NET框架。

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

  为什么使用C++托管扩展

  微软既要考虑Visual C++的兼容性,又要让传统C++语言具备足够的能力开发.NET应用程序,于是在新版本的Visual C++中,引入了C++托管扩展。

  在整个Visual Studio开发套件中,微软为了迎合.NET应用程序开发模式的要求,几乎对每个工具都作了或大或小的改进。其中,VB的变革力度应用微软各种软件产品之最。但是太大的变革往往会带来兼容性问题,特别是新版本的VB宣称其只能开发托管的应用程序(也就是.NET应用程序),所以对开发人员来讲,这肯定意味着过去使用VB编写的代码在新版本VB上进行重新构造的难度会很大。VB以前就在版本兼容性方面有着不如人意的历史—— 在VB4、VB5、VB6之间进行升级,会让开发人员付出很多辛苦。现在,由于VB的体系进行了很大的改动,所以版本兼容性问题会更严重一些—— 笔者已经在微软几个官方讨论组中看到了一些开发人员和相关人士对此表示出来的担忧,并看到不止一个开发人员对新版本VB在兼容性方面存在问题提出质疑甚至是批评。

  作为微软开发套件中的另一位“元老”—— Visual C++,我们对其提供完整的.NET开发支持感到高兴的同时也同样担心它的版本兼容性问题—— Visual C++该不会也和VB一样,彻底与MFC和ATL分裂吧?答案是:不可能!

  这是一个令人振奋的回答,下面就让我们花一点时间来了解新版本Visual C++是怎样处理变革和向下兼容之间的关系的。

  另外,之所以微软对VB进行那样大的改革,是因为微软认为VB通常适用于快速应用程序领域,这些领域一般包含对效率要求不是很高的数据库前端应用程序或后端业务组件。当更为优秀的.NET框架发布之后,微软就为VB换了换“心脏”,以期大幅度增强VB的功能,使之成为快速开发.NET应用程序的主力军。而对于Visual C++这样一个在许多传统领域依然宝刀不老的工具,当然不能急躁冒进,将已有的功能丢弃。所以,在新版本的Visual C++中,采用了一种更为折衷的方法—— 扩展现有C++语言,让Visual C++在编写纯粹的.NET应用程序的同时,依然可以利用其成熟的技术进行未托管的应用程序的开发。

  C++托管扩展是一个对现行C++语言进行扩展的集合,这个集合可以帮助Visual C++的开发人员编写.NET Framework应用程序。由于是对语言做了扩展,而不是彻底去掉原先C++语言的功能,所以在托管扩展中,开发人员可以在同一个应用程序中混合使用传统未托管的代码和新型的托管的代码。这样做得到的一个直接好处是,应用程序既可以享受未托管的代码特性也可以享受托管的代码特性。对组件开发也是一样,传统组件可以很容易被包装(wrapper)成.NET 框架组件,充分保障已有工作的投资。

  在实际工作中,如果开发人员遇到下列开发需求,使用托管扩展将是最佳选择:

  ● 需要快速地将未托管的C++应用程序合并到.NET框架中

  对于以前开发的传统未托管的C++应用程序,因为开发人员可以在同一个应用程序中(甚至是在同一个文件中)混合使用两种类型的代码,所以托管扩展为实现两种代码的无缝转化提供了一种平滑的转化方式。

  开发人员可以继续使用未托管的C++来编写组件,以利用语言本身强大的功能和灵活性。然后,为了让.NET 框架应用程序顺利访问这个传统组件,开发人员可以使用托管扩展编写一个很小的、转换效率很高的包装(wrapper)程序。

  ● 需要让任何一种与.NET框架相容的语言可以访问C++组件

  托管扩展支持从任何.NET 框架相容语言来调用C++类。调用之所以可以实现,是因为使用托管扩展可以编写简单的包装类来对访问方暴露对应的C++类和方法。这些包装类都是托管的,并可以从其他.NET框架相容程序中进行调用。在调用过程中,外包类在托管的类和未托管的类之间扮演了映射层的角色—— 它让方法调用直接传递到未托管的类中。另外,需要特别指出的是,托管扩展支持对任何未托管的DLL或库的调用。

  ● 需要从未托管的代码中访问.NET框架类

  为了得到更多的功能,在未托管的代码中,可以访问.NET 框架中的类。使用托管扩展,可以从C++代码中直接创建、调用一个.NET 框架类。在实际编程中,可以像处理普通未托管的C++类一样对待对托管的类的处理。另外,在.NET框架中提供了对未托管的COM的调用支持,可以编写未托管的代码直接访问。

  因为托管的代码和未托管的代码各有优点,在实际工作中,开发人员可以根据项目的实际情况,灵活选择两者的使用。在某些追求访问效率的情况下,通过.NET 框架提供的COM接口进行访问可以收到比较好的运行效果;而在某些需要快速完成任务的情况下,利用.NET 框架提供的简便性进行工作会让开发人员倍感轻松。

  托管C++并非独立存在的编程语言,而仅仅是微软对C++的一个语法扩展,允许C++程序员在.NET框架和CLR的基础上进行托管编程。与C#和Visual Basic .NET相比,其主要优点是旧代码可以比较快地移植到新的平台上,而且即使不完全重写代码,也可以通过互操作在同一个模块中无缝整合托管和非托管代码,从新的.Net框架中获益。.Net框架封装了大量的API,例如网络访问、字符串操作、数据访问、XML服务、图形界面控件库、邮件服务、加密服务、文件输入/输出,甚至是WMI管理,也使得应用程序员可以编写更加简洁的代码。目前只有托管C++及其后继者C++/CLI可以做到无缝整合托管和非托管代码,而在托管代码中调用COM的速度又相当慢,所以经常被用于其他语言和非托管代码之间的桥梁。

  托管C++允许程序员编写托管代码,内存管理的工作现在可以让CLR去自动处理,访问时也增加了类型检查,减少了缓冲区溢出和内存泄漏的危险,增加了程序的稳定性,但是在性能敏感的应用中,庞大的.NET框架和缓慢的自动内存管理并不是必要的,传统非托管代码仍然是一些人的首选。

3、什么是CLR

CLR常用简写词语,CLR是公共语言运行时,Common Language Runtime)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。

4、什么是C++/CLI

http://baike.baidu.com/view/459502.htm

C++/CLI简介http://blog.csdn.net/eric_jo/article/details/4184916

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

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

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

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

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

  1. #using
  2. using namespace System;
  3. int main(void)
  4. {
  5. Console::WriteLine(S"Hello World");
  6. return 0;
#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列出它们各自数据类型。
(图片丢失,以后补充)

  需要注意的是,String和Object在定义一个变量时,注意要有星号("*"),但这个变量不是指针变量,这与标准C++的含义是不一样的。例如上面的代码可以改为:

  1. #using
  2. using namespace System;
  3. int main(void)
  4. {
  5. String* hello = S"Hello World";
  6. Console::WriteLine(hello);
  7. return 0;
  8. }
#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,例如下面的代码:

  1. __gc class G
  2. {
  3. public:
  4. int k;
  5. int sum(int);
  6. };
  7. G::sum(int i)
  8. {
  9. return i*(i + 1)/2;
  10. }
  11. int main()
  12. {
  13. G * g = new G;
  14. Console::WriteLine(g->sum(4)); // 结果输出10
  15. return 0;
  16. }
__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++堆中分配的变量。例如下面的代码:

    1. #using
    2. using namespace System;
    3. __value struct V { int i; };
    4. __gc struct G { V v; }; // 嵌入到__gc类中
    5. V f(V v) { // 定义一个全局函数,其值存储在运行栈中
    6. v.i += 1; // 不影响原来形参v的值
    7. return v; // 返回V结构类型的值
    8. }
    9. int main(void)
    10. {
    11. V v1 = {10}; // 在运行栈中声明并初始化
    12. V v2 = f(v1); // 调用f函数,此时v1中的i为10,而v2中的i为11
    13. G *pG = new G; // 为G实例分配堆空间
    14. pG->v = v1; // pG的v中的i为10
    15. pG->v.i += v2.i; // pG的v中的i为10+11=21
    16. Console::WriteLine(v1.i); // 输出结果为10
    17. Console::WriteLine(v2.i); // 输出结果为11
    18. Console::WriteLine(pG->v.i); // 输出结果为21
    19. return 0;
    20. }
时间: 2024-12-22 11:22:52

托管C++、C++/CLI、CLR的相关文章

CLR via C#-托管堆和垃圾回收

托管堆基础 访问类型的资源 面向对象的环境中,每个类型都代表可供程序使用的一种资源.要使用这些资源,必须为代表资源的类型分配内存.以下是访问一个资源所需的步骤. ①调用IL指令newobj,为代表资源的类型分配内存,由new操作符来完成. ②初始化内存,设置资源的初始状态并使资源可用,类型的实例构造器负责设置初始状态. ③访问类型的成员来使用资源. ④摧毁资源的状态以进行清理. ⑤释放内存,垃圾回收器独自负责这一步. 托管堆为开发人员提供了一个简化的编程模型,分配并初始化资源并直接使用. 大多数

重温CLR(十五) 托管堆和垃圾回收

本章要讨论托管应用程序如何构造新对象,托管堆如何控制这些对象的生存期,以及如何回收这些对象的内存.简单地说,本章要解释clr中的垃圾回收期是如何工作的,还要解释相关的性能问题.另外,本章讨论了如何设计应用程序来最有效地使用内存. 托管堆基础 每个程序都要使用这样或那样的资源,包括文件.内存缓冲区.屏幕空间.网络连接.数据库资源等.事实上,在面向对象的环境中,每个类型都代表可提供程序使用的一种资源.要使用这些资源,必须为代表资源的类型分配内存.以下是访问一个资源所需的步骤 1 调用IL指令newo

用C++/CLI搭建C++和C#之间的桥梁(二)—— 基本语法

托管对象的创建和引用 在前文中我们已经演示过创建一个托管对象,对于如下C#代码: System.Object x = new System.Object(); 其在C++/CLI中的等价代码如下: System::Object^ x = gcnew System::Object(); 和传统的C++创建的语法比较下, P* x = new P(); 我们不难发现,对于托管对象,主要引入了如下两个语法: 用gcnew代替new实现托管对象的创建 用^代替*实现托管对象的指针 这种方式创建的对象是可

初入 CLR - 阅读《CLR via C#》笔记

最近买了一本书<CLR via C#>阅读了第一章 - CLR 的执行模型,对 .NET 一直提到的 CLR 和 .NET Framework 有了一个大致的了解.我理解主要体现在: ■ 各种术语有了一个大致的体会:CTS CLS 和 CLI .CLR 与 .NET Framework. IL(Intermediate Language) .CIL(Common Intermediate Language)和 托管代码(Managed Code). ■ 用 C# 写完一个程序被编译成应用程序后

.NET 托管堆和垃圾回收

托管堆基础 简述:每个程序都要使用这样或那样的资源,包括文件.内存缓冲区.屏幕空间.网络连接.....事实上,在面向对象的环境中,每个类型都代表可供程序使用的一种资源.要使用这些资源,必须为代表资源的类型分配内存.以下是访问一个资源所需步骤:1.调用IL指令newobj,为代表资源的类型分配内存.(C# new操作符)2.初始化内存,设置资源的初始状态.(一般指构造函数)3.访问类型的成员来使用资源.(使用成员变量.方法.属性等)4.摧毁资源的状态以进行清除.(???Dispose???)5.释

有关CLR的初学小整理(可能理解不深刻,望大牛指出)

1. .Net程序通过CLR去加载运行管理代码, 加载CLR的进程成为“宿主”,通常操作系统加载. 加载CLR的进程也可以为某个DLL,也成为“宿主” 2. 宿主接口使宿主能够对运行库的更多方面进行控制,从而能够在 CLR 和宿主的执行模型之间进行更紧密的集成.在.NET Framework 1 版中,宿主模型使非托管宿主能够将 CLR 加载到进程中.配置某些设置以及接收事件通知.但在通常情况下,宿主和 CLR 可以在该进程中独立运行 3. 所有的CLR Hosting API提供的主要功能包括

对CLR基本原理概念&amp;垃圾回收机制的简单理解

PS,之前有说过C语言的函数&变量的一些基本概念,说得可能不是很好,先也把C#的.里相关的也说下,已成一统. 而说函数变量,其实主要就是GC,而GC又是CLR的主要内容,故就有了此文. CLR基本原理: 把这几个概念一说基本就知道了,其实就是为了跨语言.跨平台,和JAVA的JVM类似 1.MSIL,中间语言,就是独立于所在平台系统的.net的特殊代码.里面含有相关元数据信息,常用的反编译工具想ILspy等就是靠他吃饭的. 2.CLR,公共语言运行时,其实就是把VB.VC.C#等不同语言编译成同一

.net托管环境下struct实例字段的内存布局(Layout)和大小(Size)

在C/C++中,struct类型中的成员的一旦声明,则实例中成员在内存中的布局(Layout)顺序就定下来了,即与成员声明的顺序相同,并且在默认情况下总是按照结构中占用空间最大的成员进行对齐(Align):当然我们也可以通过设置或编码来设置内存对齐的方式. 然而在.net托管环境中,CLR提供了更自由的方式来控制struct中Layout:我们可以在定义struct时,在struct上运用StructLayoutAttribute特性来控制成员的内存布局.默认情况下,struct实例中的字段在栈

C# 托管堆和垃圾回收器GC

这里我们讨论的两个东西:托管堆和垃圾回收器,前者是负责创建对象并控制这些对象的生存周期,后者负责回收这些对象. 一.托管堆分配资源 CLR要求所有的对象都从托管堆分配.进程初始化时,CLR划出一个地址空间区域作为托管堆.CLR还要维护一个指针P,该指针指向下一个对象在堆中的分配位置. 那么我们进一步深入看看创建一个对象(也就是new 一个对象)时CLR做了哪些工作呢. 1.计算类型字段需要的字节数. 2.加上对象开销所需要的字节数,每个对象都有两个开销:类型对象指针和同步块索引 3.CLR检查区

CLR和内存分配

一.CLR CLR:即公共语言运行时(Common Language Runtime),是中间语言(IL)的运行时环境,负责将编译生成的MSIL编译成计算机可以识别的机器码,负责资源管理(内存分配和垃圾回收等). 可能有人会提问:为什么不直接编译成机器码,而要先编译成IL,然后在编译成机器码呢? 原因是:计算机的操作系统不同(分为32位和64位),接受的计算机指令也是不同的,在不同的操作系统中就要进行不同的编译,写出的代码在不同的操作系统中要进行不同的修改.中间增加了IL层,不管是什么操作系统,