众所周知,咱们正常编程所用的高级语言,是没法被计算机识别的。须要先将高级语言翻译为机器语言,才能被机器理解和运行。
在标准C/C++中,编译过程是这样的:
源代码首先通过预处理器,对头文件以及宏进行解析,而后通过编译器,生成汇编代码,接着,通过汇编,生成机器指令,最后将全部文件链接起来。
这种编译方式的优势在于,最终直接生成了机器码,能够直接被计算机识别和运行,无需任何中间运行环境,但缺点也在于,因为不一样平台可以识别的机器码不一样,所以程序的跨平台能力较差。
而在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运行,得到托管代码的优点。编程
在上一节中,咱们讲到,托管代码与非托管代码相比,有下列不一样:安全
本节中,咱们会涉及到托管和非托管的另外一个区别:函数
在C/C++中,资源都是须要手动释放的,好比,你new了一个指针,用过以后就须要delete掉,不然就会形成内存泄露。
而在Java中,没必要考虑资源释放的问题,Java的垃圾回收机制(GC,Garbage Collection)会保证失效的资源被自动释放。
而C#的机制与Java相似,运行于.net平台上的代码,分配的资源通常会自动由平台的垃圾回收器释放,这样的资源就是托管资源。
可是一些例外的资源,如System.IO.StreamReader等各类流、各类链接所分配的资源,须要显式调用Close()或Dispose()释放,这种资源就叫作非托管资源。性能
C#的一大优点在于Windows平台下的界面编程。但因为C#并非很普及,常常出现底层或后台代码采用C/C++编写的状况,此时,若选择C#做为界面语言,则必然遇到一个C#调用C++代码的问题。
比较广泛的解决方案就是,先将C/C++的代码生成为DLL动态运行库,再在C#中调用。
举个例子
在C中:优化
#include #include void DisplayHelloFromDLL() { printf ("Hello from DLL !\n"); } void CallHelloFromDLL(char* cp) { printf (cp); printf ("\n"); *cp='a'; cp++; printf (cp); printf ("\n"); }
在C#中:ui
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); } }
在混合编程中,涉及了几个要点。spa
;导出DLL函数 LIBRARY testLib EXPORTS DisplayHelloFromDLL CallHelloFromDLL
DLL与C#之间如何进行数据传送?
这个问题其实很复杂,像int,double这种基本的数据类型,是很好传递的。到了byte和char,就有点复杂了,更复杂的还有string和stringBuilder,以及结构体的传递等。
若传递的是指针,有两种方法,一种是采用托管的方式,使用Intptr存储指针,并使用ref得到地址(&);另外一种是在C#中编写非托管的代码,用unsafe声明:.net
unsafe
{
//非托管代码 }
在非托管代码中,便可进行指针相关的操做。
若传递的是函数指针,因为C#中没有函数指针的概念,所以采用委托(delegate)的方式。
若传递的是自定义结构体,也能够采用ref的方式传递。
这个若是有机会的话,我会单独整理一下。翻译
extern “C”、CallingConvention =CallingConvention.Cdecl)等必要声明。
这里面也牵涉到复杂的语言机制,本文再也不赘述。指针
咱们知道计算机不能直接理解高级语言,它只能理解机器语言,因此咱们必需要把高级语言翻译成机器语言,这样计算机才能执行高级语言编写的程序,在接下来的博文中,咱们将介绍非托管和托管语音的编译过程。
纯C/C++的程序一般运行在一个非托管环境中,类是由头文件(.h)和实现文件(.cpp)组成,每一个类造成了一个单独的编译单元,当咱们编译程序时,几个基本组件会把咱们的源代码翻译成二进制代码,接下来咱们经过如下图片说明非托管环境的编译过程:
图1 C/C++编译过程
首先是预处理器,若是在项目中有头文件和宏表达式,那么它将负责包含头文件和翻译全部的宏观表达式。
接下来是编译器,它不是直接生成二进制代码,而是生成汇编代码(.s),这基本上是全部现代的非结构化语言的共同基础。
而后,汇编程序把汇编代码翻译成目标代码(.o和.obj文件,机器指令)。
最后连接器,它把全部彼此相关的目标文件和生成的可执行文件或库连接起来。
总而言之,在通常状况下,咱们的代码首先翻译成汇编代码,接着翻译成机器指令(二进制代码)。
在C/C++中,宏是预处理指令,它有多种应用技术:包括预约义、建立关键字和条件编译等等。在通常状况下,这些技术在C++中使用被认为是很差的作法,主要缘由是有可能滥用C++提供的语法变化功能,甚至有可能在不知情状况下建立了非标准的语言,宏不遵循通常的源代码编译规则,因为它经过预处理来处理,而不是编译器。
在托管环境中,编译的过程略有不一样,咱们熟知的托管语言有C#和Java,接下来,咱们将以C#和Java为例介绍在托管环境中的编译过程。
当咱们在喜好的IDE中编写代码时,第一个检测咱们代码的就是IDE(词法分析),而后,编译成目标文件和连接到动态/静态库或可执行文件进行再次检查(语法分析),最后一次检查是运行时检查。托管环境的共同特色是:编译器不直接编译成机器码,而是中间代码,在.NET中称为MSIL - Microsoft Intermediate Language,Java是字节码(Bytecode)
在那以后,在运行时JIT(Just In Time)编译器将MSIL翻译成机器码,这意味着咱们的代码在真正使用的时候才被解析,这容许在CLR(公共语言运行时)预编译和优化咱们的代码,实现程序性能的提升,但增长了程序的启动时间,咱们也可使用Ngen(Native Image Generator)预编译咱们的程序,从而缩短程序的启动时间,但没有运行时优化的优势。(JeffWong的补充Java是先经过编译器编译成Bytecode,而后在运行时经过解释器将Bytecode解释成机器码;C#是先经过编译器将C#代码编译成IL,而后经过CLR将IL编译成机器代码。因此严格来讲Java是一种先编译后解释的语言,而C#是一门纯编译语言,且须要编译两次。)
图2 C#的编译过程
.Net Framework就是在Win32 core上添加了一个抽象层,它提供的一个好处就是支持多语言、JIT优化、自动内存管理和改进安全性;另一个完整解决方案是WinRT,但这涉及到另一个主题了,这里不做详细介绍。
图3 Windows API
JIT编译带来了许多好处,最大的一个在我看来是性能的优点,它容许CLR(通用语言运行时扮演Assembler组件)只执行须要的代码,例如:假设咱们有一个很是大的WPF应用程序,它不是当即加载整个程序,而是CLR开始执行时,咱们代码的不一样部分将经过一个高效的方法翻译成本地指令,由于它可以检查系统JIT和生成优化的代码,而不是按照一个预约义的模式。不幸的是,有一个缺点就是启动的过程比较慢,这意味着它不适用于加载时间长的包。
JIT的替代方案使用NGen
若是Visual Studio由JIT建立,那么它的启动咱们将须要等待几分钟,相反,若是它是使用Ngen(Native Image Generator)编译,它将建立纯二进制可执行文件,若是只考虑速度的问题,那是绝对是正确的选择。
在非托管环境中,咱们须要知道编译的过程分红编译和链接两个阶段,编译阶段将源程序(*.c,*.cpp或*.h)转换成为目标代码(*.o或*.obj文件),至于具体过程就是上面说的C/C++编译过程的前三个阶段;连接阶段是把前面转成成的目标代码(obj文件)与咱们程序里面调用的库函数对应的代码连接起来造成对应的可执行文件(exe文件)。
托管环境中,编译过程能够分为:词法分析、语法分析、中间代码生成、代码优化和目标代码生成等等过程;不管是.NET仍是Java,它们都会生成中间代码(MSIL或Bytecode),而后把优化后的中间代码翻译成目标代码,最后在程序运行时,JIT将IL翻译成机器码。
不管是托管或非托管语言,它们的编译编译过程是把高级语言翻译成计算机能理解的机器码,因为编译过程涉及的知识面很广(编译的原理和硬件知识),并且本人的能力有限,也只能简单的描述一下这些过程,若是你们但愿深刻了解编译的原理,我推荐你们看一下《编译原理》。