跳到主要内容

C# 与 Lua 参数 Marshal 设计规范

本文档为 参数编组总览;具体类型规则见分册:

文档类型
../../type-system-spec.md类型解析、元表、成员访问、枚举类型表
../../marshal/struct.mdstruct(值类型)、枚举 userdata 构造StructStackScope
../../marshal/class.mdclass、引用类型、数组元素
../../marshal/function.mdDelegate、Lua 函数回调
../../method-overload-spec.md重载与实参匹配

平台原则: Mono 与 Il2Cpp 的 Lua 可见编组语义一致;Il2Cpp 侧重零 GC 与生成代码快速路径。

函数 / delegate: Lua 调用 C# 方法时,delegate 形参接受 Lua function,由 MethodBridge 隐式 marshal,规则见 ../../marshal/function.md §4.0。

覆盖标注: 参数、返回值、方法或字段上的 [LuaMarshalAs] 可覆盖默认规则;须符合 §6.2 合法集合,否则 §6.3 回退 Default 并在 Editor 打错误日志。


1. 默认 Marshal 规则总览

未标注 [LuaMarshalAs](或标注为 LuaMarshalType.Default)时,各类型在 C# ↔ Lua 双向调用中的默认编组如下。

C# 类型C# → LuaLua → C#说明
boolbooleanboolean
charinteger / numberinteger / number按 Unicode 码点(16 位)
byteinteger / numberinteger / number见 §1.1
sbyteinteger / numberinteger / number见 §1.1
shortinteger / numberinteger / number见 §1.1
ushortinteger / numberinteger / number见 §1.1
intinteger / numberinteger / number见 §1.1
uintinteger / numberinteger / number见 §1.1
longinteger / numberinteger / number见 §1.1
ulonginteger / numberinteger / number见 §1.1;须落在 Lua integer 可表示范围
floatnumbernumber
doublenumbernumber
IntPtrinteger / numberinteger / number指针 数值ToInt64 / new IntPtr);与 §7.1 非托管指针 不同
UIntPtrinteger / numberinteger / number指针 数值ToUInt64 / new UIntPtr);与 §7.1 不同
nint / nuintIntPtr / UIntPtrIntPtr / UIntPtrC# 本机整数别名;语义与对应指针宽度类型一致
T*(非托管指针,如 int*void*Pointer(lightuserdata)Pointer(lightuserdata)透传;见 §7.1
函数指针(如 delegate*<int,int>Pointer(lightuserdata)Pointer(lightuserdata)透传;见 §7.2
System.TypedReference不支持不支持无互操作意义;见 §7.3
stringstringstring
byte[]ArrayUserDataArrayUserDataT[] 相同;[LuaMarshalAs(Bytes)] 时改为 ↔ string,见 §6
classClassUserDataClassUserData引用身份;nilnull
T[](一维 / szarray)ArrayUserDataArrayUserData../../marshal/class.md../../type-system-spec.md §7
T[,] 等多维(mdarray)ArrayUserDataArrayUserData同上
enuminteger / numberinteger / numberenum userdata默认 推送 userdata;详见 §2
structOpaqueValue(lightuserdata)StructUserDatatableC#→Lua 默认 StructHandle(§4);Lua→C# 亦接受 _ctor / __call 产物;table 规则见 ../../marshal/struct.md §6.4
DelegateDelegateUserDatafunctionDelegateUserDataLua→C# 接受 Lua function 隐式 bridge;见 ../../marshal/function.md
object运行时类型 Pushboolean / number / string / userdata多态装箱;见 §7.4
Nullable<T>TnilTnilT 为值类型时 nilnull;见 §7.4
interfaceClassUserDataClassUserData与 class 相同,按 实现对象 编组
decimal暂不支持(默认)暂不支持(默认)见 §7.4
ref struct(如 Span<T>../../marshal/struct.md / ../../lib-spec.md../../marshal/struct.md不能作为普通 by-val 形参默认传递;见 §7.4
void(返回值)(无)
null / nilnilnil引用类型Nullable、delegate 等可空形态

UserData 形态: 上表中的 ClassUserData、ArrayUserData、StructUserData、enum userdata、DelegateUserData 均为带类型元表的 full userdatalua_newuserdata + metatable),脚本侧经 : / . 访问成员;与 §4 的 OpaqueValue(无 metatable 的 lightuserdata)以及 §7.1 的 Pointer(无 metatable、仅透传的 lightuserdata)不同。

1.1 integer 与 number

  • Lua 5.4+:整型基元、char、枚举底层整型、IntPtr / UIntPtr 数值优先使用 integerlua_pushinteger / lua_isinteger)。
  • 不支持 integer 的 Lua 版本:退化为 number,须为整数值(无小数部分)。
  • Il2Cpp Codegen 与 Mono 反射路径的 可见语义一致;仅实现层 API 不同。

(class、struct、数组等详细规则见各分册。)


2. 枚举(enum)

枚举在 C# 中为 值类型,底层为单一整型字段。Lua 侧 默认不按 userdata 传递,而按 integer / number 编组;需要 boxed 形态时通过类型表 _ctor / __call 构造 userdata(见 ../../marshal/struct.md §5.4、../../type-system-spec.md §3.5)。

2.1 默认规则(C# ↔ Lua)

方向默认形态说明
C# → Luainteger(优先)或 number推送枚举的 底层整数值,不推送 userdata
Lua → C#integer / number接受整型 Lua 值,按目标枚举 底层类型 转换并 Enum.ToObject / 等价路径
Lua → C#(备选)enum userdata从 userdata payload 读出底层整型再转换(见 ../../marshal/struct.md §5.4)

不接受(除非 [LuaMarshalAs] 另行规定):将枚举默认编组为 string(枚举名)、boolean、或普通 table

2.2 底层类型与范围

Codegen / 反射须读取枚举 underlying typeSystem.Int32System.Byte 等):

  • Pop 时校验 Lua 整型值是否落在底层类型可表示范围内;越界 → luaL_error
  • Push 时使用与底层类型宽度一致的 integer/number 语义。
底层类型Push 优先Pop 接受
sbyteulonginteger / numberinteger / number(整型)
非整型底层(罕见)numbernumber

2.3 与类型表常量字段的关系

CSharp[assembly][EnumType].MemberName 在类型表上暴露为 同名 integer/number 字段(非 userdata),其值等于该枚举常量的底层整型值。详见 ../../type-system-spec.md §3.5。

下列写法在作为 enum 形参 时等价(默认 marshal):

local e = CSharp.AC['MyGame.Color'].Red -- integer/number 常量
foo(e)
foo(CSharp.AC['MyGame.Color'].Red)
foo(1) -- 裸整型,须能转换为该 enum

2.4 userdata 形态(非默认,显式构造)

当脚本需要 enum 实例 userdata(长生命周期持有、与 struct 相同的 : 实例 API、或 [LuaMarshalAs] 强制 userdata)时:

local boxed = CSharp.AC['MyGame.Color'](CSharp.AC['MyGame.Color'].Red)
-- 或
local boxed = CSharp.AC['MyGame.Color']._ctor(CSharp.AC['MyGame.Color'].Blue)

构造语义与 blittable struct_ctor → userdata 相同,见 ../../marshal/struct.md §5.4。

作为 enum 形参 传入 C# 时,userdata 与 integer/number 均接受(默认规则 §2.1)。

2.5 [LuaMarshalAs] 扩展

类型级或参数级可覆盖 §1 默认行为;LuaMarshalType 语义见 §6。struct 相关的双向配置(如 StructUserDataComposeFromStack)另见 ../../marshal/struct.md §7。

2.6 Mono / Il2Cpp 一致性

要求
默认 Push / Popinteger/number ↔ 底层整型
常量字段类型表上为 integer/number
_ctor / __call单整型参数 → enum userdata
错误消息一致或等价

3. ref / out / in(Lua → C#)

范围:Lua 调用 C# 方法/构造时的形参;[LuaInvoke]、delegate bridge 不支持 ref/out(见 ../../marshal/function.md §9)。

统一规则: Lua 侧 不区分 ref / out / in,均按 ref 语义 处理;C# 侧仍保留各自 CLR 语义(in 只读等)。

3.1 核心原则:StructUserData = 真 ref,否则 = 拷贝

Lua 实参C# ref/out/in 形参 Pop 行为C# 修改后 Lua 侧
StructUserData(见 §3.2)且类型与 T 一致绑定 payload 地址 / box,真 refuserdata 内值/字段已更新
其他可转换形态(number、string、table、nil…)by-val 规则读入 临时 ref 槽拷贝语义不变不报错
local x = 5
CS.Demo.Increment(x) -- 正常执行;C# 内 ref 参数已变,Lua 的 x 仍为 5

local n = zlua.new_ref(zlua.types.int32, 5)
CS.Demo.Increment(n) -- 真 ref;zlua.deref(n) 或读 payload 得新值

设计理由: 裸 Lua 值无 lvalue,无法回写 local;与 C# 将 by-val 实参隐式拷入临时变量再取 ref 的行为一致,脚本不应因此失败。

3.2 何谓 StructUserData(ref 变量)

满足以下条件的 userdata 视为 ref 变量(真 ref 实参):

来源说明
zlua.new_ref(ref_type [, value, ...])显式 ref 槽;见 ../../lib-spec.md §6
值类型 _ctor / __call 产物Point2D(1, 2);与 new_ref 共用 payload 存储,传给 ref Point2D真 ref
enum _ctor / __call 产物Color(1);传给 ref Color 为真 ref
C#→Lua 推送的 struct StructUserData长生命周期 userdata;Lua 再传入 ref T 为真 ref

类型校验: Pop 时 userdata 绑定类型须与形参元素类型 TparameterType.GetElementType()精确匹配;不匹配 → luaL_error

非 ref 变量: 基元/enum 的 integer、string、table 等 不是 StructUserData,走 §3.1 拷贝分支。

3.3 按元素类型 T 的分支

T真 ref(StructUserData)拷贝语义(非 StructUserData)
基元 / enumpayload 原地更新临时槽;Lua 裸值不变
structpayload / box 原地更新;字段经 IMT 可见临时 struct 副本;原 Lua 值不变
class / string / array / delegate共享对象引用;C# 对 ref 重新赋值不回 Lua同左;临时槽丢弃 rebind

引用类型 重新绑定 ref(refParam = other永不写回 Lua;可变对象原地修改(如 ref StringBuilder)仍通过共享引用可见。详见 ../../marshal/class.md §2。

3.4 out 与缺省 / nil

实参拷贝分支StructUserData 分支
省略 / nil临时 default(T);Invoke 后 丢弃new_ref(T) / new_ref(T, nil)default(T);Invoke 后 payload 更新

若要取得 out 结果,须传入 zlua.new_ref(T)(或已存在的同类型 StructUserData)。

3.5 桥接流程(概念)

PopRefArgument(luaIndex, T, isOut):
if IsStructUserData(luaIndex) && userdata.Type == T:
return BindRef(&payload) // 真 ref;Invoke 后 Lua 已可见
value = PopByValue(luaIndex, T) // 与 by-val 相同
if isOut && (missing || nil): value = default(T)
temp = AllocTempRefSlot(T, value) // 仅本次 Invoke 有效
return BindRef(temp) // Invoke 后丢弃,不写回 Lua 栈/local

Il2Cpp:tempMethodBridge 栈帧;StructUserData 为 lua_newuserdata payload 指针。
Mono:可先 GCHandle box;可观察语义与 Il2Cpp 一致。

3.6 相关 API 与文档

文档
zlua.new_ref../../lib-spec.md §6
zlua.to_user_data本节 §4.4、../../lib-spec.md
struct payload / scope../../marshal/struct.md §5、§6.2
ref 引用类型../../marshal/class.md §2
delegate 不含 ref../../marshal/function.md §9

4. OpaqueValue(临时不透明参数)

本节定义 C#→Lua marshal 时,对 栈上临时形参/局部 的统一承载形态(原 StructValueOps 职责,现扩展并更名)。struct 默认路径(StructHandle)、以及需零拷贝暴露 引用类型形参槽 的场景,均走 OpaqueValue。参数/返回值标注 [LuaMarshalAs(LuaMarshalType.OpaqueLightUserData)]强制 走本形态,见 §6.3

4.1 Lua 可见形态

规则
Lua 类型lightuserdata
载荷handleId,类型为 uintptr_tvoid*,与 lua_pushlightuserdata / lua_touserdata 一致
平台须兼容 32 位:使用 uintptr_t / intptr_t不得假定 uint64_t 或固定 64 位编码
不透明性Lua 不能将其当作地址解引用;仅 C++ / 绑定层在 Validate 通过后解析
// Push(概念)
void* handleId = EncodeOrRegister(paramSlotPtr); // 见 §4.2
lua_pushlightuserdata(L, (void*)(uintptr_t)handleId);

无 metatable、无 __index / __newindex、无字段/方法访问。 脚本侧把它当作 不可操作的临时令牌

4.2 handleId 与参数地址

handleId 在逻辑上映射到 C# 调用栈上某一参数的存储地址StructStackScope 登记,见 ../../marshal/struct.md §3):

元素类型 Tstack[i] / 登记地址含义Resolve 结果
struct(值类型)指向 struct 实例字节void*T 的栈上实例地址
class 等引用类型指向 Il2CppObject*void*(即 Il2CppObject**引用槽地址;读 *slot 得对象指针

因此 OpaqueValue 不仅能表达 struct,也能表达 object 形参槽;与长生命周期的 ClassUserDataObjectRegistry)不同,此处 立即注册 GCHandle / userdata,仅暴露「本次调用栈上的参数位置」。

校验(沿用现有设计): 任意使用前 ValidateHandle(handleId)——解码或查表确认 generation 仍有效、登记未撤销(../../marshal/struct.md §3.4)。过期 → luaL_error(如 zlua: opaque handle expired)。

4.3 生命周期与禁止持久化

规则说明
产生C#→Lua marshal 路径; Lua API 伪造 handle
有效域与产生它的 C# 函数形参/局部作用域 及外层 StructStackScope 同步;EndScope 递增 generation 后集体失效
禁止保存不得 写入 upvalue、全局、表字段后在 异步后续 pcall 中使用
失效后再调用 zlua.to_user_data 或作为实参 Pop → 报错(校验失败)
local h = CS.Demo.GetPointHandle() -- lightuserdata,同步链内有效
-- 若 pcall 返回后仍持有 h 并在下次调用中使用 → error

长生命周期、可字段访问的实例须转为 StructUserDataClassUserData(§4.4)。

4.4 唯一脚本操作:zlua.to_user_data

OpaqueValue 不支持 任何成员访问;唯一 支持的 Lua 操作是升级为正式 userdata:

local opaque = ... -- lightuserdata from C#→Lua
local ud = zlua.to_user_data(opaque)
解析类型产出语义
struct 槽StructUserData拷贝 栈上 struct 到 userdata payload(与旧 ToUserData() 相同)
Il2CppObject**ClassUserData*slot,按 ObjectRegistry 规则 PushConstructorInstance
  • 拷贝/注册后,opaque 与 userdata 相互独立(struct 为拷贝语义)。
  • EndScope 前,原 opaque 仍可继续使用(若仍需同步链内零拷贝)。
  • Native:OpaqueValue::ToUserData / __zlua_to_user_data;详见 ../../lib-spec.md

4.5 与 Lua→C# Pop 的关系

方向OpaqueValue 行为
C#→Lua默认 struct StructHandle → Push OpaqueValue
Lua→C#若在 同一同步链 内将收到的 lightuserdata 原样传回 对应形参,Pop 时 Validate + 按类型 memcpy 或绑定 ref 槽(见 ../../marshal/struct.md §6.1、§6.2.3)
Lua→C#期望 StructUserData / ClassUserData 的 API 不得依赖对 opaque 做 : / . 访问

4.6 实现模块(C++ / Mono)

模块职责
StructStackScope登记参数地址、generation、嵌套 scope(../../marshal/struct.md §3)
OpaqueValueValidate / Resolve / ToUserData 提供字段/方法 __index
NotBlittableStructRegistry / ObjectRegistryto_user_data 的目标路径

Mono 与 Il2Cpp Lua 可见语义一致;Mono 可用 pinned / 临时 box 代替裸栈地址,但 lightuserdata + 校验 + to_user_data 行为须相同。

4.7 设计合理性(评估摘要)

维度结论
uintptr_t 与 lightuserdata与 Lua C API 一致,32/64 位自然适配;避免把 uint64_t 编码塞进指针在 32 位上截断或与 GC 混淆
统一 struct / object 槽形参在栈上均为「某地址上的值」;引用类型用 Il2CppObject** 与 CLR ref 槽一致,减少两套临时 marshal
禁止 opaque 上直接访问避免脚本误把 即将失效的栈副本 当长生命周期对象;强制 to_user_data 显式升级
沿用 StructStackScope 校验不增加新机制;异步/跨 pcall 误用仍被 generation 拦截
StructUserData / Class userdata 分工Opaque = 零拷贝临时;userdata = 可持有、可成员访问;边界清晰

5. 其他类型(索引)

类型文档
OpaqueValue / StructHandle本节 §4;../../marshal/struct.md §3
struct userdata../../marshal/struct.md §5
class / 引用类型../../marshal/class.md
delegate / Lua function../../marshal/function.md
数组../../marshal/class.md../../type-system-spec.md §7
指针 / 函数指针 / TypedReference本节 §7
[LuaMarshalAs] / LuaMarshalType本节 §6(含 §6.2 合法集合、§6.3 非法回退)

6. [LuaMarshalAs]LuaMarshalType

LuaMarshalAsAttribute 定义于 ZLua.Common,可标注于 参数返回值方法(方法级标注作用于该方法全部参数/返回值,除非被更细粒度标注覆盖)。

public enum LuaMarshalFlags
{
None = 0,
OptionalField = 1, // struct table 组装时缺键不报错;见 STRUCT_MARSHAL_SPEC.md §6.4
}

public enum LuaMarshalType
{
Default,
UserData,
Bytes,
OpaqueLightUserData,
}

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Method | AttributeTargets.Field)]
public sealed class LuaMarshalAsAttribute : Attribute
{
public LuaMarshalType LuaMarshalType { get; }
public LuaMarshalFlags Flags { get; set; }
public LuaMarshalAsAttribute(LuaMarshalType luaMarshalType = LuaMarshalType.Default);
}

6.1 LuaMarshalType 枚举说明

适用方向说明
Default双向使用 §1 默认 Marshal 规则,不做额外转换。
UserData双向强制 编组为 full userdata(带类型元表的 UserData 形态)。会 改变 下列类型的默认行为:
基元类型boolchar、整型、float/doubleIntPtr/UIntPtr 等):默认的 boolean / integer / number → userdata
string:默认的 Lua stringuserdata
enum:默认的 integer/number → enum userdata(与显式 _ctor 构造形态一致)
Pop 时须传入与形参类型匹配的 userdata;不接受对应的裸 Lua 值(除非实现层另有兼容,以 Codegen / 反射路径为准)。
Bytes双向强制 在 C# byte[] 与 Lua string 之间转换。会 改变 下列类型的默认行为:
• C# byte[]:默认的 ArrayUserData → Lua string(字节序列,非 UTF-8 文本语义时按原始 octet 处理)
• 标注于 byte[] 形参/返回值 时,Lua 侧须传 string;Pop 时 不接受 ArrayUserData
• 若标注于 string 形参/返回值,则走 byte[] ↔ string 的对偶规则(按声明类型解析)
OpaqueLightUserData仅 C# → LuaC# 调用 Lua[LuaInvoke]、delegate bridge 的 C#→Lua push 等)路径生效。Push 到 Lua 的值为 OpaqueValuelightuserdata,载荷为 StructStackScope 登记的 handleId),见 §4
metatable,脚本 不能 直接访问字段/方法
• 须在同步调用链内使用,或通过 zlua.to_user_data 升级为 StructUserData / ClassUserData
Lua → C# 方向 无效(标注于 Lua→C# 形参时不生效或报错,以实现为准)

6.2 各类型的合法 LuaMarshalType 集合

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

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

C# 类型(分类)合法 LuaMarshalTypeDefault 除外)说明
基元boolcharbyteulongfloatdoubleUserData强制 full userdata,替代默认 boolean / integer / number
IntPtr / UIntPtr / nint / nuintUserData强制 userdata,替代默认 integer / number
stringUserDataBytesBytesstring ↔ Lua string原始 octet 语义(与 byte[]+Bytes 对偶);UserData:强制 userdata
byte[]BytesUserDataBytes:↔ Lua stringUserData:保持 ArrayUserData(显式标注时与默认相同)
T[] / mdarrayUserData默认已是 ArrayUserData;显式 UserData 等价强调 full userdata
enumUserData强制 enum userdata,替代默认 integer / number
struct(普通值类型 struct)UserDataOpaqueLightUserDataUserData:StructUserData;OpaqueLightUserData仅 C#→Lua(§4)。另见 ../../marshal/struct.md §7 的 StructUserDataComposeFromStackLuaStackFields扩展枚举(与本表 UserData / OpaqueLightUserData 正交)
class / interfaceUserData默认已是 ClassUserData
Delegate 及子类UserData默认已是 DelegateUserData 或 function bridge
objectUserData按运行时类型 Push 时强制 userdata 形态(实现层须与 §7.4 多态规则一致)
Nullable<T>T 的合法集合T 为上述任一行时继承该行;T 非法则 Nullable 亦非法
非托管指针T*void* 等,§7.1)(无)Default(Pointer lightuserdata 透传)
函数指针delegate*<…>,§7.2)(无)Default
TypedReference(无)类型本身不支持互操作(§7.3);标注无效
decimal(无)v1 默认不支持(§7.4)
ref structSpan<T> 等)(无)不能作为普通 by-val 形参默认 marshal(§7.4)

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

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

LuaMarshalFlags(如 OptionalField 仅作用于 struct 字段 的 table 组装,不改变上表对 LuaMarshalType 的合法性;见 ../../marshal/struct.md §6.4、§7.3。

6.3 非法标注:回退默认规则与 Editor 日志

当解析到的 [LuaMarshalAs(LuaMarshalType.X)]X ≠ Default不满足 §6.2(类型不在合法集合、或 OpaqueLightUserData 用于不允许的方向)时:

行为说明
编组Default 处理——与未标注 [LuaMarshalAs] 相同,走 §1 默认规则 因非法标注中断调用
日志仅 Editor(Unity Editor / UNITY_EDITOR、Mono 反射 Editor 路径)输出 错误级 日志,至少包含:成员签名(类型/方法/参数或返回值/字段)、CLR 类型非法 LuaMarshalType回退为 Default 的说明
Player / Il2Cpp 发布包 打印上述日志(避免运行时开销与噪音);仍静默回退 Default

示例(概念):

[ZLua] Invalid LuaMarshalAs: ZLua.Tests.Fixtures.MarshalDefaultProbe.EchoInt(int value)
parameter 'value' (System.Int32): LuaMarshalType.Bytes is not allowed; falling back to Default.
[ZLua] Invalid LuaMarshalAs: MyGame.Point2D (struct field push)
LuaMarshalType.OpaqueLightUserData is CSharpToLua-only; falling back to Default for LuaToCSharp.

实现要点:

  • 校验时机:首次 绑定方法/字段/属性 marshal 策略时(Codegen 构建期或 Mono 首次反射注册时)各记录 一次;避免每次调用重复刷屏。
  • Il2Cpp 与 Mono 回退语义一致;差异仅在 Editor 是否打日志。
  • 非法标注 抛异常到 Lua/C# 调用栈(与「配置错误但运行可继续」一致);若需 CI 捕获,可另增 Editor 专用 校验工具或构建期 Weaver 报错(超出本节运行时范围)。

6.4 解析优先级(概念)

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

  1. 参数 / 返回值 上的 [LuaMarshalAs](若 ≠ Default
  2. 方法 上的 [LuaMarshalAs](若 ≠ Default
  3. 类型 上的 [LuaMarshalAs] / XML 双向配置(见 ../../marshal/struct.md §7)
  4. §1 内置默认

任意参数、返回值或方法级标注 ≠ Default 时,Il2Cpp Codegen 对该方法生成 专用 push/pcall/pop 代码, 走泛型默认模板(见 ../../architecture/il2cpp-architecture.md §5.2)。

解析过程中若标注 非法(§6.2),该条标注 视为未设置,继续向下一优先级查找;若全部为非法或未标注,则使用 §1 默认

6.5 与 §1 / §4 的关系

场景默认(§1)[LuaMarshalAs] 覆盖
C# 调 Lua,struct 形参视上下文可能为 OpaqueValue 或 userdataOpaqueLightUserData → 强制 OpaqueValue(§4)
C# 调 Lua,int 实参integerUserData → full userdata
Lua 调 C#,string 形参stringUserData → userdata;Bytes 不适用
Lua 调 C#,byte[] 形参ArrayUserDataBytes → Lua string
双向 enuminteger/numberUserData → enum userdata

struct 专用的 StructUserDataComposeFromStackLuaStackFields 等扩展枚举见 ../../marshal/struct.md §7;与上述 UserData / OpaqueLightUserData 正交,可组合使用。


7. 指针、函数指针与不支持的 CLR 类型

本节补充 §1 总览表中 未单独展开明确不支持 的 CLR 形态。与 IntPtr / UIntPtr(整型数值编组)严格区分。

7.1 非托管指针(T*void* 等)

范围: CLR 中 Type.IsPointer == true 且元素为 非托管 类型的指针,例如 int*byte*void*MyStruct*MyStruct 为 unmanaged struct)。不包含 IntPtr / UIntPtr / nint / nuint(它们走 §1 整型数值规则)。

方向默认形态说明
C# → LuaPointerlightuserdataPush 指针 地址值uintptr_t / 平台指针宽度); metatable
Lua → C#PointerlightuserdataPop 时须为 同一 Pointer 令牌 或绑定层认可的 lightuserdata;按声明的指针类型还原

Lua 侧能力(刻意受限):

允许禁止
作为实参 原样传递 给下一个 C# 调用(同步链内透传)解引用、读写指向内存
nil 区分(非 null 指针才有 Pointer): / . 成员访问、算术、#pairs
写入全局 / 表 / upvalue 后在 异步跨 pcall 使用(地址可能失效)

设计理由: Lua 无法安全表达 C# 非托管指针的生命周期与别名;仅支持 不透明令牌式透传,供 native / 底层 API 衔接,不提供脚本级指针运算。

IntPtr 对比:

类型Lua 默认形态脚本可当作整数运算
IntPtr / UIntPtrinteger / number可以(按数值)
int* / void*lightuserdata(Pointer)不可以(仅透传)

7.2 函数指针(function pointer)

范围: CLR 中 Type.IsFunctionPointer == true 的类型,例如 C# 9+ 的 delegate*<int, int>delegate*<void>,以及 native 函数指针 typedef 映射到 CLR 的 function pointer 类型。

方向默认形态说明
C# → LuaPointerlightuserdataPush 函数入口 地址 metatable
Lua → C#PointerlightuserdataPop 还原为对应 function pointer 类型

Lua 侧能力: 与 §7.1 相同——仅透传,不能从 Lua 侧 调用 该地址(无 : 调用、不能当 Delegate / Lua function 使用)。

Delegate 对比:

类型Lua 默认形态Lua 侧可调用
Action / Func<...>DelegateDelegateUserData 或 Lua function可以(bridge)
delegate*<...> 函数指针lightuserdata(Pointer)不可以(仅透传)

7.3 System.TypedReference

方向规则
C# → Lua不支持
Lua → C#不支持

原因: TypedReference 仅在受控反射 / 可变参数栈场景有意义,无法映射为稳定的 Lua 值,且无安全脚本用法。Codegen / 反射绑定遇到该形参或返回值时 报错NotSupportedException / luaL_error), 尝试按 pointer 或 object 降级。

7.4 其他 §1 未单独展开的类型

下列类型在 §1 总览中已 简要列出引用他册;此处集中说明默认 marshal 与文档缺口,避免与 §7.1–§7.3 重复。

C# 类型默认 marshal(摘要)文档 / 实现状态
objectPush 按 运行时实际类型;Pop 接受 boolean / number / string / userdata多态 boxing;ReadBoxedObjectValue 路径;无单独分册
Nullable<T>有值时同 TnullnilT 为非 Nullable 值类型时 Pop 接受 nil
interfaceclass 相同(ClassUserData)../../marshal/class.md
decimal默认不支持Pop/Push 未纳入 v1 默认路径;需 [LuaMarshalAs] 或专用规则后再定
ref structSpan<T>ReadOnlySpan<T> 等)不能 作为普通 by-val 形参默认 marshalref / zlua.new_ref / OpaqueValue 等受控路径;见 ../../marshal/struct.md../../lib-spec.md §6
params T[]视具体签名为 数组展开;delegate bridge 可不支持../../marshal/function.md §9
开放泛型形参(如 void M<T>(T x)T 未实例化)调用时类型实参 决定../../type-system-spec.md 泛型解析
dynamic编译期按 object 处理无独立 Lua 形态
string 构建器 / 自定义 class`class../../marshal/class.md

注册 / 暴露阶段即应拒绝的类型(非 marshal 规则,而是签名非法):

  • TypedReference 的参数或返回值(§7.3)
  • [LuaInvoke] / delegate bridge 上的 ref / out / in../../marshal/function.md §9;普通 C# 方法的 ref 见 §3)
  • 无法解析的 byref 修饰符ref struct by-val 形参组合

7.5 Mono / Il2Cpp 一致性

要求
Pointer / function pointer Pushlightuserdata,地址宽度 = 平台指针
Pointer Pop仅接受 Pointer 形态; 与 integer / full userdata 隐式互转
TypedReference双向均报错,语义一致
错误消息一致或等价(如 unsupported arg type TypedReference