Mono是如何工作的

49

我曾在Visual Studio中使用C#和.NET,并在openSUSE Linux上玩过一点Mono,但我不是很了解它的工作原理。

如果我在Windows上用.NET编写应用程序,这与Mono有何关系?我不能在Linux上执行一个Windows .exe文件,除非使用Wine,因此它不能帮助我执行在Windows中开发的应用程序。

其目的纯粹是为了在Linux(和其他系统)上拥有一个等效的.NET库,以便于跨平台开发吗?例如,如果我是一家企业,想要吸引Linux客户,但又想使用.NET,那么Mono就应该是我的选择吗?或者还有什么我所不知道的东西吗?

8个回答

132
这是一个旧问题(已经有了选定的答案),但我认为没有人真正回答好这个问题。
首先,一些背景知识。。。
.NET是如何工作的?
传统的Windows .EXE文件是一个二进制文件,表示一系列计算机语言指令,你的电脑能够理解并调用Win32 API,这是Windows提供的应用程序可利用的服务的一部分。使用的计算机语言非常特定于您的计算机类型和Win32调用使可执行文件非常依赖于Windows。.NET可执行文件不是这样的。
重要的是要意识到,.NET可执行文件(.EXE文件)实际上并不是原生的Windows应用程序。Windows本身不知道如何运行.NET可执行文件中的代码。你的电脑也不懂。
与Java类似,.NET应用程序由称为CIL(Common Intermediate Language)的指令组成,它是一种可以看作是不存在的理想计算机的机器语言。在.NET中,这个理想化机器的软件实现称为公共语言运行时(CLR)。在Java世界中,相当于CLR的是Java虚拟机(JVM)。在Java中,等效于CIL的被称为Java字节码。CIL有时被称为MSIL(Microsoft中间语言)。
CIL被设计为在CLR(一种理想化的机器)上运行,但除此之外,它是跨平台的,这意味着CIL不关心您使用的计算机种类或正在运行的操作系统。
就像你需要在要运行Java的每个平台上都有一个本地版本的Java JVM一样,你需要一个本地版本的CLR来运行.NET CIL可执行文件。CLR是一个原生的Windows应用程序,就像上面描述的传统Win32 EXE文件一样。CLR本身特定于设计它运行的Windows实施和计算机体系结构。
无论您从哪个.NET语言开始(C#,VisualBasic,F#,IronPython,IronRuby,Boo等),它们都会被编译成CIL字节码。您可以轻松地将CIL程序“反汇编”为一种面向对象的汇编语言形式,这对人类易于阅读。你可以直接用CIL编写程序,但很少有人这样做。
在Windows上,CLR在运行可执行文件之前就实时(JIT)编译了CIL代码。这意味着CIL字节码被转换(编译)为实际在您的计算机上运行的本机机器码。CLR的这一部分称为JIT编译器或通常称为JIT。
迄今为止,Microsoft已经发布了四个版本的CLR:1.0、1.1、2.0和4.0。如果您想运行针对该运行时的.NET可执行文件,则需要在计算机上安装正确版本的CLR。CLR 2.0支持.NET 2.0、3.0和3.5应用程序。对于其他版本的.NET,.NET版本与CLR版本映射清晰。除了JIT/CLR之外,.NET还提供了一系列库(程序集),它们组成了.NET框架的其他部分,并提供了许多功能和服务,供.NET应用程序调用。这些程序集中的绝大多数都是纯CIL代码,在CLR上运行。在Windows上,一些程序集也会调用Win32 API。安装.NET时,您将安装CLR、类库(框架)以及一堆开发工具。每个CLR版本通常需要完整的这些“框架”程序集集合。某些.NET版本(例如3.0和3.5)添加了额外的框架程序集,而没有更新CLR或与该CLR关联的现有程序集。
Windows中的.EXE文件以可移植可执行(PE)文件格式交付,其中包含描述可执行文件并将文件标识为.NET文件或本地Win32文件的头文件。当Windows尝试运行.NET文件时,它会看到这个头文件,并自动代表您调用CLR。这就是为什么.NET EXE文件似乎可以在Windows上本地运行的原因。
好的,那么Mono是如何工作的呢?
Mono在Linux、Mac和其他平台上实现了CLR。Mono运行时(CLR)是一个原生应用程序,主要使用C语言编写,并编译成针对所设计运行的计算机系统的机器语言代码。与Windows上的情况类似,Mono运行时是针对操作系统和使用的机器类型特定的。
就像在Windows上一样,Mono运行时(CLR)即时编译您的.NET可执行文件中的CIL字节码以生成计算机可以理解和执行的本地代码。这样,.NET文件对Linux与Windows一样“本地”。
要将Mono移植到新架构,需要移植JIT/CLR。这就像将任何原生应用程序移植到新平台一样。
.NET代码在Linux或Mac上运行得有多好,实际上只是CLR在这些系统上实现得有多好的问题。理论上,Mono CLR可以比MS版本的.NET在Windows上更好地执行.NET代码。但实际上,MS的实现通常更优秀(虽然并非所有情况都如此)。
除了CLR之外,Mono还提供了组成.NET框架的大部分其余库(程序集)。与Microsoft的.NET版本一样(事实上更甚),Mono程序集也是以CIL字节码形式提供的。这使得可以将从Mono获取的*.dll或*.exe文件作为CIL是这些系统的CLR实现的“本地”语言,在Windows、Mac或Linux上运行而无需修改。
就像在Windows上一样,Mono支持多个CLR版本和相关程序集:
早期版本的Mono(1.2之前?)只支持CLR 1.0或1.1。 直到其自己的2.0版本,Mono才支持2.0框架的大部分内容。
Mono版本直到2.4版本为止,都支持CLR 1.1和CLR 2.0应用程序。

从Mono 2.6开始,添加了CLR 4.0,但CLR 2.0仍然是默认值。

从Mono 2.8开始,CLR 4.0成为默认值,不再支持CLR 1.1。

Mono 2.10仍然将CLR 4.0作为默认值,并继续支持CLR 2.0。

就像真正的.NET一样,但情况要少得多,有一些Mono程序集调用本地库。为了使System.Drawing程序集在Mono上工作,Mono团队编写了一个Linux程序来模拟Win32 API的GDI+部分。这个库叫做“libgdiplus”。如果你从源代码编译Mono,你会注意到你需要在构建“mono”之前构建这个“libgdiplus”文件。在Windows上不需要“libgdiplus”,因为Win32 API的GDI+部分已经是Windows的一部分。将Mono完全移植到新平台需要将这个“libgdiplus”库进行移植。

在.NET库的设计过程中受到Windows设计的影响较大且不适合Mac或Linux系统的区域,Mono团队编写了.NET框架的扩展。Mono扩展也只是CIL字节码,在.NET上通常也能正常工作。

与Windows不同,Linux通常不会检测.NET可执行文件并默认启动CLR。用户通常必须直接运行CLR,例如输入“mono appname.exe”。这里,“mono”是实现CLR的应用程序,“appname.exe”是包含要执行的.NET代码的EXE文件。

为了方便用户,Mono应用程序通常会被包装在一个启动CLR的shell脚本中。这样隐藏了与Windows一样使用CLR的事实。还可以告诉Linux在遇到使用PE文件格式的文件时启动CLR。通常不这样做,因为PE文件格式也用于本地Win32 Windows可执行文件,当然CLR(Mono)不支持。

在我所知道的情况下,没有技术原因可以阻止Linux使用PE启动器,然后根据需要启动理解本机Windows代码的系统(如Wine)或CLR(Mono)。

来回转移

任何坚持“完全托管”的.NET代码,这意味着它不调用非.NET代码,应该在所有平台上都能在Mono上正常工作。我经常在Linux和Mac上使用从Windows编译的.NET程序集(我没有其中的代码)。

我还可以拿到我在Mono上编译的任何代码,并在Windows上运行.NET。例如,我可以向客户端提供我用Mono编译的代码,并不用担心他是32位还是64位Windows。当然,客户端需要安装正确版本的.NET(正确的CLR)。 CLR 2.0已经存在很长时间了,你可以肯定几乎所有Windows用户都已经安装了它。Mono编译器和其他代码也只是CIL可执行文件,因此如果您喜欢,它们可以在Windows上运行得很好。
Mono的兼容性足够好,以至于实际微软代码的大部分,如ASP.NET MVC,可以从实际的MS版.NET中获取(如果合法),并在Mac或Linux上运行。总的来说,Mono团队在实现CLR和框架的其余部分(类库/程序集)方面做得非常出色。
在Windows上,Internet Information Server(IIS)知道如何调用CLR来执行.NET作为Web应用程序的一部分。在Linux / Mac上,有一个Apache模块(mod_mono),提供类似的功能给Apache Web服务器。这个应用程序是用C编写的,必须也适配到新的架构上。
这次讨论确定了Mono的部分是作为“本机”可执行文件构建的,并且必须存在于要运行.NET应用程序的系统上。
以下三个组件,再加上类库,提供了一个.NET环境,看起来“本地”于您需要运行的.NET可执行文件。
- CLR(包括JIT编译器)-通常称为Mono - libgdiplus(对于不支持GDI + API的系统[仅适用于Windows]) - mod_mono(允许Apache调用CLR以执行.NET Web应用程序)
这就是Mono的工作原理。

2
非常好的全面撰写。不过,我有一个问题。当您运行已编译的 .net exe 时,操作系统如何知道它需要通过 JIT 运行还是不需要?它难道不必利用一些 PE32 可执行文件黑客技巧来运行 JIT(如果需要)吗?但是,如果这样做,会使其平台相关。由于另一个平台不了解 PE32,因此该怎么使用该 exe? - greatwolf
1
@Victor - PE(EXE)文件结构包含一个头部。该头部的“可选”部分将文件标识为.NET可执行文件。在Windows XP之前,可执行文件加载器不了解.NET,并需要本地跳转指令来调用CLR。自XP以来,加载器查看头部并直接调用CLR(无需本地代码)。CLR调用JIT。对于Mono,通常在启动时手动调用CLR(例如通过键入“mono somefile.exe”)。PE“hackery”只是头部中的数据。Mono和现代Windows都不使用EXE中的任何本地代码。 - Justin
2
+1,非常好且全面的回答。略微挑剔还是可以的:“Mono实现了在Linux、Mac和其他平台上的CLR。” 严格来说,它并没有。根据标准(ECMA-335,第5版,分区1,第12章),Mono运行时实现了VES(虚拟执行系统),CLI的运行时部分。CLR仅仅是微软的一个VES实现。 - Frank
理论上,Mono CLR 可以在这些系统(Linux 和 Mac)上比 MS 版本的 .NET 在 Windows 上执行 .NET 代码更好。为什么呢?您能详细说明一下吗? - Drasive
4
如果我能再增加10个赞,我会这样做。这对我理解Mono的整体情况有很大帮助。干得好! - Everyone
显示剩余3条评论

22

Windows EXE包含多个“部分”。简单来说,.NET代码(即MSIL)只是EXE的一部分,而EXE中还有一个“真正”的本地Windows部分,它作为某种启动器用于.NET Framework,然后执行MSIL。

Mono将只取出MSIL并执行它,忽略本地Windows启动器的内容。

再次强调,这是一个简化的概述。

编辑:我担心我的深入了解不够详细(我大致知道PE头是什么,但不是很清楚细节),但我发现以下链接很有帮助:

.NET程序集结构 - 第二部分

.NET基础知识 - .NET程序集结构


有没有可能提供稍微详细一些的版本? :) - DavidG
我担心我对深层细节的理解还不够好(我大致知道PE头是什么,但并不真正了解细节),但我发现这些链接很有帮助:http://is.gd/4n4i http://is.gd/4n4n - Michael Stum
4
EXE 文件中没有真正的“本地”的 Windows 部分。标头仅仅是一种描述而已,它可能指向 EXE 或 DLL 中的入口点,但这个入口点只能在托管环境中被执行或者不能执行。实际的“启动器”是外部于可执行文件的,可能是 Windows 本地程序,也可能是 CLR 的一部分(适用于 .NET 和 Mono)。 - Justin
@Justin 我相信 .net 仍然包含一个 Windows Native stub。请记住,Windows 2000 不知道 .net,所以 .exe 需要在其中包含一个 Windows Loader。后来的 Windows 操作系统中的 EXE Loaders 理解 .net 并可以跳过它。Mario Hewardt 的《高级 .NET 调试》一书中有一个解释。不确定这是否仍适用于 .net 4,因为它只运行在较新的 Windows 版本上。同时希望我正确理解了你的评论 :) - Michael Stum
4
Windows使用DLL文件(mscoree.dll)来初始化CLR并启动.NET可执行代码。这个“CLR启动器”是独立于EXE文件本身的外部程序。对于早期的Windows版本,你说的没错,在EXE文件中有一条跳转指令将执行权转移到这个DLL文件上。从XP开始,Windows加载器会检测.NET文件并自行加载CLR。现代的Windows和Mono都不需要或使用跳转指令。在所有情况下(包括Mono、旧版和新版Windows),CLR本身会检查EXE文件的入口点,然后执行相应的CIL字节码。 - Justin

11

实际上,您可以在Linux上使用Mono运行.NET .exe文件。这不需要Wine。事实上,Mono将程序编译为.exe文件,可以在Linux上使用Mono或在Windows上作为可执行文件运行。


1
正确。Mono实现了CLR(.NET),而Wine则试图实现完整的本地Windows环境(Win32 API和内核函数)。 - Justin

3

Mono是Microsoft .NET CLR(通用语言运行时)的开源实现。这是运行部分.NET程序的东西,这些程序不是以本地代码而是以CIL(通用中间语言)编写的,它是一种语言和机器中立的中间语言。运行时接收该中间代码并将其转换为机器代码。

在当前Mono的状态下,您可以在任何Mono运行的地方运行使用.NET主要部分(mscorlib.dll)的.NET程序,而不仅仅是Windows。


2
但是需要注意的是,尽管Mono是开源的,但你不能仅依赖它提供完整的.NET实现,因为它有一些控件是无法工作的。此外,你还必须小心应用程序所使用的P/Invoke,例如,你的应用程序将与win32下的MCI(多媒体控制器接口)通信。然而,我曾经使用Mono编写过GTK#应用程序,并且也使用过我的Windows应用程序,它们可以在不进行任何重新编译的情况下正常工作,正如我们的同行程序员所提到的那样,即Mono是微软.NET的一个开源替代品。默认情况下,如果你正在构建WinForms或Gtk#应用程序,Mono会编译并为每个文件创建一个.exe程序集,当然,如果你想的话,它也会创建一个动态链接库(DLL),几乎和.NET一样。只是建议尝试编写一些使用Gtk#的应用程序(使用MonoDevelop IDE,它有一个名为stetic的内置GUI设计器)。当然,Mono也可以成为Web服务的一个很好的替代品,你可以在.NET上创建它们,并在Apache上托管它们(因为Linux托管现在比Windows更便宜),Web服务和其他ASP.NET应用程序将通过必须包含在Apache中的mod_mono模块在apache下工作。

虽然有些偏离主题,但我只是想从我的经验中告诉你一些情况。


1
另外,如果你的目标是将应用程序从Windows迁移到Linux,你还可以参考MoMA。
Mono Migration Analyzer(MoMA)工具可以帮助你在将.NET应用程序移植到Mono时确定可能存在的问题。它可以帮助精确定位平台特定的调用(P/Invoke)和Mono项目尚未支持的区域。
点击这里查看MoMA。
这是一个网络应用程序,可以比较Mono和.NET Framework 3.5已经实现的BCL类型。

http://mono.ximian.com/class-status/2.0-vs-3.5/index.html


0

进一步说,我认为你需要在Mono上重新编译应用程序才能在Mono运行时上运行。可能会有例外情况。我只是稍微尝试了一下Mono,并且我总是重新编译源代码。我从未尝试过直接在Mono上运行.NET应用程序。

此外,据说Mono 1.9完全符合.NET 2.0标准。Mono Olive和Moonlight应该添加.NET 3.0(不包括WPF)和Silverlight功能。


1
只是一条注释。你不必重新编译 .NET 应用程序就可以在 Mono 上运行。 - Justin

0

1
虽然这理论上回答了问题,但最好在此处包含答案的基本部分,并提供参考链接。 - Adi Lester

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接