65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript Shell

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (4投票s)

2010年11月18日

CPOL

4分钟阅读

viewsIcon

32089

downloadIcon

795

创建您自己的应用程序 JavaScript Shell 的基础。

引言

您在应用程序中需要使用 shell 的次数有多少?其中有多少次您自己编写了解析器?对我来说,两者都各有几次。

几周前,我正在使用MongoDB,查看其出色的 JavaScript shell 时,我突然想到:我也想要一个!

因为我需要它用于 .NET,所以我没有使用 Mongo 的代码(它是用 C++ 编写的,而且我不了解它可能使用的第三方依赖项)。我也不想第 n 次花很多时间构建解析器;所以我使用了现有的Microsoft.JScriptMicrosoft.Vsa,这两个都相当模糊,文档匮乏且不可扩展。但是,它们出色地完成了任务。

在本文件中,我不会过多解释(如果有的话)CodeDom 或 VSA,请参考我的参考文献有用链接部分。

Using the Code

首先,让我们看看使用 shell 会有多容易。以下示例是一个测试程序:LocalShell

using (var shell = ShellFactory<SampleShell>.Default.CreateShell())
{   
    shell.OpenShell();  
    shell.AddGlobalObject("tokens", new List<string>());
    shell.AddGlobalObject("globals", new Dictionary<string, object>());
    do 
    { 
        Console.Write(" > "); 
        try 
        { 
            shell.Eval(Console.ReadLine());
        } 
        catch (Exception ex) 
        { 
            Console.WriteLine(ex); 
        } 
} while (shell.Running);  

SampleShell 类型定义了从提示符可用的全局函数。将有两个全局vars可用:tokens(一个列表)和globals(一个字典)。shell 实例本身会在退出时通知我们。

查看 shell 控制台

> for(var i = 0; i < 5; i++) tokens.Add(i.ToString("D8"))
> Print(tokens[0])
00000000
> Print(tokens[1])
00000001
> for(var t in tokens) Print(t)
00000000
00000001
00000002
00000003
00000004
> Exit()

在前面的示例中,我们使用了全局变量 tokens 和全局函数Print。shell 类型中的方法将作为全局函数工作。

创建 Shell

客户端将使用EvalCreateMethodHandler与 shell 交互,这两个名称都很清楚,稍后会解释。要创建 shell,您只需定义从Scaredfinger.JSShell.Shell扩展的 shell 类,然后使用Scaredfinger.JSShell.ShellFactory<TShell>创建 shell 实例。幕后会执行一些操作,这些操作相当隐秘,这就是 shell 实例使用Factory创建的原因。

namespace SampleShell
{ 
    public class SampleShell : Shell 
    {   
        public TextWriter Output { get; set; }   

        [ShellOperation]   
        public void Help()  { … }   

        [ShellOperation]   
        public void Print(string text)  { … }   

        [ShellOperation]   
        public void Exit() { … } 
    }
}

SampleShell的完整实现可以在同名示例项目中找到。

CreateMethodHandler 方法

我能想到的一个 shell 用途是,在运行时为我的应用程序触发的事件编程事件侦听器。

 EventHandler<EventArgType> handler = shell.CreateEventHandler<EventArgs>(jsBody);
...
application.SomeEvent += handler ;      

是不是很简单?现在让我们看看jsBody的代码,其中有一些技巧。

string jsBody = @"
// Any javascript statement, as many as you’d need
// this, sender and e are available. No globals, not vars nor functions
// Print(globals) ; 
// Error, actually a compile time one, but in our case, there is no difference
this.Print(this.globals); 
// Now, we’re talking
";

没有函数声明,没有大括号。shell 会添加它们。作用域也存在问题。在函数内部,shell 的作用域不起作用(不幸的是,VSA 太过模糊,无法以简单优雅的方式修复此问题)。但是,我可以指定任何对象作为this,因此this将是 shell 作用域的引用,如果您在其中更改某些内容,则在返回外部时将发生更改。是的,我知道:打印字典没有意义。我从来没有擅长举例子。

Eval 方法

至少对我来说是最常用的方法。执行代码块。您可以包含任意数量的语句,并像往常一样用分号分隔。此方法还会返回一个结果对象,该对象将是具有值的最后一个表达式,如果没有任何表达式返回值,则返回null

还有一个非常特殊的返回值……

 result = shell.Eval("Print(1)") ; 
// result is null

result = shell.Eval("var x = 1; Print(x);") ; 
// result is 1

result = shell.Eval("var x = 1; Print(x); var y = 2"); 
// result is 2

// Now
result = shell.Eval("var Foo = function() { ... }") ; 
// result is a function, kind of a delegate.  

还记得我提到的那个非常特殊的返回值吗?它将是最后一个。它返回一个ScriptFunction,这是一个可立即使用的对象。

这是我用来创建事件处理程序的机制,您也可以用它来创建任何类型的委托。我没有进行通用的委托创建,因为这需要为每个结果生成委托类型,而且我确实不想让事情变得复杂。

创建特定委托

 var function = shell.Eval("var __F = function() {...}") as ScriptFunction;
var @this = null ; // Or any value you like to make as the this inside the function body

if (function != null)
{ 
    var dlg = (DelegateType)delegate(T1 p1, T2 p2, T3 p3... ) 
    {   
        function.Invoke(@this, new {p1, p2, p3, ...}); 
    } ;
}

示例项目

  1. 创建一个简单的 shell,包含 3 个操作:HelpPrintExit。允许自定义输出,默认为Console.Out
  2. LocalTest:从本地命令提示符使用之前的 shell。定义了一些导入和全局变量。
  3. ShellServer:使用SampleShell而不是bash的 telnet 服务器。

安全问题

使用如此灵活的 shell 涉及许多危险。因此,请自行承担风险使用它。

例如:如果您允许远程访问本地运行的 shell,即使您只引用了*System.dll*,也始终可以使用反射来加载更多有害的包。理论上,如果您在 shell 定义中没有使用“import System.Reflection”,则您应该无法使用反射,但是,黑客通常会找到难以想象的变通方法。

参考文献

  1. .NET 中的 VSA 脚本
  2. System.CodeDom 命名空间
  3. Microsoft.JScript 命名空间
  4. Microsoft.JScript.ScriptFunction 类

有用的链接

  1. 使用 Rhino 和 IKVM 将 JavaScript 嵌入 C#。
  2. .NET 脚本编辑器(C#、VB.NET 迷你 IDE)
© . All rights reserved.