跳到主要内容

OpaqueValue(临时不透明参数)

规范性: C# 调用 Lua 时,将形参/局部在 C# 调用栈上的存储地址暴露给脚本的临时令牌;以及 [LuaMarshalAs(OpaqueValue)] 强制路径。
byref 默认(C#→Lua): ref/in/out 形参 默认 即为 OpaqueValue,见 03-BYREF.md §2
API: zlua.get_opaquevalue / zlua.set_opaquevalue(native:__zlua_get_opaquevalue / __zlua_set_opaquevalue),签名见 ../05-LIB.md

1. 定义

OpaqueValue 是 C# 调用 Lua 时,将某个 形参/局部在 C# 调用栈上的存储地址 暴露给脚本的临时令牌。脚本可在 本次调用有效期内 读取、写回;在 目标形参类型允许 时亦可将 handle 原样 作为 Lua→C# 实参传回(§6);不可 持久化后跨调用使用。

参数/返回值标注 [LuaMarshalAs(LuaMarshalType.OpaqueValue)]强制 Push OpaqueValue(02-MARSHAL-AS.md);ref / out / in 形参在 C#→Lua 路径上 默认 即为 OpaqueValue(§3)。

2. Lua 可见形态

规则
Lua 类型lightuserdata
载荷handle(编码 generation + index);与 lua_pushlightuserdata / lua_touserdata 一致
平台32/64 位用对应宽度整数装入 void*不得 假定固定 64 位
metatable不可 : / . 访问成员

脚本侧将其视为 不可自解引用的临时 handle;读写须经 zlua.get_opaquevalue / zlua.set_opaquevalue(§5)。

与长生命周期 ByObjUserData / StructUserData 不同:OpaqueValue 注册对象表、不拷贝到独立 userdata,仅暴露「当前调用栈上的参数位置」。

3. 产生方向与可表达类型

规则说明
仅 C# → Lua 创建只由 native 在 C# 调 Lua 的 marshal 路径 Push; Lua API 伪造合法 handle
可产生 OpaqueValue 的形参ref / in / out T任意 T)——默认即为 OpaqueValue(§3.1);② 任意 by-val CLR 类型——标注 [LuaMarshalAs(OpaqueValue)] 即可 Push Opaque(含基元 / enum;对后者通常无实质必要)
方向限制OpaqueValue 标注仅用于 C#→Lua;标在纯 Lua→C# 形参上非法(见 02-MARSHAL-AS.md §3.1
槽义valueAddress 指向该实参在 当前 C# 栈帧 上的存储位置(by-val 为值槽;ref/in/out指针槽

3.1 ref / out / in 默认即为 OpaqueValue

方向规则
C# → Lua形参为 ref / out / in T 时,默认 Push OpaqueValue(登记 byref 类型 + 指针槽地址);无需 再标 OpaqueValue
非 byref默认走 01-OVERVIEW.md;任意类型标注 OpaqueValue 时 Push Opaque(基元 / enum 合法但通常无实质必要)

因此 Lua 回调收到的 ref int x 不是 integer,而是 lightuserdata handle;须用 §5 API 读整数 / 写回。若再传给其它 C# 方法:对 struct / 托管引用类型 形参可将 handle 原样 传回(§6);对 int 等简单类型 形参 必须get_opaquevalue(§6)。

4. 生命周期与禁止持久化

规则说明
有效域 产生它的那次 C# 调用 Lua 尚未返回期间(与 OpaqueParameterScope / generation 同步)
回调内可用在同一回调内:get / set;以及按 §6 规则作为 Lua→C# 实参传回
禁止保存不得 写入全局、upvalue、表字段后在 异步后续 pcallC# 已返回 后再使用
失效后get / set / 作为实参 Pop(自动解路径) → 报错invalid opaque parameter handle
-- C# 调 Lua 回调期间:
function OnTick(h) -- h = OpaqueValue (e.g. ref int)
local v = zlua.get_opaquevalue(h) -- 合法
zlua.set_opaquevalue(h, v + 1) -- 合法
CS.Demo.UseInt(v) -- 合法:简单类型须先解值再传
-- CS.Demo.UseInt(h) -- 不合法:int 形参不自动识别 OpaqueValue
end

function OnPoint(h) -- h = OpaqueValue (e.g. Point2D / ref Point2D)
CS.Demo.UsePoint(h) -- 合法:struct 形参自动解 OpaqueValue(§6)
local p = zlua.get_opaquevalue(h) -- 亦可先解再传 / 访问成员
print(p:GetSum())
end
-- C# 返回后仍持有 h → 下次使用报错

长生命周期: 须用 zlua.to_user_data(opaque)拷贝 到 StructUserData / ClassUserData)或 C#→Lua 默认 Push 的 StructUserData 路径,见 05-STRUCT.md

5. 读写 API:get_opaquevalue / set_opaquevalue

OpaqueValue 成员访问;脚本侧读写指向内存须通过下列 API。

5.1 zlua.get_opaquevalue(opaque_handle) → value

将 handle 指向的参数 按默认 C#→Lua 规则压栈并返回(Lua 封装通常返回单值):

handle 指向类型行为
ref/in/out对槽上值走 01-OVERVIEW.md 默认 marshal Push(如 int → integer,string → string)
ref / in / out T先解引用 指针槽,再对 T(去 byref)做默认 Push。例:ref intinteger不是 指针 / lightuserdata

5.2 zlua.set_opaquevalue(opaque_handle, new_value)

new_value 更新 handle 指向的参数:

handle 指向类型行为
ref/in/out01-OVERVIEW.md 默认 Lua→C# marshal 写入槽
ref / in / out T先解引用,再按 T 的默认 Lua→C# 规则写入目标内存。例:ref int ← integer,更新指针所指单元
function OnRefInt(h)
local x = zlua.get_opaquevalue(h) -- integer
zlua.set_opaquevalue(h, x + 10)
end

过期 / 非 lightuserdata / 损坏 handle → luaL_error

6. 作为 Lua→C# 实参传回(按目标类型分流)

在 handle 仍有效 的同步链内,脚本可将 OpaqueValue 用于 Lua→C# 形参。为降低热路径开销,并非 所有目标类型都会在 Pop 时探测 lightuserdata 是否为 OpaqueValue:

目标 C# 形参类型(去 byref 后的元素类型)Lua→C# 是否自动解 OpaqueValue脚本侧做法
托管引用类型:普通 classstringdelegate、数组、boxed struct(object / ByObj 路径上的装箱值类型)等可将 handle 原样 传入;校验 + 类型兼容后从登记的 valueAddress 拷贝 / 绑定 到目标槽
struct(普通值类型 struct,非 enum)同上;典型零拷贝/同槽回传场景
简单类型boolchar、整型、float/doubleIntPtr/UIntPtrenumPop 检查 OpaqueValue。须先 zlua.get_opaquevalue(h) 得到默认 marshal 后的 Lua 值(如 integer),再传入
-- struct / class:自动解
function OnOpaquePoint(h)
CS.Demo.AcceptPoint(h) -- OK
end

-- int / enum 等:不自动解
function OnOpaqueInt(h)
CS.Demo.AcceptInt(zlua.get_opaquevalue(h)) -- OK
-- CS.Demo.AcceptInt(h) -- 失败
end
细则说明
性能动机基元 Pop 是极热路径;若每次先测 OpaqueValue,会拖慢全部 int/float 等编组
byrefref/in/out A)一律03-BYREF.md识别 OpaqueValue,类型兼容则 直传地址(含 ref int); 套用上表「简单类型不自动解」规则
by-val 简单类型上表「否」:须先 get_opaquevalue 再传入
类型兼容Opaque→byref / 自动解时须与目标类型兼容,否则报错(禁止 ref object Opaque → ref int
过期 handle仅在 会走自动解 的路径上才执行 OpaqueValue 校验
成员访问期望 ByObjUserData / StructUserData 的 API 不得 对 opaque 做 : / .;须先 get_opaquevalue,或传给接受该类型的 C# 形参让绑定层自动解

7. 与 struct Handle 路径的关系

struct 的 默认 C#→Lua by-val 路径在同步调用链内也可能产出 OpaqueValue(lightuserdata handle),与 §2 形态一致。脚本 不可 对 opaque 做字段/方法访问;须:

  • zlua.get_opaquevalue / zlua.set_opaquevalue 读写;或
  • zlua.to_user_data(opaque) 拷贝 为 StructUserData 后再 : / .;或
  • 在 §6 允许的类型上 原样传回 C# 形参。

详细 struct 形态见 05-STRUCT.md

8. 设计要点摘要

维度结论
仅 C#→Lua 创建地址来自 C# 调用栈;Lua 无法伪造合法槽
谁可 Opaqueref/in/out(任意 T) 默认;任意 by-val 类型均可标注 OpaqueValue(基元 / enum 合法但通常无实质必要)
无 metatable避免误当 userdata 成员访问;强制经 get/set
禁止跨调用保存generation 拦截过期 use-after-return
get 解引用 byrefref int 对脚本呈现为 int,符合默认 marshal 心智
Lua→C# 自动解分流 struct / 托管引用类型 Pop 识别 OpaqueValue;简单类型须 get_opaquevalue 后再传

9. 相关文档

主题文档
Lua→C# byref03-BYREF.md(Opaque / ByValUserData 直传地址;其余临时槽)
[LuaMarshalAs(OpaqueValue)]02-MARSHAL-AS.md
struct ByVal / StructUserData05-STRUCT.md
zlua.* 签名../05-LIB.md
实现细节../../impl/marshal/