C++ 通用语言框架 CLI 认识

在使用 visual studio 2019 开发时,有涉及 CLI 编程的经历,那么什么是 CLI 编程,这和传统的 Managed C++ 有什么区别呢?本文进行总结

什么是 Managed C++、C++/CLI?

  • Managed C++(C 托管扩展 / 托管 C):是一组现已弃用的 C 语言扩展,用于将语法和语言 C 引入. NET Framework,C
  • CLI (Common Language Infrastructure, 通用语言框架):用于取代 Managed C++ ,是最新的托管 C++ 实现方式,其提供了一套可执行代码和它所运行需要的虚拟执行环境的规范
  • Managed C 使用 gc 创建托管类型,**C**/CLI 使用 ref 或 value 创建托管类型

C++/CLI 的优势?

  • 可以直接和原生 C/C++ 代码进行混合编译,使用 C/C++ 的函数和数据结构(被微软称为 mixed mode 编程)
  • 使用 C++/CLI 调用 Native 代码时,再也不需要编写额外的 P/Invoke (平台调用) 代码,只需要引入头文件,然后直接调用

C++/CLI 包括哪些部分?

  • CTS(Common Type System,通用类型系统):CLI 的基础,一个类型规范,定义了所有 CLI 平台上定义的类型集合,所有基于 CLI 的语言类型都是 CTS 的一个子集,目前 C++/CLI 是对 CTS 描述支持最好的高级语言
  • Metadata(元数据) 用来描述和引用 CTS 定义的类型
  • CLS(Common Language Specification,通用语言规范) 是用以确保所有 CLI 语言能够互操作的一组规则,它定义了所有 CLI 语言都必须支持的一个最小功能集(一个 CTS 的子集)
  • VES(Virtual Execution System,虚拟执行系统) 为 CLI 程序提供了一个在各种可能的平台上加载和执行托管代码的虚拟机环境,只是一个规范,比如微软和 momo 就各有自己的实现

C++/CLI 与 Net、CLR 的关系?

  • .NET Framework 是微软对 CLI 的一个实现,当然也是目前最好的实现,
  • .NET Framework 主要包含 CLR 和 BCL,CLR(实时通用语言)是核心也即 CLI 的实现,BCL 是一套通用的代码库,可以被所有的. NET 语言(C#, C++/CLI)程序所使用
  • Visual C2005 是 C/CLI 的实现

C 与 C/CLI 的区别?

  • 传统的 C++ 编译模式包括把单独的源文件编译为目标文件(obj),再把目标文件与库函数链接在一起,以生成可执行程序
  • CLI 模式却大不相同,它涉及到程序集的创建与使用,简单来说,在不计输入源文件数目的基础上,程序集即为单次编译的输出。如果输出带有一个进入点函数(例如 main 函数),它即为一个. Exe 文件;如果没有,它则为一个. Dll 文件;
  • 任何引用外部程序集而生成的编译,必须要访问所依赖的程序集,此时也没有类似传统链接时用到的头文件机制,而是通过编译器在所依赖的程序集内部查找,来访问所需的外部信息

Managed C++ 中托管类型与非托管类型的创建?

  • 非托管类型
    1
    2
    3
    4
    5
    6
    7
    8
    class Foo  
    {
    private:
    int x;
    public:
    Foo(): x(0){}
    Foo(int xx): x(xx) {}
    };
  • 托管类型
    1
    2
    3
    4
    5
    6
    7
    8
    __gc class Bar  
    {
    private:
    int x;
    public:
    Bar(): x(0){}
    Bar(int xx): x(xx) {}
    };
  • 注意事项
    • 托管类型定义有__gc 关键字
    • 托管类型是可以被垃圾回收器所回收的。他们必须要用关键字 new 来创建,永远都不会在栈中出现
      1
      2
      3
      4
      5
      6
      Foo f; //合法
      Bar b; //非法
      // 非托管对象,必须手动清理
      Foo* pf = new Foo(2);
      // . . .
      delete pf;
    • 托管类型 不能实现多重继承,或者继承与非托管类型
    • 托管类型 不能用 friend 关键字来实现私有访问
    • 托管类型 不能实现拷贝构造函数

使用 C++ CLI 中创建托管类型?

  • 引用类型
    1
    2
    ref class
    ref struct
  • 值类型
    1
    2
    value class
    value struct
  • 接口
    1
    public interface class IMyFile { ... };
  • 抽象类
    1
    2
    public ref class Shape abstract {};
    public ref class Shape2D abstract : public Shape{};

什么是 C 互操作 (C Interop)?

  • 让拖管代码对象和非托管对象协同工作的过程称为互用性 (Interoperability),通常简称为 Interop
  • .NET 通过 P/Invoke (平台调用)、System.Runtime.InteropServices 命名空间、C++ 互操作性和 COM 互操作性(COM 互操作)实现与非托管代码的互操作性
  • C++ 互操作性允许托管和非托管代码存在于同一应用程序中,甚至存在于同一文件中

P/Invoke (平台调用) 和 C 互操作 (C Interop) 的区别?

  • C++ 互操作优先于 P/Invoke: 因为 P/Invoke 不是类型安全的。因此,错误主要在运行时报告,但 C++ 互操作也比 P/Invoke 具有性能优势
  • C++ 互操作执行的数据封送处理是最简单的形式:参数只是以按位方式跨托管 / 非托管边界复制;根本不执行任何转换。对于 P/Invoke,仅当所有参数都是简单、可切换的类型时,才如此。否则,P/Invoke 将执行非常可靠的步骤,将每个托管参数转换为适当的本机类型,如果参数标记为 “out” 或 “in,out”,则反之亦然
  • C++ 互操作使用尽可能快的数据封送处理方法,而 P/Invoke 使用最可靠的方法。这意味着 C 互操作(以 C 的典型方式)默认提供最佳性能,程序员负责解决此行为不安全或不适当的情况
  • C++ 互操作要求必须显式提供数据封送处理,但优点是程序员可以根据数据的性质以及如何使用它来自由决定什么是合适的。此外,尽管 P/Invoke 数据封送处理的行为可以在一定程度上进行自定义修改,但 C++ Interop 允许逐个调用地自定义数据封送处理。这在 P/Invoke 中是不可能的

使用 C++ CLI 定义托管类?

  • CLI 作为 "胶水" 连接 C++ 的非托管类到 C# 上
  • 1)C++ 定义非托管类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // CPPDLL.h
    class __declspec(dllexport) MyClass
    {
    public:
    MyClass()
    {
    this->a1 = 0;
    this->a2 = "hello";
    this->b.x = 1;
    this->b.y = 2;
    this->cNum = 3;
    this->c = new int[this->cNum]{ 4,5,6 };
    std::cout << "init UnManegerClass..." << endl;
    };
    ~MyClass() {};

    int setA(int a1, string a2)
    {
    std::cout << "[in]a1:" << a1 << " a2:" << a2 << endl;
    this->a1 = a1;
    this->a2 = a2;
    std::cout << "[out]a1:" << this->a1 << " a2:" << this->a2 << endl;

    return 0;
    };

    int setB(point b)
    {
    std::cout << "[in]b:" << this->b.x << "," << this->b.y << endl;
    this->b = b;
    std::cout << "[out]b:" << this->b.x << "," << this->b.y << endl;

    return 0;
    };

    int setC(int* c, int num)
    {
    for (int i = 0; i < this->cNum; i++) {
    std::cout << "[in]c:" << this->c[i] << " input:" << c[i] << endl;
    }

    this->c = new int[cNum];
    for (int i = 0; i < cNum; i++) {
    this->c[i] = c[i];
    }

    for (int i = 0; i < cNum; i++) {
    std::cout << "[out]c:" << this->c[i] << " input:" << c[i] << endl;
    }

    return 0;
    };

    private:
    int a1;
    string a2;
    point b;
    int* c;
    int cNum;
    };
  • 2)CLR 定义托管类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    // CLRDLL.h
    #include "CPPDLL.h"
    namespace CLRDLL
    {
    public ref struct point_
    {
    public:
    double x;
    double y;
    };

    public ref class ManegerClass // C#的托管类
    {
    public:
    ManegerClass();
    ~ManegerClass();

    int setA(int a1,System::String^ a2);
    int setB(point_^% b);
    int setC(int* c, int num);
    private:
    MyClass* myClass; // C++的非托管类
    };
    }

    // CLRDLL.cpp
    #include "CLRDLL.h"
    #include <string>
    #include <msclr\marshal_cppstd.h>

    using namespace std;
    using namespace System;
    using namespace Runtime::InteropServices;
    using namespace msclr::interop;

    namespace CLRDLL
    {
    ManegerClass::ManegerClass()
    {
    myClass = new MyClass();
    std::cout << "init ManegerClass..." << endl;
    }
    ManegerClass::~ManegerClass()
    {
    throw gcnew System::NotImplementedException();
    }
    int ManegerClass::setA(int a1, System::String^ a2)
    {
    std:;string a2_= marshal_as<std::string>(a2->ToString());
    myClass->setA(a1, a2_);
    return 0;
    }
    int ManegerClass::setB(point_^% b)
    {
    point* b_=new point();
    b_->x = b->x;
    b_->y = b->y;
    myClass->setB(*b_);
    return 0;
    }
    int ManegerClass::setC(int* c, int num)
    {
    myClass->setC(c, num);
    return 0;
    }
    }
  • 3)C# 调用 CLR 托管类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    using CLRDLL;
    public static void Main()
    {
    CLRDLL.ManegerClass manegerClass=new CLRDLL.ManegerClass();

    Console.WriteLine("-------------------------------");

    int a1 = 10;
    string str = "hello CLR";
    manegerClass.setA(a1, str);

    Console.WriteLine("-------------------------------");

    CLRDLL.point_ p = new CLRDLL.point_();
    p.x = 100;
    p.y = 200;
    manegerClass.setB(ref p);

    Console.WriteLine(string.Format("p(x,y):({0},{1})", p.x, p.y));

    Console.WriteLine("-------------------------------");

    int[] c = { 8,9,10,0 };
    int num = c.Length;
    unsafe
    {
    fixed(int* c_=c)
    {
    manegerClass.setC(c_, num);
    }
    }
    }

什么是托管代码 (managed code)?

  • .NET Framework 的核心是其运行库的执行环境,称为公共语言运行库 (CLR) 或.NET 运行库。通常将在 CLR 的控制下运行的代码称为托管代码 (managed code) ,如:.Net 的公共语言运行时 (CLR) 管理的代码
  • 不像 C++ 编译过程 直接得到机器码,托管代码使用相应的编译器编译这些语言编写的代码时,无法获得机器代码, 而是获得公共中间语言 (CIL) 代码,然后运行时 CLR 会对其进行编译为机器码,并将其执行。托管代码应用程序可以获得公共语言运行库服务,例如自动垃圾回收、运行库类型检查和安全支持等。这些服务帮助提供独立于平台和语言 的、统一的托管代码应用程序行为

什么是非托管代码 (unmanaged code)?

  • 由操作系统直接执行的代码称为非托管代码,这些代码只能运行在编译出它们的计算机上,如 C/C++ 程序
  • 非托管代码不能享受一些公共语言运行时 (CLR) 所提供的服务, 必须提供自己的垃圾回 收、类型检查、安全支持等服务

托管代码与非托管代码的区别?

  • 托管代码是一种中间语言,运行在公共语言运行时 (CLR) 上;非托管代码被编译为机器码,运行在机器上
  • 托管代码独立于平台和语言,能更好的实现不同语言平台之间的兼容; 非托管代码依赖于平台和语言
  • 托管代码可享受 CLR 提供的服务(如安全检测、垃圾回收等),不需要自己完成这些操作 ; 非托管代码需要自己提供安全检测、垃圾回收等操作

什么是托管代码的互操作性?

  • 公共语言运行时 (CLR) 允许越过托管与非托管环境之间的边界,同时,即使在基类库 中,也有很多代码可以做到这一点。 这称为互操作性 (interop)
  • 使用这些机制可以包装某个非托管库以及调用该库。 但是如果采取这种方法,当代码越过运行时的边界时,实际的执行管理将再次交接到托管代码,因而需要遵守相同的限制

托管代码与非托管代码的性能比较?

  • 所有.Net 语言都将被编译成为一个叫做 IL 汇编的中间语言,当某段 IL 代码被第一次运行的时候,JIT 编译器就会将这段 IL 代码,全部编译成本地代码,然后再执行。所以.NET 程序第一次运行都启动很慢的原因!
  • 可以事先将.NET 程序所有的 IL 代码都编译成本地代码并保存在缓存区中,这样一来,这个程序就跟 c++ 编译 的一模一样了,没有任何区别,运行时也可以脱离 JIT (编译器) 了

什么是托管堆?

  • C# 中的所有类型都是(直接或间接)从 System.Object 类型派生的
  • C# 的类型被分成两大类:(1)引用类型(reference type), 从低地址往高地址分配在 托管堆 (Managed Heap) 上;(2)值类型(value type), 从高地址往低地址分配在堆栈上

什么是托管资源?

  • .NET 可以自动进行回收的资源,被 CLR 控制的内存资源,这些资源的管理可以由 CLR 来控制,主要是指托管堆上分配的内存资源
  • 托管资源的回收工作是不需要人工干预的,由.NET 运行库在合适调用垃圾回收器进行回收
  • .NET 中超过 80% 的资源都是托管资源

什么是非托管资源?

  • 非托管资源指的是.NET 不知道如何回收的资源,是 CLR 不能控制或者管理的部分,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等
  • 这些资源一般情况下不存在于托管堆中。垃圾回收器在清理的时候会调用 Object.Finalize () 方法
  • 默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源

托管意味着托管数据吗?

  • 对于 Visual Basic 和 C# 来说,当你在那些语言里面声明一个类,那么这个类的实例会在托管堆 中被创建,垃圾收集器 (GC) 会帮 我们管理这些对象的回收
  • 在 Visual C++ 中,可以决定哪些类是托管类型,哪些类是非托管类型的