CS-Script Evaluator LoadCode:如何编译和引用第二个脚本(可重用库)

本文关键字:第二个 引用 脚本 编译 LoadCode Evaluator 何编译 CS-Script | 更新日期: 2024-10-20 06:48:14

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

我正在尝试使用 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

此行在运行时会导致编译器异常

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 脚本评估器的文档:http://www.csscript.net/help/evaluator.html

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

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

CS-Script Evaluator LoadCode:如何编译和引用第二个脚本(可重用库)

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

我确实建议将CSScript.Evaluator.LoadCode视为托管模型的第一个候选者,因为它提供的开销更少,并且可以说更方便重新加载模型。然而,它伴随着成本。您对重新加载和依赖项包含(程序集、脚本(的控制非常有限。内存泄漏并非 100% 可以避免。它还使脚本调试完全不可能(Mono 错误(。

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

看看"[cs-script]'Samples'Hosting'CodeDOM'Modifying script without restart"样本。

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

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

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;

编译器异常的快速解决方案似乎不是使用计算器来编译程序集,而是像这样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 中进行了解释,并且在上一篇文章中提到了有用的示例。

我觉得我可能错过了一些关于脚本更改检测的内容,但这种方法效果很好。