Miracle's profilejack的世界PhotosBlogLists Tools Help

Blog


    June 15

    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所带来的额外开销。

    Comments

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    Trackbacks

    The trackback URL for this entry is:
    http://miracle46.spaces.live.com/blog/cns!97339AF66D538630!976.trak
    Weblogs that reference this entry
    • None