X# 编译器还会在启动代码中做一些额外的 "手脚"。
XBase 代码可能有所谓的 INIT 过程,其中包含启动时将执行的代码。例如,在 VO GUI 类中有一个过程
PROCEDURE __WCInitCriticalSections() _INIT1
为确保在启动时调用这些程序,编译器会在函数类中生成一至三个特殊方法,名称分别为 $Init1、$Init2、$Init3 和 $Exit。VOGuiclasses 程序集中有两个这样的方法($Init1 和 $Init3)。在 ILSpy 中查看该方法的内容,可以看到如下内容(使用 C# 反编译)
// VOGUIClasses.Functions
using System.Runtime.CompilerServices;
[CompilerGenerated]
public static void $Init1()
{
__WCInitCriticalSections();
}
在某些程序集中,你会发现 $Init1() 方法是存在的,但却是空的。例如,在 VOSystemClasses:
// VOSystemClasses.Functions
using System.Runtime.CompilerServices;
[CompilerGenerated]
public static void $Init1()
{
}
我们创建这些空初始化器的原因如下:
我们的许多 VO 客户都是通过调用 CreateInstance() 来间接实例化类的。
要做到这一点,类必须在运行时可用。
我们使用的 Roslyn 编译器非常聪明。当 exe 不引用外部程序集中的任何类型或方法时,编译器不会将外部程序集的引用包含到 exe 中。
因此,如果您在应用程序中调用 CreateInstance(#DbServer),但从未声明 DbServer 类型的变量(例如,仅声明 DataServer 类型的变量),那么即使您在编译时包含对 VORDDClasses 程序集的引用,您的主应用程序中也不会包含对 RDDClasses 的引用。
这就是我们生成空 $Init() 方法的原因。
在生成主程序时,X# 编译器会检查所有引用的程序集,并查找所有 $Init1、$Init2、$Init3 和 $Exit 方法,然后生成一些代码来调用所有这些方法。因此,所有引用的程序集都将在启动时加载。您可以在编译器生成的 <module> 类中的特殊编译器生成方法中找到这段启动代码:
// <Module>
using Application1.Exe;
using System;
using System.Runtime.CompilerServices;
using VOWin32APILibrary;
using XSharp;
using XSharp.RT;
[CompilerGenerated]
internal static void $AppInit()
{
try
{
RuntimeState.AppModule = typeof(Application1.Exe.Functions).Module;
RuntimeState.CompilerOptionOVF = false;
RuntimeState.CompilerOptionVO11 = false;
RuntimeState.CompilerOptionVO13 = false;
RuntimeState.Dialect = XSharpDialect.VO;
VOWin32APILibrary.Functions.$Init1();
XSharp.RT.Functions.$Init1();
Application1.Exe.Functions.$Init1();
}
catch (Exception innerException)
{
throw new Exception("Error when executing code in INIT procedure(s)", innerException);
}
}
正如您所看到的,这段代码不仅调用了几个 $Init1() 方法,还在 X# 运行时中设置了一些属性。您还可以看到,上面的代码是在 VO Dialect 中调用的。
如果查看 Start 函数的生成代码,它看起来是这样的:
// Application1.Exe.Functions
using System;
using XSharp.RT;
public static void Start()
{
try
{
<Module>.$AppInit();
XSharp.RT.Functions.QOut("Function Start");
}
finally
{
<Module>.$AppExit();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
编译器创建了一个 try finally 块,并调用 $AppInit() 来初始化运行时状态并调用每个引用程序集中的 init 过程。
您还可以看到,在 finally 子句中调用了 $AppExit(),其中调用了 EXIT 程序(我们在 X# 中添加了这些程序),垃圾回收器会清除所有引用,并在应用程序结束前等待所有最终程序结束。
遗憾的是,这并不意味着所有打开的服务器都会关闭。如果您打开了一个 DbServer 并将该对象赋值给一个全局变量,那么它并不会自动关闭服务器。
运行时中有一些代码会处理这个问题(但这是另一个主题)......