6.1 KiB
Classes, structs and members
Declaring classes
native class IPhysicsBody as NativeEngine.IPhysicsBody
{
void SetMass( float f );
float GetMass();
}
Every class declaration starts with native or managed - which side implements it:
native class- implemented in C++, imported into managed. The generator emits a C# wrapper struct holding the native pointer, with a method per function.managed class- implemented in C#, exported to native. The generator emits a C++ class (in the native header) whose methods call into managed, and[UnmanagedCallersOnly]thunks on the managed side. Instances are addressed by an object id resolved viaSandbox.InteropSystem.
The forms:
| Form | Meaning |
|---|---|
native class X |
Instance class - wrapper carries a self pointer. |
native static class X |
No instances; members are called as X::Member(). A static class whose native name starts with global binds free functions at global scope (::Member()). |
native accessor g_pThing |
Like a static class, but members are reached through a global pointer: g_pThing->Member(). The generated thunk asserts the pointer isn't null. |
managed class X / managed static class X |
Same split, implemented in C#. |
Names and aliasing
as gives the type a different name on the other side:
native class CVideoPlayer as NativeEngine.CVideoPlayer
managed static class Sandbox.Engine.Bootstrap
The first name is the implementing side's name, the as name is the other side's. Namespaces use
. (or :: for native) and are split off automatically. Without as, both sides use the same
name.
Base classes
native class CSceneObject as Sandbox.SceneObject
native class CSceneModel as Sandbox.SceneModel : Sandbox.SceneObject
: Base references another class in the same def. The derived wrapper inherits all of the base's
non-static functions and gets implicit/explicit conversion operators to and from every base. The
conversions go through native dynamic_cast, because with multiple inheritance base pointers can
differ from derived pointers.
Functions
bool Play( string pUrl, string pExt );
static CVideoPlayer Create( Sandbox.VideoPlayer managedObject ); [new]
void Destroy(); [delete]
int GetMemRequired( int width, int height ) const;
staticmembers don't take aself/instance argument.- A trailing
constis accepted (and required to match a const native method on managed classes). - Overloads are fine - mangled export names are de-duplicated automatically.
- A function named
GetTypeis emitted asGetType_Nativein C# (to avoidobject.GetType).
Specials after the semicolon:
| Special | Meaning |
|---|---|
[new] |
The thunk runs return new NativeType( args ); instead of calling a method. |
[delete] |
The thunk runs delete (NativeType*)self;. On the managed side the wrapper also nulls its own pointer afterwards. |
Inline functions
A function starting with inline carries its native body in the def file, instead of binding to an
existing native method:
inline void SetBoneName( string boneName )
{
self->m_boneName = boneName;
self->m_pBoneName = self->m_boneName.String();
}
The body is emitted into the generated .cpp. Parameters arrive already converted to their native
types under their declared names (self is the typed instance pointer; this-> is rewritten to
self->). return expressions are marshalled like a normal return value.
Variables
A field declaration generates a get/set thunk pair and a C# property:
native class SheetSequence_t
{
uint m_nId;
bool m_bClamp;
float m_flTotalTime;
}
Structs, enums and pointer handles
Value types that cross the boundary are declared at file level:
native struct MeshTraceInput
native enum ButtonCode_t is NativeEngine.ButtonCode;
native pointer SwapChainHandle_t;
struct- both sides must define the type with an identical layout. Sizes are verified at startup (structSizesexchange); a mismatch is a fatal error. Structs 8 bytes or larger are passed by pointer when used as parameters; smaller structs must be marked[small]and are passed by value.enum- passed as a 64-bit integer and cast on both sides.pointer- a native type that pretends not to be a pointer (DECLARE_POINTER_HANDLE). Managed treats it as an opaqueIntPtr-sized struct.
as / is (synonyms) alias the managed name, same as for classes.
Attributes
Attributes are written on the line(s) before a declaration and apply to the next class, struct or function parsed.
On functions
| Attribute | Meaning |
|---|---|
[nogc] |
Emits [SuppressGCTransition] - the call skips the GC transition, so it must be short, non-blocking and never call back into managed. On a class, applies to all of its functions. |
[callback] |
Marks a function that can re-enter managed. Cancels [nogc] inherited from the class. |
On classes
| Attribute | Meaning |
|---|---|
[Handle:Managed.Type] |
Instances cross the boundary as handle ids instead of raw pointers. Managed resolves them with Sandbox.HandleIndex.Get<T>, native with GetManagedHandle. |
[ResourceHandle:HModel] |
The class is accessed through a strong resource handle (HModelStrong). Thunks dereference the handle (returning default if unloaded), and the class automatically gains handle-management functions: DestroyStrongHandle, IsStrongHandleValid, IsError, IsStrongHandleLoaded, CopyStrongHandle, GetBindingPtr. |
[SharedDataPointer] |
For Qt-style classes that are just a smart pointer to shared data. The wrapper is a public class (not a struct) that copies by value across the boundary and queues a Dispose from its finalizer. |
[WindowsOnly] |
When generating on a non-Windows platform, the native thunks are emitted as stubs returning default values. |
On structs
| Attribute | Meaning |
|---|---|
[small] |
The struct is smaller than a pointer (8 bytes) and is passed by value. Required - the generated code static_asserts that unmarked structs are at least 8 bytes. |