简而言之,问题是:如何引用包含可重用脚本代码的第二个脚本,在不重新启动主机应用程序的情况下卸载和重新加载脚本?
我试图使用CS-脚本“编译器即服务”(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已经支持根据scripts.html包括其他脚本)
以下是CS-脚本评估器的文档:http://www.csscript.net/help/evaluator.html
谷歌对此结果的缺乏令人沮丧,但我希望我错过了一些简单的东西。任何帮助都将不胜感激。
(此问题应在不存在的cs标记下存档。)
发布于 2013-12-18 16:04:43
CompilerException的快速解决方案似乎不是使用计算器编译程序集,而是像这样的CSScript.LoadCode
var compiledAssemblyName = CSScript.CompileCode(LibraryScriptCode);
var evaluatorWithReference = CSScript.Evaluator.ReferenceAssembly(compiledAssemblyName);
dynamic scriptInstance = evaluatorWithReference.LoadCode(ScriptCode);但是,正如前面的答案所述,这限制了CodeDOM模型提供的依赖控制的可能性(如css_include)。此外,没有看到对LibraryScriptCode的任何更改,这再次限制了评估器方法的有用性。
我选择的解决方案是AsmHelper.CreateObject和AsmHelper.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;
}
}
}其中大部分是在托管指南.html中解释的,前面的文章中提到了一些有用的示例。
我觉得我可能错过了一些关于脚本更改检测的内容,但是这种方法确实有效。
发布于 2013-12-18 00:20:13
这里有点混乱。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脚本托管模式,它实际上非常健壮、直观和易于管理。惟一的真正的缺点是,您传递给(或从)脚本的所有对象都需要是可序列化的或从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;https://stackoverflow.com/questions/20644984
复制相似问题