Files
sbox-public/engine/Tools/InteropGen/docs/classes.md
2026-06-11 10:36:53 +00:00

152 lines
6.1 KiB
Markdown

# 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 via `Sandbox.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;
```
- `static` members don't take a `self`/instance argument.
- A trailing `const` is 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 `GetType` is emitted as `GetType_Native` in C# (to avoid `object.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 (`structSizes` exchange); 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 opaque `IntPtr`-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_assert`s that unmarked structs are at least 8 bytes. |