在构建解析器等工具时,有时需要在应用程序中执行JavaScript,无论是为了抓取数据、提供脚本支持,还是为了模拟旧版的ASP。Microsoft提供了COM接口来使用这些脚本,但直接在.NET中使用它们并不优雅。幸运的是,创建了一个小巧的库,可以简化这一过程。
Microsoft提供了一个可扩展的脚本框架,它在旧版ASP页面和Internet Explorer中使用。默认情况下,Windows附带了VBScript和JavaScript(JScript),但第三方也添加了其他脚本语言。这些脚本引擎是COM可见的,并将它们的脚本作为可以调用代码或被代码调用的COM对象公开。唯一的问题是接口比较混乱。
所做的是将这些接口封装在两个易于使用的对象中,即ScriptHost
和ScriptEngine
。
首先,脚本引擎不能独立存在。它们需要一个扎根的地方,这就是ScriptHost
的作用。ScriptHost
管理脚本引擎的创建和生命周期,同时为脚本引擎提供各种服务,如错误报告和对象检索。在创建任何脚本引擎之前,必须创建一个这样的小东西。记得完成后要释放它。关闭脚本宿主也会关闭由它创建的所有引擎。
接下来,有ScriptEngine
,可以通过ScriptHost.Create()
获得。这些代表脚本代码的独立区域。为了理解这意味着什么,要知道每个网页中的<script>
块将对应于这些中的一个,而页面本身将是ScriptHost
。传递一个语言到Create()
以获得想要的语言的脚本引擎。有一个“快速”的JS引擎,可以通过传递ChakraJS
常量来访问,但没有测试过,也不确定它到底有多快。
有了ScriptEngine
,可以Evaluate()
表达式,Run()
语句,或者AddCode()
到脚本块。还可以将对象添加到Globals字典中,然后可以在脚本代码中通过名称访问这些对象。
希望以下示例代码能清楚地说明一切:
C#
Console.WriteLine("Creating script host");
using(var host = new ScriptHost())
{
// create a JavaScript engine
Console.WriteLine("Creating javascript engine");
var engine = host.Create("javascript");
// you can use the ChakraJS const here // for IE9+'s fast engine
// evaluate an expression
Console.WriteLine("Evaluate 2+2: ");
Console.WriteLine(engine.Evaluate("2+2"));
// add some code to the current script
Console.WriteLine("Add code: var i = 1;");
engine.AddCode("var i = 1;");
// get the object for the script
Console.Write("i = ");
dynamic obj = engine.Script;
// print var i
Console.WriteLine(obj.i);
// add an app object to the script
Console.WriteLine("Add global app object");
engine.Globals.Add("app", new AppObject());
// let the script call the app object
Console.Write("Run \"app.writeLine('Hello World!');\": ");
engine.Run("app.writeLine('Hello World!');");
}
请注意,在上面的脚本引擎中添加了AppObject
。以下是它的定义:
C#
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch /* .AutoDual */)]
// automatically implements com interface IDispatch ( and IUnknown )
public class AppObject
{
/// <summary>
/// allows scripts to write to the console
/// </summary>
public void writeLine(string s) { Console.WriteLine(s); }
}
请注意,从System.Runtime.InteropServices
添加了一些属性。这是必要的,以便.NET创建所需的基础设施,让脚本通过COM/OLE Automation与对象通信。如果没有添加这些,脚本将无法与对象通信,将收到一个错误。
另外,奇怪的是,尽管每个脚本引擎都有自己的全局变量集合,但脚本站点内的所有全局变量共享相同的命名容器。这意味着无论在哪个脚本引擎下创建,每个名称只有一个全局变量被认可。如果两个脚本引擎有相同名称的项目,只有第一个实例将与该名称关联。将对象添加到两个或更多脚本引擎下是没有问题的。这不是设计的方式 - 它是Microsoft Active Script框架设计的一部分。