跳到主要内容

[LuaMarshalAs]LuaMarshalType

规范性: 参数、返回值、字段、属性及类型(class / struct)上的编组覆盖规则。
默认矩阵: 未覆盖时见 01-OVERVIEW.md
源码: ZLua.Common 中的 LuaMarshalAsAttributeLuaMarshalType

1. 概述

[LuaMarshalAs] 可标注于:

  • 参数返回值字段属性
  • 类型class / struct 上的类型级默认)

不可 标注于 方法(须对每个参数/返回值分别标注)。

覆盖标注须符合 §3 合法集合,否则 §4.1 回退 Default 并在 Editor 打错误日志。

public enum LuaMarshalType
{
Default,
UserData,
Bytes,
OpaqueValue,
UnpackedValues, // struct / class / interface:多 Lua 栈槽 ↔ 列出的成员
Table, // struct / class / interface:单个 Lua table ↔ 列出的成员
ParamsTable, // params T[]:顺序 table ↔ 数组
}

[AttributeUsage(
AttributeTargets.Parameter | AttributeTargets.ReturnValue |
AttributeTargets.Field | AttributeTargets.Property |
AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class LuaMarshalAsAttribute : Attribute
{
public LuaMarshalType LuaMarshalType { get; }

/// <summary>
/// <see cref="LuaMarshalType.Table"/> / <see cref="LuaMarshalType.UnpackedValues"/> 必填。
/// 元素为 CLR 字段名或 property 名,可混合;顺序即 UnpackedValues 的栈顺序 / Table 的读写顺序。
/// 名字以 '?' 结尾表示 Table、Lua→C# 时缺键不赋值(§6)。
/// </summary>
public string[] FieldOrPropertyNames { get; set; }

public LuaMarshalAsAttribute(LuaMarshalType luaMarshalType = LuaMarshalType.Default);
}

2. LuaMarshalType 枚举说明

适用方向说明
Default双向使用 01-OVERVIEW.md 默认规则,不做额外转换。
UserData双向 可标注于 托管引用类型struct(§3);不可 用于基元 / enum / IntPtr 等。语义:强制走 UserData 形态(ByObjUserData / ByValUserData / ClassUserData 等)。
实质有效的目标: 几乎只有 string——默认 C#↔Lua 为 Lua string,标注后改为 ByObjUserData(托管 System.String 对象)
class / interface / 数组 / 普通 struct / object: 默认 marshal 已是 UserData,标注与 Default 等价
Delegate 可标注,但 无实质作用——仍按 09-FUNCTION.md 编组为 Lua function 或既有 bridge
Bytes双向强制 在 C# byte[] 与 Lua string 之间转换。
• C# byte[]:默认 ByObjUserData → Lua string(原始 octet,非 UTF-8 文本语义)
• 标注于 byte[] 形参/返回值 时,Lua 侧须传 string;Pop 时 不接受 ByObjUserData / table
• 若标注于 string 形参/返回值,则走 byte[] ↔ string 的对偶规则
OpaqueValue仅 C# → LuaPush OpaqueValue(lightuserdata,无 metatable),见 04-OPAQUE.md
ref / out / in T(任意 T默认 即为本形态(无需标注)
by-val(任意 CLR 类型)均可 显式标注强制 Push Opaque;对 基元 / enum / IntPtr 合法但通常 无实质必要(默认已是轻量 integer/number)
• 脚本经 zlua.get_opaquevalue / zlua.set_opaquevalue 读写;回传给 C# 时遵循 04-OPAQUE.md §6
不可 跨调用持久化;Lua → C# 单独形参上标注本类型视为 非法(§3.1)
UnpackedValues双向struct / class / interface(§3)。将 §5FieldOrPropertyNames 列出的成员与 多个连续 Lua 栈槽 互转:
Lua → C#:从栈上按名单 顺序 Pop N 个值,写入对应 field/property
C# → Lua:按名单 顺序 Push N 个值(多返回值或展开 push)
须配置 FieldOrPropertyNames;未配置 → 绑定期错误(§4.2)
Table双向struct / class / interface(§3)。将 §5FieldOrPropertyNames 列出的成员与 单个 Lua table 互转:
Lua → C#:Pop 一个 table,按 键名 读取并写入 field/property
C# → Lua:Push 一个 table,键为成员名,值为各成员 marshal 结果
须配置 FieldOrPropertyNames;未配置 → 绑定期错误(§4.2)
ParamsTable双向params T[] 形参(§3、§7)。在默认 szarray 规则之上,强制 可变参数段 顺序 table 编组:
C# → Lua:Push 一个 table,[1]…[n] 为各元素
Lua → C#:Pop 一个 table(数组形态); 接受 ByObjUserData 占据 params
使用 FieldOrPropertyNames

3. 各类型的合法 LuaMarshalType 集合

每个 CLR 形参/返回值/字段类型仅允许绑定 §2 中与其语义相容的 LuaMarshalTypeDefault 对所有类型均合法)。Codegen / Mono 反射在 解析标注时 须先查表;不在合法集合内 的标注视为 无效,见 §4

下表「合法集合」列出的为 Default 之外 可显式标注的值;未列出的 LuaMarshalType 对该类型 非法

OpaqueValue 所有 CLR 类型均可在 C#→Lua 方向合法标注(§3.1);ref/in/out 默认已是 Opaque,无需再标。对基元 / enum / IntPtr 等标注虽合法,通常 无实质收益

C# 类型(分类)合法 LuaMarshalTypeDefault 除外)说明
基元boolcharbyteulongfloatdoubleOpaqueValueUserData 非法OpaqueValue 合法但通常无实质必要。若为 ref/in/out 基元 → 默认已是 Opaque(见「byref」行)
IntPtr / UIntPtr / nint / nuintOpaqueValue同基元
stringUserDataBytesOpaqueValueUserData:强制 ByObjUserData;Bytes:octet string;OpaqueValue仅 C#→Lua
byte[]BytesUserDataOpaqueValueBytes:↔ Lua string;UserData 与默认等价;OpaqueValue仅 C#→Lua
T[](szarray)UserDataOpaqueValueUserData 与默认等价;OpaqueValue仅 C#→Lua
T[,…](mdarray)UserDataOpaqueValue同上;Lua→C# 因标注接受 table
enumOpaqueValueby-val 不可 UserData;boxed 用 zlua.box08-ENUM.md)。OpaqueValue 合法但通常无实质必要。ref/in/out enum → 见「byref」
struct(普通值类型 struct)UserDataOpaqueValueTableUnpackedValuesUserData 与默认等价;OpaqueValue仅 C#→LuaTable / UnpackedValues 须名单
classUserDataTableUnpackedValuesOpaqueValueUserData 与默认等价;OpaqueValue仅 C#→Lua
interfaceUserDataOpaqueValueTableUnpackedValues默认 ByObjUserData;UserData 与默认等价;Table / UnpackedValues 须名单(同 class)
Delegate 及子类UserDataOpaqueValueUserData 无实质作用;OpaqueValue仅 C#→Lua
objectUserDataOpaqueValueOpaqueValue仅 C#→Lua
ref / in / out T(任意 T)(通常无需标注)C#→Lua 默认 OpaqueValue(04-OPAQUE.md)。显式 [OpaqueValue] 合法
Nullable<T>T 的合法集合(含 OpaqueValueT 为基元/enum 时 by-val 除 Default/OpaqueValue 外无其它合法值
非托管指针T*void* 等)OpaqueValue默认可 Default(Pointer 透传,见 10-POINTER.md);亦可标 OpaqueValue(C#→Lua)
函数指针delegate*<…>OpaqueValue同非托管指针
TypedReference(通常无需标注)默认即 OpaqueValue(双向均 此形态,见 10-POINTER.md)。显式 [OpaqueValue] 合法且等价;UserData / Table非法
decimalOpaqueValuev1 默认 by-val 仍可不支持;OpaqueValue(C#→Lua)合法
ref structSpan<T> 等)OpaqueValue不能作为普通 by-val 默认 marshal;OpaqueValue(C#→Lua)合法
params T[] 形参ParamsTableOpaqueValue默认同 szarray(§7);ParamsTable 强制仅 table;OpaqueValue仅 C#→Lua

3.1 方向过滤(与上表叠加)

LuaMarshalType允许标注的方向
UserDataBytesTableUnpackedValuesParamsTable双向(Pop / Push 均可能生效,以形参/返回值方向为准)
OpaqueValue仅 C# → Lua(返回值、或 C# 调 Lua 时的 push 实参);标注于 纯 Lua→C# 形参 时视为 非法

4. 非法标注与配置错误

4.1 类型 / 方向非法(回退 Default)

[LuaMarshalAs(LuaMarshalType.X)]X ≠ Default不满足 §3(类型不在合法集合、或 OpaqueValue 用于不允许的方向)时:

行为说明
编组Default 处理——与未标注相同; 因非法标注中断调用
日志仅 Editor 输出 错误级 日志(成员签名、CLR 类型、非法 LuaMarshalType、回退 Default)
Player静默回退 Default

示例:

[ZLua] Invalid LuaMarshalAs: ...EchoInt(int value)
parameter 'value' (System.Int32): LuaMarshalType.UserData is not allowed; falling back to Default.

4.2 Table / UnpackedValues 配置错误(绑定期失败)

下列情况 回退 Default,须在 Codegen / Weaver / Mono 首次绑定 阶段 失败(Editor 与 CI 可见):

条件行为
LuaMarshalTypeTableUnpackedValues,但 FieldOrPropertyNames 缺失、为空数组绑定失败
名单中某名称 不是 目标类型上可访问的 public field 或 property绑定失败
property Lua→C# 不可写(无 public set / init)绑定失败
property C#→Lua 不可读(无 public get)绑定失败
名字以 ? 结尾但 LuaMarshalType 不是 Table绑定失败
ParamsTable 标注于 params 形参,或形参类型 不是 一维 T[]绑定失败
UnpackedValues 且 Lua 实参个数 名单长度(? 不参与计数,Unpacked 不支持 ?运行时 luaL_error

示例:

[ZLua] LuaMarshalAs configuration error: ...Foo(MyStruct v)
LuaMarshalType.Table requires non-empty FieldOrPropertyNames.

5. TableUnpackedValues(struct / class / interface)

适用范围: classinterfacestruct(非 ref struct)。

默认行为: 接受 Lua table 或多栈参数组装整个对象;须显式标注 TableUnpackedValues 并提供 FieldOrPropertyNames

5.1 成员名单

  • 类型为 string[],元素为 CLR field 名property 名可混合
  • 顺序 即语义顺序:UnpackedValues 的栈槽顺序;Table 的读写遍历顺序(键仍按 成员名 查找,与顺序无关)。
  • 绑定期校验:名称存在、读写权限满足当前 Pop/Push 方向。

5.2 UnpackedValues 示例

void Foo([LuaMarshalAs(LuaMarshalType.UnpackedValues, FieldOrPropertyNames = new[] { "Y", "X" })] Vector2 v);
Foo(2.0, 1.0) -- 第一槽 → Y,第二槽 → X;不是 table
[return: LuaMarshalAs(LuaMarshalType.UnpackedValues, FieldOrPropertyNames = new[] { "X", "Y" })]
Vector2 GetPos();
-- Lua: local x, y = CS.Demo.GetPos()

5.3 Table 示例

void Foo([LuaMarshalAs(LuaMarshalType.Table, FieldOrPropertyNames = new[] { "X", "Y" })] Vector2 v);
Foo({ X = 1, Y = 2 })

5.4 class 补充

Pop 时须能构造实例(如无参 ctor + property setter,或实现层文档规定的工厂);名单内 引用类型成员 按该成员类型的 01-OVERVIEW.md 默认规则递归 Pop/Push。

5.5 嵌套限制

名单成员类型若为 struct/class,默认 自动展开为 table/多槽;须该成员类型自身标注或走 userdata 默认路径(v1 可限制名单仅含标量 / enum / string 等)。

6. Table 可选成员:? 后缀

仅当 LuaMarshalType.Table 且方向为 Lua → C# 时:

  • FieldOrPropertyNames 中某元素以 ? 结尾(如 "OptionalTag?")表示 可选键
  • 解析时去掉尾部 ? 得到 CLR 成员名;Lua table 缺该键跳过赋值(不报错)。
  • struct / class 在写入前已 零初始化 / default,跳过即保持 默认值
  • ? 后缀 的成员:table 缺键luaL_error
  • UnpackedValues 不支持 ?;缺槽即 arity 错误。
[LuaMarshalAs(LuaMarshalType.Table, FieldOrPropertyNames = new[] { "X", "Y", "Tag?" })]
public struct MyDto { public int X; public int Y; public string Tag; }
Foo({ X = 1, Y = 2 }) -- OK;Tag 保持 null
Foo({ X = 1, Y = 2, Tag = "a" }) -- OK
Foo({ X = 1 }) -- 缺 Y → error

7. params T[] 形参与 ParamsTable

范围:普通 C# 方法 / 构造函数 上带 params 修饰的一维数组形参。[LuaInvoke] / delegate bridge 上的 params不支持(见 09-FUNCTION.md)。

与 szarray 的关系: params T[] 的编组规则与 01-OVERVIEW.md §4 szarray 相同(C#→Lua ByObjUserData;Lua→C# ByObjUserData数组形态 table)。差异在于 Lua 侧传参形态空 / null 语义

7.1 默认行为(未标注或 Default

方向规则
C# → Lua与 szarray 相同:Push T[]ByObjUserData 解压为多栈槽、 默认 Push table)
Lua → C#params 位占单个栈槽;Pop ByObjUserData数组形态 table,构造 / 传入 T[]

禁止 C# 式隐式展开: Lua 不支持params 形参之后的 多个连续实参 自动收集为 T[]。必须 显式 传入 一个 实参占据 params 位置:

传入C# 收到
ByObjUserDataT[] 实例)该数组引用
table {}(空数组形态)T[0](零长度数组)
table { … }1…n 连续键)按元素构造的 T[n]
nilnull 空数组)

示例:

static void Sum(params int[] values) { /* … */ }
static void Prefix(int head, params int[] tail) { /* … */ }
-- ✅ 合法:显式数组 userdata
CS.Demo.Sum(arr) -- arr 为 int[] ByObjUserData

-- ✅ 合法:显式 table
CS.Demo.Sum({ 1, 2, 3 })
CS.Demo.Sum({}) -- 零个元素 → T[0]
CS.Demo.Prefix(0, { 1, 2 }) -- tail = {1,2}

-- ✅ nil → null(非空数组)
CS.Demo.Sum(nil)

-- ❌ 非法:多槽隐式收集(不支持)
-- CS.Demo.Sum(1, 2, 3)
-- CS.Demo.Prefix(0, 1, 2)

与重载分派: params 形参在 overload 匹配时仍计为 单个 形参位; 将后续栈槽并入 params 段(见 ../04-METHOD-OVERLOAD.md)。

7.2 ParamsTable(显式标注)

params T[] 形参 上标注 [LuaMarshalAs(LuaMarshalType.ParamsTable)] 时:

方向规则
C# → LuaPush 一个 table;键 1nt[i] 为第 i-1 个元素
Lua → C#Pop 一个 table(01-OVERVIEW.md §4.4 数组形态约束); 接受 ByObjUserData

空 / null: {}T[0]nilnull

不接受: Lua → C# 时用 多个栈槽ByObjUserData;稀疏 table、字符串键 table。

FieldOrPropertyNames 不适用

7.3 与 struct Table 对比

struct / class TableParamsTable
适用struct / class / interface 形参params T[]
Lua 键成员名(field/property)整数 1…n(数组段)
默认(无标注)userdata,不自动 table同 szarray(ByObj table); 多槽收集
FieldOrPropertyNames必填不使用

8. 解析优先级

Codegen / Mono 反射在 Pop / Push 时按 由细到粗 解析:

  1. 参数 / 返回值 上的 [LuaMarshalAs](若 ≠ Default
  2. 类型 上的 [LuaMarshalAs] / XML 配置(class / struct 类型级)
  3. 01-OVERVIEW.md 内置默认

支持方法级 [LuaMarshalAs];方法上若出现该属性,绑定层应视为 配置错误忽略并告警(推荐 Editor 绑定期失败)。

任意 参数 / 返回值(或合法类型级)标注 ≠ Default 时,Il2Cpp Codegen 对该方法生成 专用 push/pcall/pop 代码。Table / UnpackedValues 在绑定期展开 FieldOrPropertyNames 运行时反射。

解析过程中若标注 类型非法(§3),该条 视为未设置 并回退;若 配置错误(§4.2),绑定 失败

场景默认[LuaMarshalAs] 覆盖
C# 调 Lua,struct 形参视上下文可能为 OpaqueValue 或 userdataOpaqueValue → 强制 OpaqueValue
C# 调 Lua,ref intOpaqueValue(默认)无需标注
C# 调 Lua,by-val int + OpaqueValueinteger合法;Push Opaque(通常无实质必要)
Lua 调 C#,string 形参Lua stringUserDataByObjUserData
Lua 调 C#,byte[] 形参ByObjUserData 或 tableBytes → Lua string
struct / class / interface 形参userdataTable / UnpackedValues
params T[] 形参同 szarrayParamsTable table

9. 相关文档

主题文档
默认矩阵01-OVERVIEW.md
OpaqueValue04-OPAQUE.md
struct 编组05-STRUCT.md
数组 / Bytes07-ARRAY.md
枚举 boxed08-ENUM.md