• C#的三大难点之二:托管与非托管


    相关文章:

    C#的三大难点之前传:什么时候应该使用C#?
    C#的三大难点之一:byte与char,string与StringBuilder
    C#的三大难点之二:托管与非托管
    C#的三大难点之三:消息与事件

    托管代码与非托管代码

    众所周知,我们正常编程所用的高级语言,是无法被计算机识别的。需要先将高级语言翻译为机器语言,才能被机器理解和运行。
    在标准C/C++中,编译过程是这样的:
    enter description here
    源代码首先经过预处理器,对头文件以及宏进行解析,然后经过编译器,生成汇编代码,接着,经过汇编,生成机器指令,最后将所有文件连接起来。
    这种编译方式的优点在于,最终直接生成了机器码,可以直接被计算机识别和运行,无需任何中间运行环境,但缺点也在于,由于不同平台能够识别的机器码不同,因此程序的跨平台能力较差。
    而在Java语言中,源代码并没有被直接翻译成机器码,而是编译成了一种中间代码(字节码Bytecode)。因此,运行Java程序需要一个额外的JRE(Java Runtime Enviromental)运行环境,在JRE中存在着JVM(Java Virtual Mechinal,Java虚拟机),在程序运行的时候,会将中间代码进一步解释为机器码,并在机器上运行。
    使用中间代码的好处在于,程序的跨平台性比较好,一次编译,可以在不同的设备上运行。
    托管/非托管是微软的.net framework中特有的概念,其中,非托管代码也叫本地(native)代码。与Java中的机制类似,也是先将源代码编译成中间代码(MSIL,Microsoft Intermediate Language),然后再由.net中的CLR将中间代码编译成机器代码。
    而C#与Java的区别在于,Java是先编译后解释,C#是两次编译。
    托管的方式除了拥有跨平台的优点之外,对程序的性能也产生一定的影响。但程序性能不在本文讨论的范围,这里不在赘述。
    此外,在.net中,C++也可以进行托管扩展,从而使C++代码也依赖于.net和CLR运行,获得托管代码的优势。

    托管资源与非托管资源

    在上一节中,我们讲到,托管代码与非托管代码相比,有下列不同:

    1. 编译运行过程不同
    2. 跨平台能力不同
    3. 程序性能不同

    本节中,我们会涉及到托管和非托管的另一个区别:

    1. 释放资源的方式不同

    在C/C++中,资源都是需要手动释放的,比如,你new了一个指针,用过之后就需要delete掉,否则就会造成内存泄露。
    而在Java中,不必考虑资源释放的问题,Java的垃圾回收机制(GC,Garbage Collection)会保证失效的资源被自动释放。
    而C#的机制与Java类似,运行于.net平台上的代码,分配的资源一般会自动由平台的垃圾回收器释放,这样的资源就是托管资源。
    但是一些例外的资源,如System.IO.StreamReader等各种流、各种连接所分配的资源,需要显式调用Close()或Dispose()释放,这种资源就叫做非托管资源。

    托管与非托管的混合编程

    C#的三大难点之前传:什么时候应该使用C#?中我提到过,C#的一大优势在于Windows平台下的界面编程。但由于C#并不是很普及,经常出现底层或后台代码采用C/C++编写的情况,此时,若选择C#作为界面语言,则必然遇到一个C#调用C++代码的问题。
    比较普遍的解决方案就是,先将C/C++的代码生成为DLL动态运行库,再在C#中调用。
    举个例子
    在C中:

    #include 
    #include 
    
    void DisplayHelloFromDLL()
    {
        printf ("Hello from DLL !
    ");
    }
    
    void CallHelloFromDLL(char* cp)
    {
        printf (cp);
        printf ("
    ");
        *cp='a';
        cp++;
        printf (cp);
        printf ("
    ");
    }
    

    在C#中:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace TestConsole
    {
        using System;
        using System.Runtime.InteropServices;     // DLL support
    
        class Program
        {
            [DllImport(@"TestLib.dll")]
            public static extern void DisplayHelloFromDLL();
    
            [DllImport(@"TestLib.dll", CallingConvention = CallingConvention.Cdecl)]
            public static extern void CallHelloFromDLL(StringBuilder s);
    
            static void Main()
            {
                Console.WriteLine("This is C# program");
                DisplayHelloFromDLL();
                StringBuilder sb = new StringBuilder(100);
                CallHelloFromDLL(sb);
                Console.WriteLine(sb);
        }
    }
    

    在混合编程中,涉及了几个要点。

    1. 如何在DLL中将函数接口暴露出来?
      有两种方式,一种是采用__declspec(dllexport)的声明,另一种是编写额外的def文件,如
      ;导出DLL函数
      LIBRARY testLib
      EXPORTS 
      DisplayHelloFromDLL
      CallHelloFromDLL
      
    2. DLL与C#之间如何进行数据传送?
      这个问题其实很复杂,像int,double这种基本的数据类型,是很好传递的。到了byte和char,就有点复杂了,更复杂的还有string和stringBuilder,以及结构体的传递等。
      若传递的是指针,有两种方法,一种是采用托管的方式,使用Intptr存储指针,并使用ref获得地址(&);另一种是在C#中编写非托管的代码,用unsafe声明:

      unsafe
      {
      //非托管代码
      }
      

      在非托管代码中,即可进行指针相关的操作。
      若传递的是函数指针,由于C#中没有函数指针的概念,因此采用委托(delegate)的方式。 
      若传递的是自定义结构体,也可以采用ref的方式传递。
      这个如果有机会的话,我会单独整理一下。

    3. extern “C”、CallingConvention =CallingConvention.Cdecl)等必要声明。
      这里面也牵涉到复杂的语言机制,本文不再赘述。

    参考文献:
    [译]C++, Java和C#的编译过程解析
    编译原理

     
  • 相关阅读:
    Linux文件结构
    磁盘分区
    BASH简介
    磁盘的基本概念
    Linux文件操作
    创建文件系统
    文件系统挂载
    一些常用命令
    asp.net创建PPT
    asp.net创建、删除、移动文件夹 文件
  • 原文地址:https://www.cnblogs.com/wwwbdabc/p/11678298.html
Copyright © 2020-2023  润新知