代码改变世界

在.NET平台上使用Scala语言(下):分析

2009-12-21 00:30  Jeffrey Zhao  阅读(7547)  评论(20编辑  收藏  举报

上一篇文章里我们简单尝试了在Scala里编写.NET应用程序。这个过程并不困难,因为似乎Scala官方已经对此已经有较好的支持了。我们要做的只是“获取工具”,“编译成IL”,最后再“生成程序集”即可。那么,这些工具究竟做了些什么,Scala究竟又是如何支持.NET平台的,它的可用性究竟如何,我们还需要进一步的分析及尝试。

现在看第一个问题。我们知道从Scala源代码生成IL文件的脚本是scalac-net.bat。如果需要了解它做的事情,最直接的方法莫过于查看其中的内容。如果要看明白它的代码,可能需要我们对cmd命令有些了解——不过我也只是略知一二罢了,如果您对其了解不多其实也没有太大关系。经过合理推测,我们知道scalac-net.bat本身不会有什么功能,它只是调用编译器而已。因此,这个脚本文件的职责,无非是收集参数并执行编译器。于是我们打开scalac-net.bat,在众多for/if之中可以发现它最后执行了这样一个命令:

%_JAVACMD% -Xbootclasspath/a:"%_BOOT_CLASSPATH%" %_JAVA_OPTS% %_PROPS% -cp "%_EXTENSION_CLASSPATH%" scala.tools.nsc.Main -target:msil %_ARGS%

那么我们再调用scalac-net.bat的时候这行命令究竟是什么呢?对于此类问题,我们可以再它前面加上ECHO命令,即:

ECHO %_JAVACMD% -Xbootclasspath/a:"%_BOOT_CLASSPATH%" %_JAVA_OPTS% %_PROPS% -cp "%_EXTENSION_CLASSPATH%" scala.tools.nsc.Main -target:msil %_ARGS%

ECHO可以视为cmd的print命令,我们可以用它来观察和学习脚本。再次运行,便可以看到编译器的调用方式了:

D:\scala-2.7.7.final\code> ..\bin\scalac-net.bat test.scala
java -Xbootclasspath/a:"D:\SCALA-~1.FIN\bin\..\lib\scala-library.jar" -Xmx256M -Xms16M -Dscala.home="D:\SCALA-~1.FIN\bin\.." -Denv.classpath="" -Dmsil.libpath="D:\SCALA-~1.FIN\bin\..\lib\predef.dll;D:\SCALA-~1.FIN\bin\..\lib\scalaruntime.dll;D:\SCALA-~1.FIN\bin\..\lib\mscorlib.dll" -Dmsil.ilasm="c:\Windows\Microsoft.NET\Framework\v2.0.50727\ilasm.exe"  -cp "D:\SCALA-~1.FIN\bin\..\lib\mscorlib.dll;D:\SCALA-~1.FIN\bin\..\lib\predef.dll;D:\SCALA-~1.FIN\bin\..\lib\sbaz-tests.jar;D:\SCALA-~1.FIN\bin\..\lib\sbaz.jar;D:\SCALA-~1.FIN\bin\..\lib\scala-compiler.jar;D:\SCALA-~1.FIN\bin\..\lib\scala-dbc.jar;D:\SCALA-~1.FIN\bin\..\lib\scala-library.jar;D:\SCALA-~1.FIN\bin\..\lib\scala-swing.jar;D:\SCALA-~1.FIN\bin\..\lib\scalaruntime.dll" scala.tools.nsc.Main -target:msil test.scala

可以看出,这是在运行一个java程序,并且提供了很多参数。不过参数很多,内容也很乱。不过乱的原因在于其中对于各式命令或者库文件的引用都使用的完整路径。经过换行,相对路径调整,并去除一些明显无用的参数内容(如-cp,即classpath里的dll文件),我们发现其实大约这样的:

D:\scala-2.7.7.final\code> ..\bin\scalac-net.bat test.scala
java
  -Xbootclasspath/a:"..\lib\scala-library.jar"
  -Xmx256M
  -Xms16M
  -Dscala.home=".."
  -Denv.classpath=""
  -Dmsil.libpath="..\lib\predef.dll;..\lib\scalaruntime.dll;..\lib\mscorlib.dll"
  -Dmsil.ilasm="c:\Windows\Microsoft.NET\Framework\v2.0.50727\ilasm.exe"
  -cp "..\lib\sbaz-tests.jar;..\lib\sbaz.jar;..\lib\scala-compiler.jar;..\lib\scala-dbc.jar;..\lib\scala-library.jar;..\lib\scala-swing.jar;"
  scala.tools.nsc.Main
  -target:msil
  test.scala

您可以执行整理后的命令,效果一致。经过一番摸索,再配合scalac.bat -help的输出,我们可以观察出命令的具体意义,例如:

  • Scala编译器其实是一个Java程序,入口是scala.tools.nsc.Main
  • -Dmsil.libpath表明编译时所引用的.NET程序集。
  • -Dmsil.ilasm表明ilasm.exe文件的路径,如果需要直接生成程序集则需要进行指定。

那么假设我们已经编译生成了一个test.exe文件,现在使用.NET Reflector来观察它的信息:

可见test.exe依赖另外三个程序集,它们按照依赖关系分别是:

  1. mscorlib.dll:定义了一个程序的基础需求。
  2. scalaruntime.dll:依赖mscorlib.dll,定义了Scala语言中的各种基础类型。
  3. predef.dll:依赖mscorlib.dll及scalaruntime.dll,定义了scala的基础类库。

看上去并没有什么问题,不是吗?但是,经过简单的思考,似乎又不是那么一回事情。好比,您是否觉得一个Scala程序的依赖实在少了一些?例如您平时写程序时能否仅仅依赖mscorlib.dll,而不使用System.dll或System.Core.dll等其他程序集?那么,为什么Scala便可以仅仅基于mscorlib.dll而构建predef.dll呢?为此,我们简单比较一下predef.dll与Java平台上Scala的标准库——scala-library.jar。首先是predef.dll:

其次是scala-library.jar中的定义:

可以看出,Scala标准库中定义了比predef.dll中更多的类库。例如Scala一直引以为傲的Actor类库,即scala.actors命名空间。换句话说,.NET平台上的Scala并不支持Java平台上的许多高级功能——这样似乎可以理解为什么它只需依赖mscorlib.dll就足够了。不过“标准类库少”是坏事还是好事倒也不能轻易下结论。

如果说这是坏事——类库少自然是坏事。那么“好事”又从何谈起呢?我的理解是:Scala毕竟是为Java平台设计的语言,它本可不必对.NET提供支持。也就是说,.NET平台只是Scala的“副业”。如果说,因为IL和Java Code相近(或者说有很大程度的“包含”关系),那么编译器在写起来相对问题不大,但“类库”就无法讨巧了。如果.NET类库跟得太紧,那么我反而要怀疑它的质量是否成熟。在使用Scala时,我主要关注的其实是“编译器”及最终生成的IL,我并没有期望能够使用Scala在.NET平台上编写程序。对此,编译器是否成熟对我们来说可能更加重要。因此,.NET类库少也并不是坏事——毕竟.NET Framework已经提供了足够的功能,不是吗?

是吗?

如果您比较心细,您应该已经从第一幅图中看出问题来了。我特意将焦点放在mscorlib.dll上,目的便是展示它的版本信息,即:

mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

如果您关注一下平时写程序时所使用的mscorlib.dll,会发现它是这样的:

mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e

为什么会不一样?那是因为Scala所使用的mscorlib.dll是“自己带来的”,并不是系统安装的.NET Framework。那么它究竟是什么呢?展开后便可一目了然:

因为它并不是微软提供的.NET Framework,而是Mono平台提供的.NET类库!如果您使用.NET Reflector来查看其中某些类库的具体实现,会发现它和.NET Framework中实现有很明显的不同(如字符串的连接操作)。

有朋友可能会想,这问题应该不大,只要在编译时提供机器上安装的程序集不就可以了吗?但问题是,微软发布的.NET Framework,他们都是依赖于mscorlib.dll——这是每个.NET程序的核心,例如其中定义了一些基础数据类型。想象一下,Scala编译器使用的是Mono里定义的String类型,那么如何把它传递给MS .NET里定义的方法呢?要知道后者使用的可是MS .NET里的String!

经过多番尝试,我无法让Scala编译器使用MS .NET里的程序集——即便是再简单的case。当然,目前我还无法确定这是Scala编译器的问题,亦或的确只是类库的关系。不知道修改一下Scala的编译器或是基于Mono进行编译能否成功,我会再进行更多尝试——如果某一天您发现我又写了一篇“下”,而现在这篇变成了“中”……也是非常正常的事情。:)