2

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

我正在尝试使用 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 的原因是,当任一脚本发生更改时,可以随时重新加载脚本,而无需重新启动宿主应用程序。(根据http://www.csscript.net/help/Importing_scripts.html,CSScript.LoadCode已经支持包含其他脚本)

这是关于 CS-Script Evaluator 的文档:http ://www.csscript.net/help/evaluator.html

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

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

4

2 回答 2

3

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

我确实建议考虑CSScript.Evaluator.LoadCode作为托管模型的第一个候选者,因为它提供了更少的开销并且可以说更方便的重新加载模型。然而,它伴随着成本。您几乎无法控制重新加载和依赖项包含(程序集、脚本)。内存泄漏不是 100% 可以避免的。而且它还使脚本调试完全不可能(单声道错误)。

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

看看"[cs-script]\Samples\Hosting\CodeDOM\Modifying script without restart"样品。

并且"[cs-script]\Samples\Hosting\CodeDOM\InterfaceAlignment"还将让您了解如何使用重新加载的接口。

CodeDOM 多年来一直是默认的 CS-Script 托管模式,实际上它非常健壮、直观且易于管理。唯一真正的缺点是您传递给(或从中获取)脚本的所有对象都需要可序列化或从 MarshalByRef 继承。这是在“自动”独立域中执行的脚本的副作用。因此,必须处理远程处理的所有“乐趣”。顺便说一句,这是我实现基于 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;
于 2013-12-18T00:20:13.110 回答
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.CreateObjectandAsmHelper.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-methods 工作,所以当不知道接口是否实现时,我制作了这个扩展方法以减少混乱:

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中进行了解释,并且在上一篇文章中提到了有用的示例。

我觉得我可能错过了有关脚本更改检测的一些内容,但是这种方法确实有效。

于 2013-12-18T16:04:43.277 回答