CS脚本评估器LoadCode:如何编译和引用第二个脚本(可重用库)

时间:2013-12-17 21:24:20

标签: c# cs-script

简而言之,问题是:如何引用包含可重用脚本代码的第二个脚本,在需要能够卸载和重新加载脚本的约束下,如果它们中的任何一个更改而不重新启动主机应用程序?

我正在尝试使用CS-Script“编译器作为服务”(CSScript.Evaluator)编译脚本类,同时引用刚刚从第二个“库”脚本编译的程序集。目的是库脚本应包含可以重用于不同脚本的代码。

下面是一个示例代码,用于说明该想法,但也会在运行时导致CompilerException。

using CSScriptLibrary;
using NUnit.Framework;

[TestFixture]
public class ScriptReferencingTests
{
    private const string LibraryScriptCode = @"
public class Helper
{
    public static int AddOne(int x)
    {
        return x + 1;
    }
}
";

    private const string ScriptCode = @"
using System;
public class Script
{
    public int SumAndAddOne(int a, int b)
    {
        return Helper.AddOne(a+b);
    }
}
";

    [Test]
    public void CSScriptEvaluator_CanReferenceCompiledAssembly()
    {
        var libraryEvaluator = CSScript.Evaluator.CompileCode(LibraryScriptCode);
        var libraryAssembly = libraryEvaluator.GetCompiledAssembly();
        var evaluatorWithReference = CSScript.Evaluator.ReferenceAssembly(libraryAssembly);
        dynamic scriptInstance = evaluatorWithReference.LoadCode(ScriptCode);

        var result = scriptInstance.SumAndAddOne(1, 2);

        Assert.That(result, Is.EqualTo(4));
    }
}

要运行代码,您需要NuGet包 NUnit和cs-script

此行在运行时导致 CompilerException

dynamic scriptInstance = evaluatorWithReference.LoadCode(ScriptCode);

{interactive}(7,23): error CS0584: Internal compiler error: The invoked member is not supported in a dynamic assembly.

{interactive}(7,9): error CS0029: Cannot implicitly convert type '<fake$type>' to 'int'

同样,使用 CSScript.Evaluator.LoadCode 而不是CSScript.LoadCode的原因是,可以随时重新加载脚本,而无需在任何一个脚本时重新启动主机应用程序变化即可。 (CSScript.LoadCode已根据http://www.csscript.net/help/Importing_scripts.html支持包含其他脚本)

以下是CS-Script Evaluator的文档:http://www.csscript.net/help/evaluator.html

缺乏谷歌的结果是令人沮丧的,但我希望我错过了一些简单的事情。任何帮助将不胜感激。

(这个问题应该在不存在的标签cs-script下提交。)

2 个答案:

答案 0 :(得分:3)

这里有一些轻微的混乱。 Evaluator不是实现可重载脚本行为的唯一方法。 CSScript.LoadCode也允许重新加载。

我确实建议将CSScript.Evaluator.LoadCode视为托管模型的第一个候选者,因为它提供了更少的开销,并且可以说是更方便的重新加载模型。然而它带来了成本。您几乎无法控制重新加载和依赖项包含(程序集,脚本)。内存泄漏不是100%可避免的。而且它也使得脚本调试完全不可能(Mono bug)。

在你的情况下,我真的建议你转向更传统的托管模式:CodeDOM。

请查看"[cs-script]\Samples\Hosting\CodeDOM\Modifying script without restart"示例。

"[cs-script]\Samples\Hosting\CodeDOM\InterfaceAlignment"还可以让您了解如何在重新加载时使用接口。

CodeDOM多年来一直是默认的CS脚本托管模式,实际上它非常强大,直观且易于管理。唯一的真正的缺点是,您传递给(或从中获取)脚本的所有对象都需要可序列化或从MarshalByRef继承。这是在“自动”单独域中执行的脚本的副作用。因此,人们必须处理Remoting的所有“乐趣”。 顺便说一句,这是我实施基于Mono的评估器的唯一原因。

CodeDOM模型还将自动管理依赖项,并在需要时重新编译它们。但无论如何,你似乎都知道这一点。

CodeDOM还允许您精确定义检查更改依赖关系的机制:

//the default algorithm "recompile if script or dependency is changed"
CSScript.IsOutOfDateAlgorithm = CSScript.CachProbing.Advanced; 

//custom algorithm "never recompile script" 
CSScript.IsOutOfDateAlgorithm = (s, a) => false;

答案 1 :(得分:0)

CompilerException的快速解决方案似乎不是使用Evaluator来编译程序集,而是只使用CSScript.LoadCode这样

var compiledAssemblyName = CSScript.CompileCode(LibraryScriptCode);
var evaluatorWithReference = CSScript.Evaluator.ReferenceAssembly(compiledAssemblyName);
dynamic scriptInstance = evaluatorWithReference.LoadCode(ScriptCode);

但是,如前面的答案中所述,这限制了CodeDOM模型提供的依赖控制的可能性(如css_include)。此外,看不到对LibraryScriptCode的任何更改,这再次限制了Evaluator方法的有用性。

我选择的解决方案是AsmHelper.CreateObjectAsmHelper.AlignToInterface<T>方法。这使您可以在脚本中使用常规css_include,同时允许您随时通过释放AsmHelper重新加载脚本并重新开始。我的解决方案看起来像这样:

AsmHelper asmHelper = new AsmHelper(CSScript.Compile(filePath), null, false);
object obj = asmHelper.CreateObject("*");
IMyInterface instance = asmHelper.TryAlignToInterface<IMyInterface>(obj);
// Any other interfaces you want to instantiate...
...
if (instance != null)
    instance.MyScriptMethod();

检测到更改后(我使用FileSystemWatcher),您只需致电asmHelper.Dispose并再次运行上述代码。

此方法要求脚本类使用Serializable属性标记,或者只是从MarshalByRefObject继承。
请注意,您的脚本类不需要继承任何接口。 AlignToInterface可以使用和不使用它。你可以在这里使用dynamic,但我更喜欢使用强类型接口来避免错误。

我无法使用内置的try - 方法,所以当不知道接口是否已实现时,我使用这种扩展方法以减少混乱:

public static class InterfaceExtensions
{
    public static T TryAlignToInterface<T>(this AsmHelper helper, object obj) where T : class
    {
        try
        {
            return helper.AlignToInterface<T>(obj);
        }
        catch
        {
            return null;
        }
    }
}

主要指南http://www.csscript.net/help/script_hosting_guideline_.html中解释了大部分内容,并且在上一篇文章中提到了有用的示例。

我觉得我可能错过了关于脚本更改检测的内容,但这种方法非常有效。