跳到主要内容
文档站副本

本页为语义契约的发布副本;请在上游 ZTSTest/Docs/spec 修改后执行 npm run sync-spec。(源:marshal\04-OPAQUE.md

OpaqueValue(临时不透明参数)

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


1. 定义

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


2. JavaScript 可见形态

规则
形态opaque exotic objectinternal opaque handle(实现二选一;脚本 三表成员分派)
载荷handle(编码 generation + index
成员访问不可 . / [ 访问 CLR 成员
与 ByObj/ByVal exotic不同:不注册 ObjectRegistry 长期槽位(仅调用帧 scope)

脚本读写须经 zts.get_opaquevalue / zts.set_opaquevalue(§5)。


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

规则说明
仅 C# → JS 创建由 native 在 C# 调 JS 的 marshal 路径 Push; JS API 伪造合法 handle
可产生ref/in/out T(任意 T)默认;② by-val 且 02-MARSHAL-AS.md §3 允许 — 标注 [OpaqueValue]基元 / IntPtr 族 by-val 禁止
方向OpaqueValue 标注 仅 C#→JS
槽义指向当前 C# 栈帧上的参数存储(by-val 为值槽;byref 为指针槽)

3.1 ref / out / in 默认

方向规则
C# → JSref/in/out T 默认 Push OpaqueValue
非 byref默认 01-OVERVIEW.md;允许时标注强制 Opaque

Lua 回调中的 ref int 不是 number,而是 opaque handle;再传给 ref int C# 形参可 原样 传回(§6 byref 规则)。


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

规则说明
有效域 产生它的那次 C# 调用 JS 尚未返回(OpaqueParameterScope / generation)
回调内get/set;可按 §6 传回 C#
禁止写入全局、闭包捕获后在 异步 / 后续 JS_Call / C# 已返回 后使用
失效后get/set/Pop → throw Error('zts: invalid opaque parameter handle')
function OnTick(h) {
const v = zts.get_opaquevalue(h);
zts.set_opaquevalue(h, v + 1);
CS.Demo.UseInt(v); // 简单类型须先解值
}

function OnPoint(h) {
CS.Demo.UsePoint(h); // struct:Pop 自动解 Opaque
}
// C# 返回后仍持有 h → 下次使用 throw

长生命周期: zts.to_user_data(opaque)拷贝 到 ByVal exotic)或默认 StructUserData 路径,见 05-STRUCT.md

异常路径:GetFunction invoke 须维护 OpaqueParameterScope,确保异常时 handle 同样失效(../10-LIFETIME.md)。


5. 读写 API

5.1 zts.get_opaquevalue(opaque_handle) → value

handle 指向行为
非 byref对槽值走 01-OVERVIEW.md 默认 Push 规则 转为 JS 值
ref/in/out T先解引用,再对 T 默认 Push(例:ref intnumber

5.2 zts.set_opaquevalue(opaque_handle, new_value)

handle 指向行为
非 byref01-OVERVIEW.md 默认 Pop 写入槽
ref/in/out T解引用后对 T 默认 Pop 写入
function OnRefInt(h) {
zts.set_opaquevalue(h, zts.get_opaquevalue(h) + 10);
}

过期 / 非 opaque / 损坏 → throw Error


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

handle 仍有效 的同步链内:

目标形参(去 byref 后)是否自动解 OpaqueValue脚本做法
托管引用类型(class、string、delegate、数组、boxed 等)原样 传入 handle
struct(普通值类型)同上
简单类型(bool、整型、float、enum、IntPtr 等)zts.get_opaquevalue(h) 后再传
ref/in/out A一律 先识别 Opaque;兼容则 直传地址(含 ref int03-BYREF.md
function OnOpaquePoint(h) {
CS.Demo.AcceptPoint(h); // OK
}

function OnOpaqueInt(h) {
CS.Demo.AcceptInt(zts.get_opaquevalue(h)); // OK
// CS.Demo.AcceptInt(h); // 失败
}

性能动机: 基元 Pop 为热路径;不在每次 Pop 探测 Opaque。


7. 与 struct Handle 路径

struct 默认 C#→JS by-val 在同步链内亦可能为 OpaqueValue。须 get/setto_user_data 拷贝,或在 §6 允许类型上原样传回。详见 05-STRUCT.md


8. 设计要点摘要

维度结论
仅 C#→JS 创建地址来自 C# 栈;JS 无法伪造
谁可 Opaqueref/in/out 默认;by-val 见 §3
无三表分派强制经 get/set
禁止跨调用generation 拦截 use-after-return
get 解 byrefref int 对脚本为 number
Pop 自动解仅 struct / 托管引用 by-val;byref 一律 Opaque 路径

9. 相关文档

主题文档
JS→C# byref03-BYREF.md
[TsMarshalAs(OpaqueValue)]02-MARSHAL-AS.md
struct05-STRUCT.md
zts.*../05-LIB.md
帧泵 / scope../10-LIFETIME.md