前言
我们知道,Mono 是 .NET Framework 跨平台的开源实现。Mono 的源代码就是金矿,等待我们去挖掘。
目前 Mono 的最新版本是 Mono 2.8.2,可以到 http://ftp.novell.com/pub/mono/sources/mono/ 下载 mono-2.8.2.tar.bz2,文件大小是30MB。可以参阅“在 Ubuntu 10.10 操作系统安装 Mono 2.8.2”一文。
现在,让我们来看看 Mono 是如何实现 .NET Framework Base Class Library 中的 System.Console 类的。Console 类在 CUI 应用程序中有着十分重要的作用。而且,从 .NET Framework 2.0 开始,Console 类除了实现基本的控制台输入/输出功能以外,还实现了诸如更改前景色和背景色等高级功能。下图就是一个例子:
Microsoft 实现的 Console 类只需要考虑 Windows 操作系统的控制台就行了,但是 Mono 的 Console 类就必须考虑跨平台了,要能够工作在 Windows 和 Unix 操作系统中。所以是比较复杂的。
准备在自己的工作目录下编译出 Console.dll 程序集
为了研究 Console 类的源代码,找出和 Console 类密切相关的源代码,我准备从 Console.cs 出发在自己的工作目录下编译出一个 Console.dll 程序集。我们来看看 Console 类的源代码位于 Mono 体系的什么位置:
ben@ben-1520:~$ cd src/mono-2.8.2
ben@ben-1520:~/src/mono-2.8.2$ find . -name Console.cs
./mcs/class/corlib/System/Console.cs
ben@ben-1520:~/src/mono-2.8.2$
哦,因为 Console 类定义于 .NET Framework 最核心的 mscorlib.dll 程序集,并且位于 System 命名空间。所以上面的命令就显示这样的结果了。
我们现在准备一个工作目录:
ben@ben-1520:~$ cd ~/work ben@ben-1520:~/work$ mkdir Console ben@ben-1520:~/work$ cd Console ben@ben-1520:~/work/Console$
先把上述 Console.cs 从 Mono 的源代码目录拷贝到工作目录中,然后使用 C# 编译器进行编译,根据出错信息来决定还要拷贝哪些文件。最终,发现需要 Mono 源代码 mcs/build/common、mcs/class/corlib/System 和 mcs/class/corlib/System.IO 目录下的 23 个 C# 源程序文件,在上面三个目录分别是 2 个、18 个和 3 个。我编写了一个如下内容的 mksrc.sh 脚本,用于将 Mono 2.8.2 的源代码与 Console 类相关的源文件复制到工作目录中:
01: cd ~/work/Console 02: rm -rf mcs 03: mkdir mcs 04: mkdir mcs/build 05: mkdir mcs/build/common 06: mkdir mcs/class 07: mkdir mcs/class/corlib 08: mkdir mcs/class/corlib/System 09: mkdir mcs/class/corlib/System.IO 10: cd ~/src/mono-2.8.2/mcs/build/common 11: cp Locale.cs MonoTODOAttribute.cs ~/work/Console/mcs/build/common 12: cd ~/src/mono-2.8.2/mcs/class/corlib/System.IO 13: cp Stream.cs UnexceptionalStream*er.cs ~/work/Console/mcs/class/corlib/System.IO 14: cd ~/src/mono-2.8.2/mcs/class/corlib/System 15: cp Buffer.cs Control*.cs CStream*.cs *Term*.cs *Console*.cs ~/work/Console/mcs/class/corlib/System 16: cd ~/work/Console/mcs/class/corlib/System 17: rm ConsoleColor.cs ConsoleKey.cs ConsoleModifiers.cs ConsoleSpecialKey.cs 18: cd ~/work/Console
从 Stream.cs 中分离出 NullStream 类的源代码
在 Mono 的源代码的 Stream.cs 文件中,包含以下三个类:
- public abstract class Stream : MarshalByRefObject, IDisposable
- internal class NullStream : Stream
- internal class SynchronizedStream : Stream
因为我们需要用到 NullStream 类,所以将工作目录中 mcs/class/corlib/System.IO/ 目录下的 Stream.cs 改名为 NullStream.cs,然后在该文件中删除另外两个类的源代码,只保留 NullStream 类的源代码。
虽然 Mono 是一项伟大的开源工程,大师们写的代码也非常精彩。但是在这里还是有点疏忽了。如果 Mono 的源代码的 Stream.cs 文件中只包含 Stream 类,而把 NullStream 类的源代码放在 NullStream.cs 文件中,把 SynchronizedStream 类的源代码放在 SynchronziedStream.cs 文件中。我们也就可以直接使用 NullStream.cs 文件了,不用象现在一样又是改名,又是删除代码。
编写辅助代码
现在让我们编写一些简单的辅助代码,以便能够编译生成 Console.dll 程序集文件。
ben@ben-1520:~/work/Console$ mkdir Skyiv ben@ben-1520:~/work/Console$ cd Skyiv ben@ben-1520:~/work/Console/Skyiv$ gedit AssemblyInfo.cs Environment.cs ExtensionMethods.cs MonoIO.cs Stub.cs ben@ben-1520:~/work/Console/Skyiv$
下面就是 AssemblyInfo.cs,仅仅是简单地使用 CLSCompliantAttribute 标记程序集,指示该程序集符合公共语言规范。
1: using System; 2: 3: [assembly:CLSCompliant(true)]
下面就是 Environment.cs,主要是因为在 Console.cs、ConsoleDrivers.cs 这两个源程序中使用了 Environment 静态类的 IsRunningOnWindows 静态属性,而这个属性是 internal 的。所以我们必须自己提供一个,不然程序无法通过编译。我们还必须提供在其他源程序中有使用的 NewLine 属性以及 Exit 和 GetEnvironmentVariable 方法。这三个成员原来是 public 的,但是我们自己用的话,使用 internal 来修饰是没有问题的。注意,IsRunningOnWindows 属性指示是否运行在 Windows 操作系统中,这在 Microsoft 的 .NET Framework 中是没有的,因为 Microsoft 的实现总是运行在 Windows 操作系统中。Mono 为了跨平台才需要这个属性。
01: namespace System 02: { 03: static class Environment 04: { 05: internal static bool IsRunningOnWindows { get { return false; } } 06: internal static string NewLine { get { return "\n"; } } 07: internal static void Exit(int code) { } 08: internal static string GetEnvironmentVariable(string value) { return value; } 09: } 10: }
下面就是 ExtensionMethdos.cs,仅提供一个扩展方法,即用于 StreamReader 类的 DataAvailable 方法。在 Mono 的StreamReader 类中,DataAvailable 方法是 internal 的,所以我们必须自己提供一个。这个 DataAvailable 方法在 TermInfoDriver.cs 源程序中有使用。
01: namespace System.IO 02: { 03: static class ExtensionMethods 04: { 05: internal static bool DataAvailable(this StreamReader reader) 06: { 07: return reader.Peek() != -1; 08: } 09: } 10: }
下面就是 MonoIO.cs。这个 MonoIO 类在 Console.cs 和 ConsoleDriver.cs 源程序中有使用。很奇怪,如果不提供这个 MonoIO 类,在 Ubuntu 10.10 操作系统的 Mono 2.8.2 环境下是能够编译成功的,但在 Windows 操作系统的 .NET Framework 4 环境下无法编译成功。
1: namespace System.IO 2: { 3: static class MonoIO 4: { 5: internal static IntPtr ConsoleInput { get { return (IntPtr)0; } } 6: internal static IntPtr ConsoleOutput { get { return (IntPtr)1; } } 7: internal static IntPtr ConsoleError { get { return (IntPtr)2; } } 8: } 9: }
下面就是 Stub.cs,提供了 Encoding 和 TextWriter 类的一些 internal 静态成员,以及 FileStream 类的 internal 构造函数和一个隐式转换操作符,用以把我们的 Skyiv.Stub.FileStream 隐式转换为 System.IO.FileStream。这些东东都只在 Console.cs 源程序中使用。
01: using System; 02: using System.IO; 03: 04: namespace Skyiv.Stub 05: { 06: static class Encoding 07: { 08: internal static System.Text.Encoding UTF8Unmarked { get { return System.Text.Encoding.UTF8; } } 09: internal static void InternalCodePage (ref int code_page) { } 10: } 11: 12: static class TextWriter 13: { 14: internal static System.IO.TextWriter Synchronized(System.IO.TextWriter writer, bool neverClose) 15: { 16: return writer; 17: } 18: } 19: 20: sealed class FileStream 21: { 22: internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool isZeroSize) 23: { 24: } 25: 26: public static implicit operator System.IO.FileStream(FileStream value) 27: { 28: return null; 29: } 30: } 31: }
修改 Console.cs 源程序
现在,修改 Console.cs 源程序,对第 127、131、138、144、150 和 187 行共六处的 Encoding、TextWriter 和 FileStream 前面加上“Skyiv.Stub.”,以便调用我们自己的版本。
生成编译响应文件并编译
ben@ben-1520:~/work/Console/Skyiv$ cd .. ben@ben-1520:~/work/Console$ ls Skyiv/*.cs mcs/build/*/*.cs mcs/class/*/*/*.cs > make.rsp ben@ben-1520:~/work/Console$ gedit make.rsp ben@ben-1520:~/work/Console$ dmcs @make.rsp ben@ben-1520:~/work/Console$
在使用 gedit 编辑 make.rsp 时,添加如下前四行的内容:
01: -t:library 02: -out:Console.dll 03: -unsafe 04: -nowarn:436 05: mcs/build/common/Locale.cs 06: mcs/build/common/MonoTODOAttribute.cs 07: mcs/class/corlib/System/Buffer.cs 08: mcs/class/corlib/System/ConsoleCancelEventArgs.cs 09: mcs/class/corlib/System/ConsoleCancelEventHandler.cs 10: mcs/class/corlib/System/Console.cs 11: mcs/class/corlib/System/ConsoleDriver.cs 12: mcs/class/corlib/System/ConsoleKeyInfo.cs 13: mcs/class/corlib/System/ControlCharacters.cs 14: mcs/class/corlib/System/CStreamReader.cs 15: mcs/class/corlib/System/CStreamWriter.cs 16: mcs/class/corlib/System/IConsoleDriver.cs 17: mcs/class/corlib/System.IO/NullStream.cs 18: mcs/class/corlib/System.IO/UnexceptionalStreamReader.cs 19: mcs/class/corlib/System.IO/UnexceptionalStreamWriter.cs 20: mcs/class/corlib/System/KnownTerminals.cs 21: mcs/class/corlib/System/NullConsoleDriver.cs 22: mcs/class/corlib/System/TermInfoBooleans.cs 23: mcs/class/corlib/System/TermInfoDriver.cs 24: mcs/class/corlib/System/TermInfoNumbers.cs 25: mcs/class/corlib/System/TermInfoReader.cs 26: mcs/class/corlib/System/TermInfoStrings.cs 27: mcs/class/corlib/System/WindowsConsoleDriver.cs 28: Skyiv/AssemblyInfo.cs 29: Skyiv/Environment.cs 30: Skyiv/ExtensionMethods.cs 31: Skyiv/MonoIO.cs 32: Skyiv/Stub.cs
终于,我们成功地使用 C# 编译器编译生成了 Console.dll 程序集文件。当然,这个 Console.dll 仅仅是用来研究 Mono 的源代码,是不能正常工作的,因为我们使用了一些自己编写的辅助代码替代了 Mono 系统的。
查看源程序的目录结构
下面就是我们工作目录的内容:
各种类型之间的关系图
在上图中,最核心的类型如下所示:
- Console: public static class,调用下面的 ConsoleDriver 类的静态方法和静态属性来干活。
- ConsoleDriver: internal class,内部持有一个类型为 IConsoleDriver 接口的 internal 静态字段。
- IConsoleDriver: internal interface,以下三个类均实现 IConsoleDriver 接口。
- NullConsoleDriver: internal class,只实现最基本的控制台输入/输出功能,用于哑终端等情况。
- TermInfoDriver: internal class,用于 Unix 操作系统的各种终端。
- WindowsConsoleDriver: internal class,用于 Windows 操作系统的控制台。
可以点击下载 Console.7z。