Skip to main content

Marshal Overview — Default Rule Matrix

Normative: Default Marshal for each CLR type in C# ↔ Lua bidirectional calls when [LuaMarshalAs] is absent (or set to LuaMarshalType.Default). Overrides: [LuaMarshalAs] on parameters, return values, fields, and properties — see 02-MARSHAL-AS.md. Implementation:../../impl/marshal/.

1. Platform principles

  • Mono (Editor) and Il2Cpp (Player) share the same Lua-visible Marshal semantics; differences are implementation-only (zero GC, generated code, etc.) and do not change script-observable behavior.
  • Functions / delegates: When Lua calls a C# method, a delegate parameter accepts a Lua function, marshaled implicitly by the bridge; see 09-FUNCTION.md.
  • On GetFunction-obtained delegate calls / delegate bridges (C# → Lua), the default Push for ref/out/in is OpaqueValue, which differs from the Lua→C# path; see 03-BYREF.md, 04-OPAQUE.md.

2. Default Marshal matrix

C# typeC# → LuaLua → C#Notes
boolbooleanboolean
charinteger / numberinteger / numberUnicode code point (16-bit)
byteinteger / numberinteger / numberSee §3
sbyteinteger / numberinteger / numberSee §3
shortinteger / numberinteger / numberSee §3
ushortinteger / numberinteger / numberSee §3
intinteger / numberinteger / numberSee §3
uintinteger / numberinteger / numberSee §3
longinteger / numberinteger / numberSee §3
ulonginteger / numberinteger / numberSee §3; must fit Lua integer range
floatnumbernumber
doublenumbernumber
IntPtrinteger / numberinteger / numberPointer numeric value (ToInt64 / new IntPtr); not the unmanaged pointer in 10-POINTER.md
UIntPtrinteger / numberinteger / numberSame
nint / nuintSame as IntPtr / UIntPtrSame as IntPtr / UIntPtrNative integer aliases
T* (unmanaged pointer)Pointer (lightuserdata)Pointer (lightuserdata)Pass-through only; see 10-POINTER.md
Function pointer (e.g. delegate*<int,int>)Pointer (lightuserdata)Pointer (lightuserdata)Pass-through only; see 10-POINTER.md
System.TypedReferenceOpaqueValueOpaqueValueOnly OpaqueValue; default is this; see 10-POINTER.md, 04-OPAQUE.md
stringstringstring
byte[]ByObjUserDataByObjUserData or tableSame as T[] (§4); with [LuaMarshalAs(Bytes)]string; see 02-MARSHAL-AS.md
classClassUserDataClassUserDataReference identity; nilnull; member façade = declared type; see 06-CLASS.md
T[] (1-D / szarray)ByObjUserDataByObjUserData or tableSee §4, 07-ARRAY.md
T[,] etc. (mdarray)ByObjUserDataByObjUserDataSee §4; does not accept Lua table
enuminteger / numberinteger / number or ByObjUserData (boxed)Default does not push userdata; boxed only via zlua.box; see 08-ENUM.md
structByValUserData or OpaqueValueStructUserData or Type(...) productC#→Lua normal path: 05-STRUCT.md; with OpaqueValue or ref/in/out → OpaqueValue (04-OPAQUE.md. Lua→C# also accepts StructUserData from SMT.__call. Does not accept table / multi-stack args by default; needs [LuaMarshalAs(Table | UnpackedValues)] + Members (02-MARSHAL-AS.md)
Delegatefunction or DelegateUserDatafunction or DelegateUserDataC#→Lua: if target is a Lua callback source, Push function, else ByObjUserData; see 09-FUNCTION.md
objectClassUserData (System.Object façade)boolean / number / string / userdataFaçade = declared type object; even if runtime is string, do not use special Marshal; see 06-CLASS.md
Nullable<T>Same as T or nilSame as T or nilWhen T is a value type, nilnull
interfaceClassUserData (ByObj)ClassUserDataSame as class: façade = interface declared type; may also use [LuaMarshalAs(Table | UnpackedValues)] (see 02-MARSHAL-AS.md, 06-CLASS.md)
decimalNot supported (default)Not supported (default)Not in v1 default path
ref struct (e.g. Span<T>)See 05-STRUCT.md, ../05-LIB.mdSameCannot be passed as ordinary by-val default
void (return)(none)
null / nilnilnilOnly reference types, Nullable, delegates, and other nullable forms

2.1 UserData shapes

ClassUserData, array ByObjUserData (szarray / mdarray instances), StructUserData, boxed enum (ByObjUserData), and DelegateUserData in the table above are all full userdata with a typed metatable (lua_newuserdata + metatable). Scripts access members via : / ..

These differ from:

ShapeTraitsDocs
OpaqueValuelightuserdata, no metatable04-OPAQUE.md
Pointer (unmanaged / function pointer)lightuserdata, no metatable, pass-through only10-POINTER.md

3. integer and number

  • Lua 5.4+: Integer primitives, char, enum underlying integers, and IntPtr / UIntPtr numeric values prefer integer (lua_pushinteger / lua_isinteger).
  • Lua versions without integer: Fall back to number, which must be an integral value (no fractional part).
  • Il2Cpp Codegen and Mono reflection paths share the same visible semantics; only implementation APIs differ.

4. Arrays (szarray / mdarray)

C# typeC# → LuaLua → C#
T[] (szarray)ByObjUserData (array instance userdata)ByObjUserData, or array-shaped Lua table (below)
T[,…] (mdarray)ByObjUserDataOnly ByObjUserData
byte[]Same as szarray (unless [LuaMarshalAs(Bytes)])Same as szarray

4.1 C# → Lua

Array instances are always Push'd as ByObjUserData (ObjectUserData + array ByObj instance metatable; payload is a managed array reference). Scripts access via GetValue / SetValue, #arr (szarray), etc.; see ../02-TYPE-SYSTEM.md, 07-ARRAY.md.

4.2 Lua → C# (szarray)

Accept one of:

Argument shapePop behavior
ByObjUserDataBound type must match target T[] (or element types compatible); pass array reference
Lua table (array shape)Keys 1n contiguous integers, no holes; Pop each element as T, construct T[n]
nilReference-type array → C# null

4.3 Lua → C# (mdarray)

Only accepts ByObjUserData; does not accept table. null remains nil ↔ null.

4.4 Table shape constraints (szarray Pop)

Same as ordered-table rules in 02-MARSHAL-AS.md:

  • Reject sparse tables, string-key tables, or 0-based pseudo-arrays (v1 is not compatible).

5. Reference-type façade (summary)

For all reference type parameters, return values, and fields/properties (class / interface / object / arrays / delegates, etc.):

ConceptMeaning
IdentityManaged object reference held by userdata (runtime actual instance)
View / façadeIMT and member visibility attached to userdata; sole source = declared type of this Marshal

Rule summary:

  1. C# → Lua: Always choose default marshal shape and ByObj IMT by declared type; do not reattach the actual runtime type's mt, and do not switch to special Marshal such as string because of runtime type.
  2. Downcast: Only zlua.cast(obj, targetType) (see ../05-LIB.md; target must be assignable from current façade type; returns new userdata (same identity, new façade).
  3. Object cache: Key is (identity, viewType); one instance may have multiple view userdata.

Full rules: 06-CLASS.md.

TopicDoc
[LuaMarshalAs] overrides02-MARSHAL-AS.md
ref / in / out03-BYREF.md
OpaqueValue04-OPAQUE.md
struct05-STRUCT.md
class / interface06-CLASS.md
Arrays / Bytes07-ARRAY.md
Enum08-ENUM.md
delegate / Lua function09-FUNCTION.md
Pointers / unsupported types10-POINTER.md
Overloads and argument matching../04-METHOD-OVERLOAD.md
zlua.* API../05-LIB.md