01 — 宿主 API
LuaAppDomain(含GetFunction)、[LuaMarshalAs]、[LuaAlias]。 C#→Lua / Lua→C# Marshal 细节见 marshal/。
1. LuaAppDomain
1.1 职责
ZLua.LuaAppDomain 是宿主唯一推荐的初始化门面。Common 不 引用 Mono/Il2Cpp;在 Initialize / GetFunction 时按环境 反射创建 后端嵌套类型 Runtime : ILuaRuntime:
| 环境 | 后端宿主类型 | 程序集 | 创建方式 |
|---|---|---|---|
| Editor | LuaMonoAppDomain | ZLua.Mono | Activator.CreateInstance(…+Runtime) |
| Player | LuaIl2CppAppDomain | ZLua.Il2Cpp | 同上(#if !UNITY_EDITOR 分支) |
public interface ILuaRuntime
{
void Initialize(Func<string, object> moduleLoader);
void Reset(Func<string, object> moduleLoader);
void ProcessPendingRefReleases();
Delegate GetFunction(Type delegateType, string luaModule, string luaMethodName);
}
public static class LuaAppDomain
{
public static void Initialize(Func<string, object> moduleLoader);
public static void Reset(Func<string, object> moduleLoader);
public static T GetFunction<T>(string luaModule, string luaMethodName)
where T : MulticastDelegate;
internal static void ProcessPendingRefReleases(); // 由 LuaFramePump 驱动
}
不 使用 RuntimeInitializeOnLoadMethod / SetRuntime 做隐式注册;首次 Initialize(或 GetFunction)时解析后端。
宿主面 不 暴露 Shutdown;整域拆掉再重建只走 Reset。
1.2 整域重置(Reset)
用于热更或清空脚本世界:拆掉当前唯一主 lua_State,再以给定 loader 重建。
| API | 行为 |
|---|---|
Reset(loader) | 仅预约:保存 loader,在本帧 EndOfFrame(LuaFramePump / WaitForEndOfFrame)才真正执行 teardown + 重建。多次预约以最后一次 loader 为准。真正执行时:排空 pending ref → 关闭 Registry / 模块缓存 → lua_close → 新建 lua_State 并安装 loader。Il2Cpp 进程级 Bridge / XML 表 / InternalCall 保留。 |
Initialize(loader) | 仅首次(或进程内尚无主 state)创建 lua_State 并安装 loader。已初始化时再次调用 → 抛异常(须 Reset;不再支持「只换 loader」)。 |
契约:
Reset调用当下不拆 state;EndOfFrame 应用之后,对旧GetFunction委托的调用 → 抛 C# 异常。- 旧委托 一律作废;宿主须在 Reset 生效后重新
GetFunction并丢弃字段里缓存的Action/Func。 - 因真正 teardown 在 EndOfFrame,允许在 C#↔Lua 调用中途调用
Reset(只排队);勿在同一帧 EndOfFrame 之后仍使用旧委托。 - Editor Emmy:teardown 随
lua_close停止调试器;重建后按 Settings 决定是否重启。
详见 10-LIFETIME.md §7。
1.3 模块加载器
moduleLoader(moduleName) 由宿主提供,返回 Lua 模块源码(通常为 string)。native 通过 __zlua_load_module 与 package.searchers 集成。
约定:
- 模块名与
GetFunction的luaModule字符串一致 - loader 失败应抛出明确异常,避免 silent nil
1.4 帧泵
LuaAppDomain.Initialize 注册 LuaFramePump:LateUpdate 排空 pending ref;WaitForEndOfFrame 执行已预约的 Reset。详见 10-LIFETIME.md。
1.5 Editor 调试器(可选)
仅 Editor Mono:若 Settings enableDebugger 为 true,LuaMonoAppDomain.Initialize 在现有初始化完成之后调用 LuaEnv.StartDebugger(注入 emmy_core、tcpListen、可选 waitIDE)。规范见 build/04-EMMYLUA-DEBUGGER.md。不改变 GetFunction / Marshal 语义;Il2Cpp Player 不适用本入口。
2. GetFunction — C# 调用 Lua
C#→Lua 的 唯一正式入口:按模块名与方法名取得绑定好的 Delegate,再由调用方 Invoke(或直接调用)。
2.1 签名
public static T GetFunction<T>(string luaModule, string luaMethodName)
where T : MulticastDelegate;
| 参数 | 说明 |
|---|---|
luaModule | 非空;交给 moduleLoader / require 的模块名 |
luaMethodName | 非空;模块 return { ... } 表中的键名 |
T | 具体委托类型(如 Action、Action<float>、Func<int,int,int>) |
2.2 行为
- 按
luaModule加载(或命中已加载)模块表 - 取
module[luaMethodName],须为 Luafunction - 按
T的签名将 function Marshal 为 closed delegate(规则同 marshal/09-FUNCTION.md) - 返回该
T实例
缓存: API 不保证跨调用复用同一 delegate 实例;热路径由调用方自行保存(字段 / 局部变量)。须在 Initialize 之后再调用(例如 Awake);勿放在与 RuntimeInitializeOnLoadMethod 同类型的 static 字段初始化器中。Reset 生效后旧委托一律作废,须重新 GetFunction。
2.3 示例
// 一次性 / 启动期取得
var add = LuaAppDomain.GetFunction<Func<int, int, int>>("app", "add");
int sum = add(10, 20);
var onTick = LuaAppDomain.GetFunction<Action<float>>("game", "OnTick");
onTick(0.016f);
-- app.lua
local function add(a, b) return a + b end
return { add = add }
2.4 错误
| 条件 | 行为 |
|---|---|
未 Initialize / loader 未配置 | 抛 C# 异常 |
| 模块加载失败 / 键不存在 / 非 function | 抛 C# 异常(含可诊断信息) |
T 无法从该 function 绑定(签名不兼容等) | 抛 C# 异常 |
2.5 调用与 Marshal
对返回的 delegate 执行 Invoke 时:
- 参数 / 返回值 Marshal 与普通 C#→Lua(delegate bridge) 相同,见 marshal/01-OVERVIEW.md
ref/in/out默认 Push OpaqueValue(marshal/04-OPAQUE.md)
2.6 流程(概念)
GetFunction<T>(module, method)
→ require / 取模块表
→ 取 Lua function
→ Marshal 为 T
→ 返回 T
此后:T.Invoke(...)
→ marshal 参数(含 ref → OpaqueValue)
→ lua_pcall
→ marshal 返回值 / ref 写回
→ 异常边界转换(§6)
Il2Cpp C# 层初始化仍为薄壳(与 GetFunction 无关):
public static class LuaIl2CppAppDomain
{
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void InitializeInternal(Func<string, object> moduleLoader);
public static void Initialize(Func<string, object> moduleLoader)
=> InitializeInternal(moduleLoader);
}
3. [LuaMarshalAs] — Marshal 标注
3.1 作用范围
| 可标注位置 | 说明 |
|---|---|
| 参数 | 控制 Lua↔C# 该形参的 Push/Pop |
| 返回值 | 控制 C#→Lua 返回 Push |
| 字段 / 属性 | 控制成员读写时的 marshal(codegen 消费) |
禁止标注在 方法 上(绑定期 LuaMarshalAsConfigurationException)。
完整选项见 marshal/02-MARSHAL-AS.md。
3.2 常用选项(概要)
LuaMarshalType | 用途 |
|---|---|
Default | 按类型默认规则 |
OpaqueValue | C#→Lua 强制 opaque lightuserdata(by-val 引用类型 / struct) |
Table / UnpackedValues | 仅 struct / closed 泛型 struct;Table 另允许 Nullable<struct>(须 Members) |
默认规则摘要:
- C#→Lua
ref/in/out→ OpaqueValue(无需再标) - by-val 基元 / enum → Lua boolean / integer / number / string
- class → ByObj userdata;struct → ByVal 或 Handle(见 struct 分册)
params T[]→ 同 szarray 单栈槽(table / userdata /nil);不支持尾部多槽收集;无 专用LuaMarshalType
3.3 校验时机
- Mono Attribute: 非法组合 → 错误日志 + 回退
Default(见 marshal/02-MARSHAL-AS.md §4.1),不抛绑失败。 - Il2Cpp Generate / MarshalAs XML: 配置错误可 硬失败(§4.2)。
4. [LuaAlias] — 方法 Lua 别名
[LuaAlias("run_i32")]
public void Run(int value) { ... }
[LuaAlias("Foo")] // 允许与已有方法名 / 其它别名重复
public void Bar(string s) { ... }
- 定义于
ZLua.Common - 等价于用该字符串作为该方法的 唯一最终 Lua 名(替换默认名
MethodInfo.Name,不再双挂) - 预编译 DLL 可用 独立 XML(Settings
luaAliasXmlPaths,根元素ZLuaAlias);不得写进 MarshalAs XML。见 04-METHOD-OVERLOAD.md §5.4 - 允许与其它别名或已有方法名重复;重复时该最终名下多候选,调用走 重载分派(见 04-METHOD-OVERLOAD.md §5)
- 若某最终名下仅此一候选(例如独立的
run_i32),则为 direct closure
完整规则见 04-METHOD-OVERLOAD.md §3、§5。
5. Lua→C#:无需 [MonoLuaCallback]
Lua 调用 C# 成员时,native 在 EnsureBinding 阶段为每个 public 成员生成桥接 closure 并写入三表。不需要也 不提供 业务侧 [MonoLuaCallback] 标记。
每种 ReducedType(Il2Cpp) 或 完整签名(Mono Emit) 对应唯一桥接入口。
6. 异常边界
6.1 C# 调 Lua
| 方向 | 行为 |
|---|---|
Lua error() | 捕获为 C# 异常(LuaException 或包装类型);不泄漏未处理 native longjmp 到托管栈外 |
| C# 异常传入 native | 在边界转换为 Lua error 或记录后 rethrow(实现统一) |
脚本 不应 依赖 pcall 内捕获 C# 异常的具体类型字符串;仅保证「失败可检测」。
6.2 Lua 调 C#
| 方向 | 行为 |
|---|---|
| C# 抛异常 | 转换为 luaL_error 等价消息;Mono / Il2Cpp 文案一致或等价 |
| Lua 侧 | 使用 pcall 捕获错误字符串 |
6.3 Opaque 与边界
Opaque handle 仅在 产生它的那次 C#→Lua 调用返回前有效;跨 pcall 保存后再用 → error。见 marshal/04-OPAQUE.md、10-LIFETIME.md。
7. Codegen 约束(摘要)
| 项 | 约束 |
|---|---|
[LuaAlias] | 允许与默认名 / 其它别名重复;按最终名分组(见 overload §5) |
[LuaMarshalAs] | 禁止 method 级;非法 Members → bind 失败 |
| Mono Emit | 无法 Emit 的签名 必须显式失败,禁止 silent Method.Invoke 热路径 |
| Il2Cpp stub | 未覆盖签名 → 构建期或首次绑定失败(MethodBridge 等,见 impl/codegen/) |
C#→Lua 不依赖 IL weave / 专用 stub:经 GetFunction → Delegate 桥完成。
8. 相关文档
| 文档 | 内容 |
|---|---|
| 00-OVERVIEW.md | 双运行时、初始化 |
| 04-METHOD-OVERLOAD.md | dispatch、register_method |
| marshal/01-OVERVIEW.md | Marshal 总览 |
| marshal/09-FUNCTION.md | Delegate ↔ Lua function |
| 10-LIFETIME.md | GC、单 lua_State |
| reference/csharp/lua-app-domain.md | 程序员 API 页 |