跳到主要内容

Class / Interface 编组

规范性: 引用类型(classinterfacestring、数组实例、delegate 实例等)在 Lua 与 C# 之间的默认编组规则。
相关: 类型表与成员访问 → ../02-TYPE-SYSTEM.mdref/out/in 总览 → 03-BYREF.md[LuaMarshalAs]02-MARSHAL-AS.mdzlua.cast../05-LIB.md

平台原则: Mono 与 Il2Cpp 的 Lua 可见语义一致;class 实例默认 GCHandle + full userdata(Il2Cpp:ObjectRegistry + Il2CppObject*)。


1. 默认编组(概要)

未标注 [LuaMarshalAs] 时,引用类型遵循 01-OVERVIEW.md 总览矩阵;本节补充 class / interface 特有语义。

方向Lua 形态说明
C# → LuaClassUserData(ByObj full userdata)引用身份;IMT 门面 = 声明类型(见 §2)
Lua → C#ClassUserData 或 nil校验可赋值给目标 声明类型nilnull
stringLua stringnil仅当声明类型为 string;声明为 object 时仍为 Object userdata
interface同 class门面 = 接口声明类型(非实现类);见 §2、§4
数组ByObjUserData07-ARRAY.md
Delegatefunction 或 DelegateUserData09-FUNCTION.md

UserData 形态: ClassUserData 为 lua_newuserdata + 实例元表 IMTfull userdata;脚本侧经 : / . 访问成员。与 04-OPAQUE.md 的 OpaqueValue(lightuserdata、无 metatable)及 10-POINTER.md 的 Pointer 不同。

字段、方法、静态成员访问细节见 ../02-TYPE-SYSTEM.md../metatable/


2. 声明类型门面(View / 与实际类型)

对所有 引用类型 形参、返回值、字段/属性(class / interface / object / 数组 / delegate 等),编组层区分 IdentityView

概念含义
Identityuserdata 载荷持有的托管对象引用(运行时 实际实例
View / 门面userdata 挂接的 IMT 与成员可见性;唯一来源 = 本次编组的声明类型

2.1 规则

  1. C# → Lua:始终按 声明类型 选择默认 marshal 形态与 ByObj IMT;因运行时实际类型不同而改挂更具体类型的 mt,也 因此改走 string 等特殊编组(例如 object 形参上的 string 实例仍为 Object userdata,不是 Lua string)。
  2. 值类型:无继承门面问题;仍按 05-STRUCT.md 等规则。
  3. 虚方法:成员查找使用 声明类型 上的 MethodInfo;调用时对真实 this虚表派发override 仍落到实现类)。
  4. 非虚 / new 隐藏:走声明类型槽位(经 Base 门面调用 new 隐藏的 Bar 得到 Base.Bar)。
  5. Downcast:仅 zlua.castIsAssignableFrom(targetType, obj.klass));返回 新 userdata(同 identity、新门面)。
  6. 对象缓存:键为 (identity, viewType);同一实例可有多个视图 userdata。

2.2 示例

Base CreateChild() => new Child();
local o = ObjectFactory.CreateChild() -- 门面 Base:不可见 Child.y;new Bar → Base.Bar
local c = zlua.cast(o, Child) -- 门面 Child(须 IsAssignableFrom)

虚方法经 Base 门面查找 MethodInfo,再对真实实例 虚派发

2.3 object 形参

规则
PushClassUserData,门面为 System.Object
Pop接受 boolean / number / string / userdata
运行时类型 按运行时类型改写编组(运行时 string 仍为 Object userdata)

Nullable<T> 其中 T 为引用类型时,nullnil;有值时同 T 的 class 规则。见 01-OVERVIEW.md


3. Table / UnpackedValues(class / interface)

classinterface 默认 均为 ByObjUserData(ClassUserData), 默认接受 Lua table 或多栈参数整对象组装。

须显式标注 [LuaMarshalAs]TableUnpackedValues,并配置 FieldOrPropertyNamesstring[],field/property 可混合):

形态Lua 侧说明
Table单个 table ↔ 名单成员键为成员名;可选 ? 后缀见 02-MARSHAL-AS.md
UnpackedValues连续 N 个栈槽 ↔ 名单成员顺序与名单一致

Pop 时须能构造 实现该接口的实例 或 class 实例(如无参 ctor + property setter,或实现层规定的工厂);名单内成员须为接口 / 类型上可访问的 public field/property;引用类型成员 按该成员类型的 §1 默认规则递归 Pop/Push。

interfaceclass / struct 一样,允许 Table / UnpackedValues(合法集合见 02-MARSHAL-AS.md)。


4. Interface 编组

规则
默认 C# ↔ LuaByObjUserData(ClassUserData);门面 = 接口声明类型(非实现类)
nilnull
[LuaMarshalAs]可与 class 相同覆盖:UserDataOpaqueValueTableUnpackedValues(见 02-MARSHAL-AS.md
成员访问(默认 userdata)仅接口上可见成员 + 继承的接口成员;实现类独有成员不可见

5. ref / out / in 引用类型形参(Lua → C#)

总览见 03-BYREF.md。引用类型(含 string)要点:

Lua 实参行为
OpaqueValue(与 A 类型兼容)传 handle 地址(可写回原槽)
ByObjUserData / Lua string(仅当 A 为 string)/ nil / 其它可 Pop 形态取得托管指针(或 null)→ 写入 栈临时变量 → 传临时地址

5.1 写回:临时槽 ⇒ 无 rebind

C# 侧操作Lua 侧(临时槽路径)
refParam = otherObject重新绑定不可见
可变对象 原地修改可见(同一托管对象)
ref string 赋新字符串不可见(等同 rebind)
local s = "hello"
CS.Demo.ChangeString(s) -- void ChangeString(ref string s) { s = "world"; }
-- s 仍为 "hello"

local sb = StringBuilder("hi")
CS.Demo.Append(sb, "!") -- 共享引用,内容可变

5.2 C# → Lua

ref/out/in 默认 Push OpaqueValue,见 04-OPAQUE.md


6. string 编组补充

声明类型C# → LuaLua → C#
stringLua stringLua stringnil
object(运行时 stringObject userdata(门面 System.Object按 object Pop 规则
[LuaMarshalAs(UserData)] on stringByObjUserData(托管 System.String 对象)强制 userdata 路径

[LuaMarshalAs(Bytes)] 用于 byte[] ↔ Lua string,不是 System.String;见 07-ARRAY.md


7. Mono / Il2Cpp 一致性

要求
默认 Push / PopClassUserData;nilnull
门面 (identity, viewType)两平台一致
zlua.cast同 identity、新 view
C#→Lua byrefOpaqueValue(04-OPAQUE.md
错误消息一致或等价

8. 相关文档

文档内容
01-OVERVIEW.md默认编组矩阵
03-BYREF.mdref / in / out 总规则
04-OPAQUE.mdC#→Lua byref 默认 OpaqueValue
07-ARRAY.md数组 ByObjUserData
09-FUNCTION.mdDelegate 编组
../02-TYPE-SYSTEM.md类型表、IMT、成员访问
../05-LIB.mdcastbox