LuaMarshalAs
When default Marshal (see cheatsheet) is not enough, override with [LuaMarshalAs] or XML. Authoritative text: 02-MARSHAL-AS. For 0GC topics, see 0GC Marshal.
When you need it
byte[]↔ Lua string (Bytes)- Assemble a struct from multiple stack slots or a single table (
UnpackedValues/Table) - Force Opaque for C#→Lua (
ref/out/inalready default to this) - Huge
stringyou do not want copied into a Lua string (UserData→ ByObj) - Precompiled DLL you cannot edit → attach XML in Settings
You may annotate: parameters / return values / fields / properties / types (class, struct). Not on methods; not on slots that still contain unbound generic type parameters.
Common LuaMarshalType values
| Value | Applies to (summary) | Purpose |
|---|---|---|
Default | All | No override |
Bytes | byte[] / string | Octet ↔ Lua string |
OpaqueValue | C#→Lua only | Push Opaque; byref already defaults to this |
UnpackedValues | struct (not Nullable / class) | Consecutive stack slots ↔ Members |
Table | struct / Nullable<struct> (not class) | Single table ↔ Members |
UserData | Effectively almost only string | Force ByObjUserData |
Table / UnpackedValues must configure Members; a name ending in ? means a missing Table key does not assign.
Examples
Bytes
public void Send([LuaMarshalAs(LuaMarshalType.Bytes)] byte[] payload) { }
host:Send("\0\1\2\3") -- Lua string,原始字节语义
UnpackedValues (struct, multi-slot)
The parameter occupies N Lua stack slots (N = Members length); common on hot paths:
using ZLua;
public struct Vec2 { public float X, Y; }
public class Mover
{
public void Move(
[LuaMarshalAs(LuaMarshalType.UnpackedValues, Members = new[] { "X", "Y" })]
Vec2 delta) { /* ... */ }
[return: LuaMarshalAs(LuaMarshalType.UnpackedValues, Members = new[] { "X", "Y" })]
public Vec2 Origin() => new Vec2 { X = 0, Y = 0 };
}
local m = CSharp.AC.Mover()
m:Move(3.0, 4.0) -- 两槽 → X, Y
local x, y = m:Origin() -- C#→Lua 展开多返回值
print(x, y)
Type-level annotation (all default Marshal slots for that type):
[LuaMarshalAs(LuaMarshalType.UnpackedValues, Members = new[] { "x", "y", "z" })]
public struct Vector3 { public float x, y, z; }
Table (struct / Nullable<struct>)
Occupies 1 stack slot; more readable, but allocates a table on the Lua side:
public struct Packet
{
public int Id;
public float X, Y;
public string Tag; // 可选键见 Members "?"
}
public void Submit(
[LuaMarshalAs(LuaMarshalType.Table, Members = new[] { "Id", "X", "Y", "Tag?" })]
Packet p) { }
public void TryPlace(
[LuaMarshalAs(LuaMarshalType.Table, Members = new[] { "X", "Y" })]
Vector2? pos) { }
host:Submit({ Id = 1, X = 2, Y = 3 }) -- Tag 可省略
host:TryPlace({ X = 1, Y = 2 })
host:TryPlace(nil) -- Nullable 无值
XML equivalent (precompiled assemblies):
<Type fullName="UnityEngine.Vector3">
<MarshalAs type="Table" members="x,y,z" />
</Type>
<Type fullName="UnityEngine.Transform">
<Method name="LookAt" signature="(UnityEngine.Vector3)">
<Param index="0">
<MarshalAs type="UnpackedValues" members="x,y,z" />
</Param>
</Method>
</Type>
OpaqueValue
// by-val 强制 Opaque(C#→Lua);ref/out/in 无需再标
public void PushPos([LuaMarshalAs(LuaMarshalType.OpaqueValue)] Vector3 p) { }
On the Lua side use zlua.get_opaquevalue / set_opaquevalue; do not keep across frames. See 0GC Marshal, ref/out/in.
UserData (huge string)
Default: string ↔ Lua string (copies). With UserData, use ByObjUserData (managed System.String) to avoid building a huge Lua string:
public void HandleHuge(
[LuaMarshalAs(LuaMarshalType.UserData)] string payload) { }
Still allocates a Lua userdata for GC; uncommon — see 0GC Marshal.
params pitfall
By default you cannot collect multiple slots implicitly as Sum(1,2,3); pass a single table / array userdata / nil.
Precedence (mnemonic)
Slot Attribute → XML → type-level Attribute → built-in default; Attribute beats XML.
Illegal type/direction → fall back to Default + Editor log; missing Members, etc. → Bind / Generate fails.
Simple XML rules
Settings MarshalAs Xml Paths; Mono parses at runtime; Il2Cpp writes tables at Generate; Player does not read XML.
Key points: Param uses index (0-based, excluding this); type uses OpaqueValue (not deprecated names); aliases use the separate luaAliasXmlPaths. Full schema: Spec §9.
Learning path
| Previous | ref / in / out |
| Next | 0GC Marshal |