MoonSharp 文档一-CSDN博客
MoonSharp 文档二-CSDN博客
MoonSharp 文档四-CSDN博客
MoonSharp 文档五-CSDN博客
7.Proxy objects(代理对象)
如何封装你的实现,同时又为脚本提供一个有意义的对象模型
官方文档:MoonSharp
在实际场景中,脚本通常不受你的控制。这会带来一些问题,其中包括:
安全性:这部分内容不在本页的讨论范围内,但如果你的脚本并非来自严格受控的环境,请务必阅读关于沙箱的部分。
向后和向前兼容性:MoonSharp 会尽力避免引入过去或将来的兼容性问题,但你的 API 设计是你自己的责任!
实现上述目标的一个关键方法是封装你的实现细节,确保脚本不会调用那些不应该调用的 API 字段,这样你就可以自由地调整内部模型,而无需担心意外破坏脚本。
有两种封装方式,一种是通过一些非常复杂的方式复制所有 API,另一种是通过“代理对象”(一种“脚本的 DTO”)。
概念非常简单。对于每个你想封装并暴露给脚本的类型,你需要提供一个“代理类型”,这是一个类,它操作封装(目标)类型的实例。
举个例子胜过千言万语:
// Declare a proxy class
class MyProxy
{
MyType target;
[MoonSharpHidden]
public MyProxy(MyType p)
{
this.target = p;
}
public int GetValue() { return target.GetValue(); }
}
// Register the proxy, using a function which creates a proxy from the target type
UserData.RegisterProxyType<MyProxy, MyType>(r => new MyProxy(r));
// Test with a script - only the proxy type is really exposed to scripts, yet everything it works
// just as if the target type was actually used..
Script S = new Script();
S.Globals["mytarget"] = new MyType(...);
S.Globals["func"] = (Action<MyType>)SomeFunction;
S.DoString(@"
x = mytarget.GetValue();
func(mytarget);
");
除了封装之外,代理对象还可以实现一些巧妙的技巧。
其中一个非常简单但非常有用——正确访问值类型。例如,你可以封装 Unity 的 Transform 类(它完全由值类型组成,但本身是引用类型),并通过一个不同的接口来正确保留引用!
8.Error handling(错误处理)
以及错误生成
官方文档:MoonSharp
MoonSharp 会生成几种类型的异常:
-
InternalErrorException
-
SyntaxErrorException
-
ScriptRuntimeException
-
DynamicExpressionException
所有这些异常都继承自一个共同的 InterpreterException 类型,因此可以在异常过滤器中将这些异常归类在一起。
当然,由于调用代码或 MoonSharp 代码中的错误,也可能会生成其他类型的异常,但至少在理论上,这些异常不会由于脚本代码中的错误而产生。
这些异常还支持一个 DecoratedMessage 属性,该属性包含了错误信息,并附带了生成错误的源代码中的引用(如果可用)。
Script.GlobalOptions.RethrowExceptionNested
有一个名为 RethrowExceptionNested 的全局选项。如果设置为 false(默认值),异常会在保留堆栈的情况下重新抛出,并且装饰后的错误信息可以在 DecoratedMessage 属性中获取。如果设置为 true,则会抛出一个新的异常,旧的异常会存储在 InnerException 属性中,并且装饰后的错误信息既可以在 DecoratedMessage 属性中获取,也可以在 Message 属性中获取。
你可以根据个人喜好设置 RethrowExceptionNested,但通常情况下,在 .NET 环境下工作时,设置为 false 效果更好;而在 Mono、Xamarin 和 Unity 环境下工作时,设置为 true 效果更佳。
内部错误异常(InternalErrorException)
当解释器遇到内部错误时,会抛出 InternalErrorException。这种情况通常没有太多可以处理的余地,通常应将其视为脚本引擎的致命错误。如果发生这种情况,请尽可能在论坛或 Discord 中报告,并提供所有可能的信息以便复现问题。
语法错误异常(SyntaxErrorException)
当解析器无法解析脚本代码或脚本代码因某些原因无效时,会抛出 SyntaxErrorException。
需要注意的是,此异常是在调用 Script 的某些方法(如 DoFile、RunString 等)时抛出的。如果在一个有问题的脚本上调用标准 Lua 库的 load 函数,则会生成一个 ScriptRuntimeException,并将 SyntaxErrorException 包裹在其中。
脚本运行时异常(ScriptRuntimeException)
这是所有异常中最常见的一个,可能也是最重要的一个。
每当 Lua 抛出错误时(无论是通过调用 error 函数,还是因为运行时错误等),都会抛出一个 ScriptRuntimeException。例如:
static void ErrorHandling()
{
try
{
string scriptCode = @"
return obj.calcHypotenuse(3, 4);
";
Script script = new Script();
DynValue res = script.DoString(scriptCode);
}
catch (ScriptRuntimeException ex)
{
Console.WriteLine("Doh! An error occured! {0}", ex.DecoratedMessage);
}
}
将打印:
Doh! An error occured! chunk_1:(2,5-36): attempt to index a nil value
如果我们想向 Lua 抛出一个错误,只需执行相同的操作,抛出一个 ScriptRuntimeException 即可。非常简单。
static void DoError()
{
throw new ScriptRuntimeException("This is an exceptional message, no pun intended.");
}
static string ErrorGen()
{
string scriptCode = @"
local _, msg = pcall(DoError);
return msg;
";
Script script = new Script();
script.Globals["DoError"] = (Action)DoError;
DynValue res = script.DoString(scriptCode);
return res.String;
}
这将返回:
This is an exceptional message, no pun intended.
动态表达式异常(DynamicExpressionException)
当解释器在评估动态表达式时遇到错误时,会抛出 DynamicExpressionException。有关动态表达式的具体含义,请参阅动态表达式的教程。
9.Script Loaders(脚本加载器)
如何更改 MoonSharp 从文件读取脚本的方式
官方文档:MoonSharp
MoonSharp 旨在支持多平台运行。它无法选择将在哪些平台上运行,因为这是库的最终用户的选择,因此它必须以某种方式能够在各种环境中运行,例如作为 Linux 中的守护进程、作为 WPF 应用程序、作为手机上的应用程序或作为游戏主机上的游戏。例如,像 FileStream 这样简单的 API 在 Windows Store 应用中并不可用。
此外,MoonSharp 也不知道它应该如何在使用它的应用程序中运行和工作。例如,如果你希望 loadfile 从嵌入式资源加载脚本而不是从文件加载,该怎么办?
出于这些原因,MoonSharp 提供了两个对象层次结构:
-
脚本加载器(Script Loaders) - 用于自定义从文件加载脚本的 API 的工作方式。
-
平台访问器(Platform Accessors) - 用于自定义如何完成对操作系统的底层访问。
需要注意的是,通常情况下,脚本加载器与平台访问器是独立的,尽管它们可以根据需要使用对方的方法。这意味着,例如,即使你的平台访问器不支持从文件加载,也不一定意味着你的脚本加载器不能支持,反之亦然。
它们是两个独立的对象,因为它们处理两种不同的职责。脚本加载器负责如何从文件加载脚本,而平台访问器负责处理那些需要调用操作系统 API 的库函数(主要是在 os 和 io 模块中)。
预定义脚本加载程序快速浏览
根据你所使用的平台,你可以选择以下几种脚本加载器:
-
FileSystemScriptLoader:直接访问文件系统中的文件,可自定义,但在可移植类库(PCL)中不受支持。
-
ReplInterpreterScriptLoader:与
FileSystemScriptLoader相同,但额外使用了与 Lua 相同的逻辑从环境变量(如MOONSHARP_PATH、LUA_PATH或"?;?.lua",以最先存在的为准)中获取路径。 -
EmbeddedResourcesScriptLoader:提供对给定程序集的嵌入式资源的访问,而不是文件系统。
-
InvalidScriptLoader:抛出异常。
-
UnityAssetsScriptLoader:适用于 Unity3D,用于从文本资源(Text Assets)加载脚本。
如果没有重新定义,MoonSharp 默认使用的脚本加载器按以下规则选择:
-
如果在 Unity3D 环境下运行,默认脚本加载器是
UnityAssetsScriptLoader,路径为Assets/Resources/MoonSharp/Scripts。文件必须具有.txt扩展名(因为 Unity 的行为比较特殊)。 -
如果当前构建为可移植类库(PCL),则选择
InvalidScriptLoader(除非你采取其他措施,否则无法从文件加载脚本)。 -
其他情况下,默认使用
FileSystemScriptLoader。
如何指定要使用的脚本加载器
假设我们希望从当前程序集的嵌入资源中加载脚本。
基本上有两种方法,一种是局部方法,另一种是全局方法。 首先,对于给定的脚本,可以通过以下方式修改其脚本加载器:
script.Options.ScriptLoader = new EmbeddedResourcesScriptLoader();
否则,可以使用以下方法指定所有新创建的脚本应使用新的脚本加载程序:
Script.DefaultOptions.ScriptLoader = new EmbeddedResourcesScriptLoader();
如何自定义 require 函数的行为
一个常见的需求是更改 require 函数用于加载模块的目录。
大多数脚本加载器都扩展了 ScriptLoaderBase 类,该类公开了一个 ModulePaths 属性,该属性包含了加载模块时将检查的所有路径。
你可以轻松地更改这些路径:
// These two lines are equivalent:
((ScriptLoaderBase)script.Options.ScriptLoader).ModulePaths = new string[] { "MyPath/?", "MyPath/?.lua" };
// or
((ScriptLoaderBase)script.Options.ScriptLoader).ModulePaths = ScriptLoaderBase.UnpackStringPaths("MyPath/?;MyPath/?.lua");
请注意,ScriptLoaderBase 还会检查当前 _ENV 中的 LUA_PATH 全局变量,以确定使用哪些路径来加载模块。如果你希望忽略 LUA_PATH 全局变量,可以使用:
((ScriptLoaderBase)script.Options.ScriptLoader).IgnoreLuaPathGlobal = true;
谨慎提示:仅仅改变 ModulePaths 和/或 IgnoreLuaPathGlobal 并不足以限制哪些文件可以被加载,形成"沙盒"环境。如果你需要这个功能,请实现一个自定义的脚本加载器。
如何使用 EmbeddedResourcesScriptLoader
将脚本嵌入为资源很容易。
在 Visual Studio 中:
• 在你的项目中创建一个"Scripts"文件夹
• 在该文件夹中添加一个新的文本文件,并将其重命名为 Test.lua
• 在该文件中输入一些 Lua 代码
然后,在解决方案资源管理器中右键单击该文件,会出现以下窗口:

确保 “Build Action” 设置为 “Embedded Resource”。
完成此设置后,我们就使用正确的脚本加载器:
static void EmbeddedResourceScriptLoader()
{
Script script = new Script();
script.Options.ScriptLoader = new EmbeddedResourcesScriptLoader();
script.DoFile("Scripts/Test.lua");
}
在其他 IDE 中也可以遵循类似的步骤,例如 Xamarin。
如何创建自己的脚本加载器
是时候创建你自己的脚本加载器了。
你基本上有两个选择:扩展 ScriptLoaderBase(推荐)或实现 IScriptLoader。
两者都很直观,但为了方便起见,我们还是选择第一个。
我们的脚本加载器实际上会动态生成一个小脚本,其名称是请求的文件名,而不是真正地去加载文件:
private class MyCustomScriptLoader : ScriptLoaderBase
{
public override object LoadFile(string file, Table globalContext)
{
return string.Format("print ([[A request to load '{0}' has been made]])", file);
}
public override bool ScriptFileExists(string name)
{
return true;
}
}
实现这一点很容易:
static void CustomScriptLoader()
{
Script script = new Script();
script.Options.ScriptLoader = new MyCustomScriptLoader()
{
ModulePaths = new string[] { "?_module.lua" }
};
script.DoString(@"
require 'somemodule'
f = loadfile 'someothermodule.lua'
f()
");
}
运行这些操作将打印:
A request to load 'somemodule_module.lua' has been made
A request to load 'someothermodule.lua' has been made
end



















