托管C++C++/CLICLR

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了托管C++C++/CLICLR相关的知识,希望对你有一定的参考价值。

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.   
  4.   
  5.  int main(void)  
  6.  {  
  7.      Console::WriteLine(S"Hello World");  
  8.      return 0;  
  9.  }  
#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. {  
  4.  public:  
  5.  int k;  
  6.  int sum(int);  
  7.  };  
  8.   
  9.  G::sum(int i)  
  10.   
  11. {  
  12.   
  13. return i*(i + 1)/2;  
  14.   
  15. }  
  16.   
  17.   
  18.  int main()  
  19.  {  
  20.  G * g = new G;  
  21.  Console::WriteLine(g->sum(4)); // 结果输出10  
  22.  return 0;  
  23.  }  
__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.  }  

以上是关于托管C++C++/CLICLR的主要内容,如果未能解决你的问题,请参考以下文章

为 Blogger 上的博客格式化代码片段 [关闭]

如何从我的 C++/CLI 代码进入非托管 C++ 库

[linux][c/c++]代码片段01

[linux][c/c++]代码片段02

C 中的共享内存代码片段

C语言代码片段