热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Mono为何能跨平台

概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa

概念


  • JIT 编译 (JIT compilation),运行时需要代码时,将 Microsoft 中间语言 (MSIL) 转换为机器码的编译。
  • CLR (Common Language Runtime)是通用语言运行时。和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集等),并保证应用和底层操作系统之间必要的分离。
  • 机器码 (machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据。通常情况下它是已经经过编译,但与特定机器码无关。字节码通常不像源码一样可以让人阅读,而是编码后的数值常量、引用、指令等构成的序列。
  • 字节码 (Bytecode) 是一种包含执行程序、由一序列 op 代码/数据对 组成的二进制文件。字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码。字节码的实现方式是通过编译器和虚拟机。编译器将源码编译成字节码,特定平台上的虚拟机将字节码转译为可以直接执行的指令。
  • 编译器: 在代码运行之前,把所有代码一次性翻译成 目标平台(Windows或者Linux等)的二进制指令,这些指令可以脱离编译器独立运行。
  • 解释器: 在代码运行过程中,把代码逐条生成目标平台指令,并执行,然后再处理下一条。
  • 编译执行: 要先编译再执行,就是使用编译器来将我们的代码全部编译成机器可以识别的二进制代码,然后进行执行。因为先整体进行编译,所以这里会生成编译后的机器代码。比如C,C++等语言都是编译执行的。
  • 解释执行: 是使用解释器会将我们的一句句代码解释成机器可以识别的二进制代码来执行,可以认为是,解释一句,执行一句。在这个过程中,不会生成中间文件。比如python,ruby等语言都是解释执行的。

JIT具体做法

JIT具体的做法是这样的: 当载入一个类型时,CLR为该类型创建一个内部数据结构和相应的函数,当函数第一被调用时,JIT将该函数编译成机器语言.当再次遇到该函数时则直接从cache中执行已编译好的机器语言。

iOS的限制

IL是.NET框架中中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)
在Mac OS上,因为iOS的现有限制,面向iOS的C#代码会通过AOT编译技术直接编译为ARM汇编代码。而在Android上,应用程序会转换为IL,启动时再进行JIT编译。
Mono的AOT和.NET的Ngen一样,都是通过提前编译来减少JIT的工作,但默认情况下AOT并不编译所有IL代码,而是在优化和JIT之间取得一个平衡。由于iOS平台禁止JIT编译,于是Mono在iOS上需要Full AOT编译和运行。即预先对程序集中的所有IL代码进行AOT编译生成一个本地代码映像,然后在运行时直接加载这个映像而不再使用JIT引擎。

java为什么可以做到跨平台

因为java也是一种解释型语言,虽然java也有编译器,但是编译器做的工作只是预编译,是把java代码编译成了非纯二进制的字节码,并没有直接编译成某个平台的二进制指令,这样的字节码文件再通过解释器进行解释成某个平台的二进制指令的时候,会比直接把java代码解释成二进制指令要快。

C#为什么不能热更新

准确的说,C#在安卓上可以实现热更新,但在苹果上却不能。
在安卓上可以通过C#的语言特性-反射机制实现动态代码加载从而实现热更新。
具体做法是:将需要频繁更改的逻辑部分独立出来做成DLL,在主模块调用这些DLL,主模块代码是不修改的,只有作为业务(逻辑)模块的DLL部分需要修改。游戏运行时通过反射机制加载这些DLL就实现了热更新。
但苹果对反射机制有限制,不能实现这样的热更。为什么限制反射机制?安全起见,不能给程序太强的能力,因为反制机制实在太过强大,会给系统带来安全隐患。

LUA为什么可以热更

LUA解释型语言,并不需要事先编译成块,而是运行时动态解释执行的。那C#为什么不做成解释型语言呢?因为C#的定位是一个追求效率且功能强大的编译型语言。这样LUA就和普通的游戏资源如图片,文本没有区别,因此可以在运行时直接从WEB服务器上下载到持久化目录并被其它LUA文件调用。

Mono为何能跨平台

众所周知,Unity3D引擎凭借着强大的跨平台能力而备受开发者的青睐,在跨平台应用开发渐渐成为主流的今天,具备跨平台开发能力对程序员来说就显得特别重要。传统的针对不同平台进行开发的方式常常让开发者顾此失彼,难以保证应用程序在不同的平台都有着相同的、出色的体验,这种情况下寻找到一种跨平台开发的方式将会为解决这个问题找到一种思路。从目前的开发环境来看,Web应该是最有可能成为跨平台开发的神兵利器,可是长期以来Web开发中前端和后端都有各自不同的工作流,虽然现在出现了前端和后端逐渐融合的趋势,可在博主看来想让Web开发变得像传统开发这样简单还需要一定的过渡期。

从Mono到Xamarin
  对Unity3D来说,Mono是实现它跨平台的核心技术。Mono是一个旨在使得.NET在Linux上运行的开源项目。它通过内置的C#语言编译器、CLR运行时和各种类库,可以使.NET应用程序运行在Windows、Linux、FreeBSD等不同的平台上。而在商业领域,Xamarin则实现了用C#编写Android和iOS应用的伟大创举。Windows10发布的时候,微软提出了通用应用UWP的设想,在这种设想下开发者可以直接在最新的Visual Studio中使用C#编写跨平台应用。最近微软收购了Xamarin,这一举措能够保证Xamarin这样的商业项目可以和微软的产品融合地更好。虽然在传统Web开发中Java和PHP目前占据主要优势,可是虽然云计算技术的流行,服务器成本的降低或许会让C#这样优秀的语言更加成熟。我一直坚信技术没有好坏的区别,一切技术问题的核心是人,所以接下来,我们打算追随着跨平台开发的先驱——Java,最早提出的“一次编写、到处运行”的伟大思想来探索C#程序跨平台的可能性。

Mono跨平台的原理
  在提到Mono跨平台的时候,我们首先需要引入公共语言基础(Common Language Infrastructure,CLI)这个概念,CLI是一套ECMA定义的标准,它定义了一个和语言无关的跨体系结构的运行环境,这使得开发者可以用规范定义内各种高级语言来开发软件,并且无需修正即可让软件运行在不同的计算机体系结构上。因此我们可以说跨平台的原理是因为我们定义了这样一个和语言无关的跨体系结构的运行环境规范,只要符合这个规范的应用程序都可以运行在不同的计算机体系结构上,即实现了跨平台。针对这个标准,微软实现了公共语言运行时(Common Language Runtime,CLR),因此CLR是CLI的一个实现。我们熟悉的.NET框架就是一个在CLR基础上采用系统虚拟机的编程平台,它为我们提供了支持多种编程语言如C#、VB.NET、C++、Python等。我们编写的C#程序首先会被C#编译器编译为公共中间语言即CIL或者是MSIL(微软中间语言),然后再由CLR转换为操作系统的原生代码(Native Code)。

好了,现在我们来回答最开始的问题:Mono为什么能够跨平台。我们回顾.NET程序运行机制可以发现实现.NET跨平台其实需要这三个关键:编译器、CLR和基础类库。在.NET下我们编写一个最简单的“Hello World”都需要mscorlib.dll这个动态链接库,因为.NET框架已经为我们提供了这些,因为在我们的计算机上安装着.NET框架,这是我们编写的应用程序能够在Windows下运行的原因。再回头来看Mono,首先Mono和CLR一样,都是CLI这一标准的实现,所以我们可以理解为Mono实现了和微软提供给我们的类似的东西,因为微软的.NET框架属于商业化闭源产品,所以Mono除了在实现CLR和编译器的同时实现了大量的基础库,而且在某种程度上Mono实现的版本与相同时期.NET的版本有一定的差距,这点使用Unity3D开发游戏的朋友应该深有感触吧!这就决定了我们在将应用程序移植到目标平台时能否实现在目标平台上和当前平台上是否能够具有相同的体验。因为公共中间语言即CIL能够运行在所有实现了CLI标准的环境中,而CLI标准则是和具体的平台或者说CPU无关的,因此只要Mono运行时能够保证CIL的运行,就可以实现应用程序的跨平台。我们可以通过下面这张图来总结下这部分内容:

开发第一个跨平台程序
  下面我们来尝试开发第一个跨平台程序,我们使用Visual Studio或者MonoDevelop编写一个简单的控制台应用程序,为了减少这个程序对平台特性的依赖,我们这里选择System这个命名空间来实现最为基础的Hello World,这意味着我们的应用程序没有使用任何除mscorlib.dll以外的库:

using System;namespace MonoApplication
{class MainClass{public static void Main(string[] args){Console.WriteLine("Hello World!");}}
}

因为我们的计算机安装了.NET框架,所以我们编写的这个程序会被C#编译器编译为公共中间语言CIL,然后再由CLR转换为Native Code。通常情况下公共中间语言(CIL)会被存储到.il文件中,可是在这里我们在编译的时候好像并没有看到这个文件的生成啊,这是因为这里生成的可执行文件(.exe)本质上是公共中间语言(CIL)形态的可执行文件。这一点我们可以通过ildasm这个工具来验证,该工具可以帮助我们查看IL代码,通常它位于C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin这个位置。下面是通过这个工具获得的IL代码:

.method public hidebysig static void Main(string[] args) cil managed
{.entrypoint// 代码大小 13 (0xd).maxstack 8IL_0000: nopIL_0001: ldstr "Hello World!"IL_0006: call void [mscorlib]System.Console::WriteLine(string)IL_000b: nopIL_000c: ret
} // end of method MainClass::Main

可以看到这段代码和我们编写的程序中的Main方法完全对应,关于这段代码的含义,大家可以通过搜索引擎来了解IL代码的语法。因为我们这里想要说明的是,这里生成的可执行文件(.exe)从本质上来讲并非是一个可执行文件。因为它能否执行完全是取决于CPU的,这和我们直接用C++编写的应用程序不同,我们知道不同的编译器如Windows下的VC++和Linux下的GCC都是和硬件紧密相连的,所以我们编译的程序能够在各自的平台直接运行,即CPU是认识这些程序的。可是在.NET这里就不一样了,因为我们通过C#编译器即csc.exe编译出来的文件,其实是一个看起来像可执行文件,实际上却是一个和平台无关、和CPU无关的IL文件。

那么我们就会感到迷茫了啊,平时我们编译完C#程序双击就可以打开啊,哈哈,现在隆重请出.NET程序的家长公共语言运行时(CLR)。公共语言运行时实际上是程序运行的监管者,程序运行的情况完全由运行时来决定。我们双击这个文件的时候,公共语言运行时会将其加载到内存中,然后由即时编译器(JIT)来识别IL文件,然后由CPU去完成相应的操作。

所以我们可以这样理解.NET程序跨平台,因为IL文件是一个和平台无关、和CPU无关的、跨平台的文件结构,所以我们只需要在不同的平台上实现这样一个公共语言运行时(CLR)就可以实现在不同的平台上运行同一个程序。但这个过程中,需要有一个C#编译器负责将C#代码转换为IL代码,然后需要有一个公共语言运行时(CLR)来解析IL代码。与此同时,我们在.NET框架下使用了大量的基础类库,这些类库在Windows以外的平台是没有的,所以除了C#编译器和公共语言运行时以外,我们还需要基础类库。现在大家是不是对Mono有了更清楚的认识了呢?没错,Mono所做的事情其实就是我们在讨论的这些事情。这里博主想说说即时编译(JIT)和静态编译(AOT),这两种编译方式我们可以按照”解释型”和”编译型”来理解,为什么Unity3D在iOS平台上做热更新的时候会出现问题呢?这是因为iOS平台考虑到安全性禁止使用JIT即时编译,所以像C#这种需要编译的语言在这里就无计可施了。

好了,既然我们有Mono这样的工具能够帮助我们实现跨平台开发。那么我们现在就来考虑将这个程序移植到Linux平台,这里以Linux Deepin为例,我们按照C#程序编译的过程来完成这个移植过程:
1、将C#程序编译为IL文件:在.NET下我们使用csc.exe这个程序来完成编译,在Mono下我们使用mcs.exe这个程序来完成编译,这个程序在安装完Mono以后在其安装目录内可以找到。我们在命令行下输入命令:

mcs D:\项目管理\CSharp\MonoApplication\MonoApplication\Main.cs

2、这样将生成Main.exe这样一个IL文件,现在我们需要一个运行时来解析它,在.NET下我们使用CLR来完成这个步骤,在Mono下我们使用mono.exe这个文件来完成这个步骤。我们在命令行下输入下列命令:

mono D:\项目管理\CSharp\MonoApplication\MonoApplication\Main.exe

在这里插入图片描述

我们可以看到命令行下输出了我们期望的Hello World,这意味着我们编写的程序现在运行在Mono中了,实际上在Windows下由Mono提供的C#编译器mcs.exe编译的IL文件双击是可以直接运行的,因为我们的计算机上安装了CLR,它作为.NET的一部分内置在我们的计算机中。由此我们会发现一个问题,我们这里的跨平台实际上是编译器、运行时和基础类库这三部分的跨平台,这意味着我们在Linux下运行.NET程序是需要Mono提供支持的。因为在这里我无法在Linux离线安装Mono,所以Linux下运行.NET程序的验证需要等博主以后有时间再来更新啦!可是我们可以想象到,通过C#编译器编译得到的可执行文件在Linux下是无法正常运行的,因为通常情况下Windows程序在Linux下运行是需要虚拟机环境或者Wine这样的软件来支持的,显然让这样一个Windows程序运行在Linux环境下是因为我们在Linux下安装了Mono。

谈谈Mono跨平台以后
  好了,到现在为止我们基本理清了Mono跨平台的原理。我们知道微软的技术体系在发展过程中因为某些历史遗留问题,.NET程序在不同的Windows版本中的兼容性有时候会出现问题,虽然微软宣布Windows XP停止维护,我们编写Windows应用程序的时候可以忽略对Windows XP版本的支持,可是因为国内用户不喜欢在线更新补丁的这种普遍现状,所以假如让用户在安装程序的时候先去安装.NET框架一定会降低用户体验,其次.NET框架会增加应用程序安装包的大小,所以我们需要一种能够让我们开发的.NET应用程序在脱离微软的这套技术体系时,同时能够安全、稳定的运行,所以我们这里考虑借助Mono让.NET程序脱离.NET框架运行。

首先,我们来说说.NET程序为什么能够脱离.NET框架运行,我们注意到Mono提供了一个Mono运行时,所以我们可以借助这样一个运行时来运行编译器生成的IL代码。我们继续以Hello World为例,我们在使用Mono编译出IL代码以后需要使用Mono运行时来解析IL代码,所以假如我们可以编写一个程序来调用Mono运行时就可以解决这个问题。在这个问题中,其实精简应用程序安装包的大小从本质上来讲就是解决基础类库的依赖问题,因为Mono实现了.NET框架中大部分的基础类库,所以移植.NET应用程序的关键是基础类库的移植,比如WinForm在Linux下的解决方案是GTK,这些细节在考虑跨平台的时候都是非常重要的问题。

小结
  本文从Mono跨平台的原理说起,探讨了.NET应用程序跨平台的可能性和具体实现。跨平台是一个涉及到非常多内容的话题,我个人理解的跨平台是要编写跨平台的代码,这意味着我们在编写程序的时候需要考虑减少对平台特性的移植,比如说Linq是一个非常棒的特性,可是这个特性离开了Windows、离开了.NET就没有办法得到保证,所以如果要让使用了Linq的应用程序跨平台就会是一件非常麻烦的事情!在不同的平台间保持相同的体验很难,就像我们编写的Web程序在不同的浏览器间都有着不一样的表现,所以跨平台这个问题我们就抱着学习的态度来研究吧!


推荐阅读
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文整理了Java中com.evernote.android.job.JobRequest.getTransientExtras()方法的一些代码示例,展示了 ... [详细]
  • 本文介绍了在Ubuntu 11.10 x64环境下安装Android开发环境的步骤,并提供了解决常见问题的方法。其中包括安装Eclipse的ADT插件、解决缺少GEF插件的问题以及解决无法找到'userdata.img'文件的问题。此外,还提供了相关插件和系统镜像的下载链接。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • DSP中cmd文件的命令文件组成及其作用
    本文介绍了DSP中cmd文件的命令文件的组成和作用,包括链接器配置文件的存放链接器配置信息、命令文件的组成、MEMORY和SECTIONS两个伪指令的使用、CMD分配ROM和RAM空间的目的以及MEMORY指定芯片的ROM和RAM大小和划分区间的方法。同时强调了根据不同芯片进行修改的必要性,以适应不同芯片的存储用户程序的需求。 ... [详细]
  • 本文介绍了NetCore WebAPI开发的探索过程,包括新建项目、运行接口获取数据、跨平台部署等。同时还提供了客户端访问代码示例,包括Post函数、服务器post地址、api参数等。详细讲解了部署模式选择、框架依赖和独立部署的区别,以及在Windows和Linux平台上的部署方法。 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
author-avatar
lin
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有