跳到主要内容

Struct 编组

规范性: C# struct(值类型)与 Lua 互操作的传递、构造与写回规则。
OpaqueValue / byref(C#→Lua):04-OPAQUE.md
Lua→C# byref 真 ref:03-BYREF.md
Table / UnpackedValues:02-MARSHAL-AS.md §5–§6
实现细节(GC、Registry 等):../../impl/marshal/

1. 设计目标

目标说明
零拷贝(默认 Handle)struct 已在桥接栈帧上时,C#→Lua 仅记录地址并 Push lightuserdata handle;不额外 box
安全Lua 不持有 struct 裸地址;handle 仅为 opaque 令牌;过期访问 报错
统一struct 与 class 同样走 CSharp.*obj:Method();差异在承载形态
可显式选择[LuaMarshalAs]UserDataTableUnpackedValuesOpaqueValue

术语:

  • Blittable struct:可 memcpy,无托管引用字段。
  • Non-blittable struct:含 string、class 等引用字段;userdata 路径须 GC 能扫到实例内存。

2. 默认编组(摘要)

未标注 [LuaMarshalAs] 时,与 01-OVERVIEW.md 一致:

方向默认形态说明
C# → Lua(by-val)ByValUserDataOpaqueValue长生命周期 / 显式 StructUserData 路径 Push ByValUserData;同步调用链内 by-val 亦可能为 OpaqueValue handle
C# → Luaref/in/outOpaqueValue04-OPAQUE.md
Lua → C#StructUserDataType(...) 产物默认 接受 table / 多栈参数;须 [LuaMarshalAs(Table | UnpackedValues)]

3. 三种 Lua 可见形态

┌─────────────────────────────────────────────────────────────┐
│ C# struct 在 Lua 侧的承载形态 │
├─────────────────┬───────────────────┬───────────────────────┤
│ OpaqueValue │ ByValUserData │ ByObjUserData │
│ (Handle) │ (StructUserData) │ (boxed) │
├─────────────────┼───────────────────┼───────────────────────┤
│ lightuserdata │ full userdata │ full userdata │
│ 无 metatable │ ByVal 实例 MT │ ByObj 实例 MT │
│ 仅同步有效 │ 可长期持有 │ 装箱对象路径 │
│ get/set_opaque │ : / . 访问成员 │ : / . 访问成员 │
└─────────────────┴───────────────────┴───────────────────────┘
形态典型来源成员访问生命周期
OpaqueValueC#→Lua by-val(同步)或 ref/in/out 默认不可 :/.;须 get_opaquevalue / set_opaquevalueto_user_data仅本次 C#→Lua 调用有效
ByValUserData(StructUserData)C#→Lua Push 拷贝、Type(...)to_user_dataByVal 实例元表Lua GC 管理;non-blittable 须 Registry
ByObjUserDataC# box 路径、zlua.box(struct 装箱)ByObj 实例元表与 class 对象路径同类

Opaque 与 StructUserData 互转: zlua.to_user_data(opaque)拷贝 到 StructUserData,两者独立;修改 userdata 影响原 opaque(若仍有效)。

4. ByVal 与 ByObj 双实例元表

值类型在 CLR 中既可 box 后当作 object 传到 Lua,也可 按值(by-val) 直接传递。因此 同一 struct 类型在 Lua 侧存在两套用于实例的 metatable

路径userdata 类型载荷实例元表
ByObjObjectUserData托管对象指针(boxed 实例)ByObj 实例元表
ByValByValUserData值类型的 实际数据(payload)ByVal 实例元表(类型表 __instance_mt

类型表 T 仍只有一套;T.__instance_mt 描述 ByVal 语义。ByObj 路径挂接 ByObj 实例元表

4.1 实例方法 this 解析

设待调用的实例方法定义在类型 DefType 上:

路径DefType当前 structDefTypeclass 基类
ByObjthis = object 指针 + 跳过对象头 → unboxed payloadthis = boxed 对象指针
ByValthis = payload 首地址须先 Box,再以 object 指针this

禁止 混用:对 ByVal userdata 挂 ByObj 元表(或反之)→ 校验失败或 luaL_error

4.2 C# → Lua Push 路径选择

条件Push 结果
box / object 形参等ByObjUserData + ByObj MT
显式 StructUserData / blittable 拷贝ByValUserData + ByVal MT
同步链 by-val(默认 Handle)OpaqueValue lightuserdata
[LuaMarshalAs(OpaqueValue)] on by-val structOpaqueValue

5. Lua → C#:接受的实参形态

形态说明
OpaqueValue(lightuserdata)仅当由 C#→Lua 刚 Push、仍在有效 scope 内;Pop 时校验 + 绑定;不可 在 Lua 侧读写字段
StructUserData(ByValUserData)by-val:Pop 时 拷贝 payload;ref/out/in:绑定 payload 地址,真 ref(§6)
Type(...) 构造产物StructUserData payload;传给 ref T真 ref
UnpackedValues[LuaMarshalAs(UnpackedValues)] + FieldOrPropertyNames:连续多栈参数
Table[LuaMarshalAs(Table)] + FieldOrPropertyNames:单个 table

lightuserdata 无 Lua 侧创建 API(除 C#→Lua 产生的 OpaqueValue)。

5.1 默认不接受 table / 多参数

未标注 [LuaMarshalAs] 时,不能{ X=1, Y=2 }foo(x, y) 组装 struct 传入 C#;须使用 StructUserData、Type(...)、或显式标注 Table/UnpackedValues。

6. 写回语义与 ref / out / in

总览与完整分支见 03-BYREF.md。值类型要点:

Lua 实参ref/out/in A(A 为值类型)
ByValUserData 且类型 == Apayload 地址(可写回 userdata)
ByValUserDataA 为 Nullable<T>、userdata 类型 == T拷贝进栈上 Nullable<T> 临时变量,传临时地址(写回原 userdata)
OpaqueValue(类型兼容)传 handle 地址
其它(含可 by-val Pop 的形态)拷贝进栈临时变量,传临时地址(写回 Lua)
local p = Point2D(1, 2)
CS.Demo.Offset(p, 10, 20) -- payload 真写回
assert.equal(p.x, 11)

7. Table / UnpackedValues(struct)

规则以 02-MARSHAL-AS.md §5–§6 为准;struct 特例如下。

LuaMarshalTypeLua → C#C# → Lua
UnpackedValues连续 Pop N 个栈值,按 FieldOrPropertyNames 顺序 写入按名单 顺序 Push N 个值
TablePop 一个 table,按 键名 写入名单内成员Push 一个 table
  • FieldOrPropertyNames;缺失 → 绑定期错误
  • Table、Lua→C#:可选键 用成员名 ? 后缀(如 "Tag?"),缺键不赋值。

类型级标注示例:

[LuaMarshalAs(LuaMarshalType.Table, FieldOrPropertyNames = new[] { "X", "Y" })]
public struct Vector2
{
public float X;
public float Y;
}

解析优先级(与 02-MARSHAL-AS.md §8 一致):参数/返回值 > 类型级 > 默认。

8. 枚举(enum)与 struct 的区别

枚举 默认 C#↔Lua 为 integer/number, 走 struct userdata(见 08-ENUM.md)。

能力structenum
类型表 SMT.__callPoint2D(...)
ByVal StructUserDataSMT.__callref 走拷贝或 C# 推送 StructUserData
boxed 实例zlua.box → ByObjUserDatazlua.box → ByObjUserData
默认形参StructUserData / integer 等integer/number 或 boxed
local Color = CSharp.AC['MyGame.Color']
local v = Color.Red -- integer/number 常量
local boxed = zlua.box(Color, Color.Green) -- ByObjUserData
SetColor(Color.Red) -- 默认形参 OK
SetColor(boxed) -- 默认形参 OK

ref Color 裸 integer 走 拷贝 分支;需 observable 写回时须 C#→Lua 推送的 StructUserData,或改用 struct 形参。boxed 场景用 zlua.box StructUserData ref 模型。

9. zlua.box / zlua.unbox / zlua.cast

APIstruct 语义
zlua.box(typeArg, value)将值类型 装箱ByObjUserDatavalue 可为 ByVal userdata、标量或构造参数
zlua.unbox(boxedValue)ByObjUserData 解箱为 ByVal StructUserData(或等价可访问实例)
zlua.cast(obj, targetType)引用类型门面切换;struct 场景见 06-CLASS.md 与类型系统

unbox 不接受 ByVal userdata(已是 unboxed 形态)→ 报错。

10. 生命周期

[C# 调 Lua,同步链]
Push OpaqueValue(handle) → h
lua_pcall(...) -- Lua 仅可 get/set_opaque 或 to_user_data(h)
C# 返回 -- h 失效

Lua 保存 h 至下次调用 → 使用时报错

需长期持有 → zlua.to_user_data(h) 或 C#→Lua StructUserData 路径
形态失效条件
OpaqueValueC#→Lua 调用返回;或 scope EndScope 递增 generation
StructUserDataLua GC 回收 userdata(__gc 释放 Registry 条目)

11. Non-blittable struct 要求(规范层)

Non-blittable struct 的 ByValUserData 路径须满足:

  1. userdata 内 完整拷贝 struct 实例(含引用字段)。
  2. GC 须能扫描 userdata 内 struct 内存中的托管引用(实现层可用 Registry + push root 回调等;Mono 可用 GCHandle)。
  3. userdata __gc 与 Registry 对称释放

Blittable struct 仅 memcpy 到 userdata payload,无额外 GC 登记。

12. 与元表 / 类型表的衔接

  • value type 类型表:__struct = true(见 ../02-TYPE-SYSTEM.md)。
  • ByValByValUserData + ByVal 实例元表__instance_mt)。
  • ByObjObjectUserData + ByObj 实例元表
  • Opaque 实例 metatable;不可 : / .
  • 禁止 通过实例访问 static 成员。

13. Mono / Il2Cpp 一致性

要求
三种形态(Opaque / ByVal / ByObj)Lua 脚本 不区分 平台
Handle 过期两边均 luaL_error
to_user_data拷贝语义一致
Table / UnpackedValues / ? 后缀同一 [LuaMarshalAs] 配置两边生效
ref/out/in StructUserData 真 ref语义一致;Mono 实现可 pin/box,但行为须对齐

14. 相关文档

主题文档
默认矩阵01-OVERVIEW.md
[LuaMarshalAs]02-MARSHAL-AS.md
byref03-BYREF.md
OpaqueValue04-OPAQUE.md
枚举08-ENUM.md
zlua.* API../05-LIB.md
类型表 / __call../02-TYPE-SYSTEM.md