This page describes the overall architecture of the C# scripting integration in Godot, implemented in the modules/mono directory. It covers how the module fits into the engine, the major components involved, and how they relate to each other at a high level.
For detailed coverage of specific subsystems, see:
The mono module adds C# as a first-class scripting language in Godot. It allows .cs files to be attached to Godot nodes and resources as scripts, provides all Godot engine API types as generated C# classes, and manages the lifetime of the .NET runtime alongside the engine. The module is disabled by default and must be explicitly enabled at build time (modules/mono/config.py34-36).
The module lives entirely under modules/mono/. Key subdirectories:
| Directory | Purpose |
|---|---|
mono_gd/ | .NET runtime initialization (GDMono) and managed callback cache |
editor/ | BindingsGenerator, editor internal calls, hot-reload support |
glue/ | C++ interop stubs and the C# GodotSharp library source |
glue/GodotSharp/GodotSharp/Core/Bridge/ | C# bridge types (ScriptManagerBridge, ManagedCallbacks) |
build_scripts/ | SCons helpers for configuring the mono build |
utils/ | Name conversion, path utilities |
Build entry point: modules/mono/SCsub which delegates to modules/mono/build_scripts/mono_configure.py
The GD_MONO_HOT_RELOAD preprocessor flag is defined for editor builds only (modules/mono/build_scripts/mono_configure.py22).
Sources: modules/mono/SCsub modules/mono/config.py modules/mono/build_scripts/mono_configure.py
Major class overview — bridging natural language names to code entities:
Sources: modules/mono/csharp_script.h modules/mono/mono_gd/gd_mono.h modules/mono/mono_gd/gd_mono_cache.h modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
CSharpLanguage (modules/mono/csharp_script.h401-578) is the singleton implementing the ScriptLanguage interface. It is registered with the engine's ScriptServer and is the entry point for all C# script operations:
cs, type string: CSharpScriptSCRIPT_NAME_CASING_PASCAL_CASEmake_function() (appending function stubs at end of file breaks C# compilation) (modules/mono/csharp_script.cpp413-424)init(), creates a GDMono instance and calls initialize() if the project uses C# (modules/mono/csharp_script.cpp101-133)CSharpScript (modules/mono/csharp_script.h58-305) is the Script resource type for .cs files. Each script holds:
| Field | Type | Purpose |
|---|---|---|
type_info | TypeInfo | C# class name, native base, tool/abstract/generic flags |
valid | bool | True when the C# class was found and loaded |
base_script | Ref<CSharpScript> | Parent script (if the C# class inherits another script) |
instances | HashSet<Object*> | Live instances of this script |
event_signals | Vector<EventSignalInfo> | Signals declared as C# events |
methods | Vector<CSharpMethodInfo> | Exported methods |
The TypeInfo struct captures whether the script is a tool script, global class, abstract, or a generic type (modules/mono/csharp_script.h65-126).
CSharpInstance (modules/mono/csharp_script.h307-386) is the ScriptInstance implementation attached to a Godot Object at runtime. It holds a MonoGCHandleData gchandle — a garbage-collector handle to the corresponding C# object — and routes property get/set and method calls across the C++/C# boundary via GDMonoCache::managed_callbacks.
GDMono (modules/mono/mono_gd/gd_mono.h60-162) owns the .NET runtime handle. On initialization it:
hostfxr (or falls back to CoreCLR/Native AOT on non-editor platforms)GodotPlugins.dll (editor) or the game assembly (export)InitializeFromEngine / InitializeFromGameProject in the managed assemblyManagedCallbacks struct from C# and stores it in GDMonoCache(modules/mono/mono_gd/gd_mono.cpp636-730)
GDMonoCache::ManagedCallbacks (modules/mono/mono_gd/gd_mono_cache.h70-148) is a flat struct of C function pointers, one per operation the C++ side needs to invoke on the managed side. Examples:
ScriptManagerBridge_CreateManagedForGodotObjectBinding — instantiate a C# wrapper for a native ObjectCSharpInstanceBridge_Call — invoke a method on a CSharpInstanceCSharpInstanceBridge_SerializeState / DeserializeState — hot-reload state transferGCHandleBridge_FreeGCHandle — release a GC handleThe C# struct ManagedCallbacks (modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs) mirrors this layout exactly (marked [StructLayout(LayoutKind.Sequential)]) and is filled from static methods on ScriptManagerBridge and friends using C# function pointers (delegate* unmanaged).
BindingsGenerator (modules/mono/editor/bindings_generator.h43) is an editor-only tool that generates the GodotSharp C# class library from Godot's ClassDB. It produces C# proxy classes with TypeInterface, MethodInterface, PropertyInterface, and SignalInterface descriptors, writes XML documentation comments, and handles name casing conversions (snake_case → PascalCase). It runs as part of the glue generation step and is not involved at runtime.
From engine startup to a running C# script:
Sources: modules/mono/csharp_script.cpp101-133 modules/mono/mono_gd/gd_mono.cpp636-730 modules/mono/mono_gd/gd_mono_cache.cpp40-99
When a .cs file is assigned to a node:
CSharpScript::reload() — calls ScriptManagerBridge_AddScriptBridge (C# side) to link the script pointer to the C# Type, then ScriptManagerBridge_UpdateScriptClassInfo to pull property/method/signal metadata back into C++.CSharpScript::instance_create() — calls _create_instance(), which invokes ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance. The C# constructor runs, binding the managed object to the native Object*.CSharpInstance holds the resulting MonoGCHandleData. All subsequent property access, method calls, and notifications route through GDMonoCache::managed_callbacks.Hot reload is only available in editor builds (GD_MONO_HOT_RELOAD).
Hot reload flow:
State backup is stored in CSharpScript::StateBackup (modules/mono/csharp_script.h155-161), which holds serialized Variant properties and event signal data. Inheritance order is respected during reload via CSharpScriptDepSort (modules/mono/csharp_script.cpp540-560).
Sources: modules/mono/csharp_script.cpp562-1029 modules/mono/mono_gd/gd_mono.h151-154
The full communication surface between C++ and C# is the ManagedCallbacks struct. Both sides define it with the same memory layout:
| C++ typedef (gd_mono_cache.h) | C# field (ManagedCallbacks.cs) | Direction |
|---|---|---|
FuncScriptManagerBridge_CreateManagedForGodotObjectBinding | ScriptManagerBridge_CreateManagedForGodotObjectBinding | C++ → C# |
FuncCSharpInstanceBridge_Call | CSharpInstanceBridge_Call | C++ → C# |
FuncCSharpInstanceBridge_Set / _Get | CSharpInstanceBridge_Set / _Get | C++ → C# |
FuncCSharpInstanceBridge_SerializeState | CSharpInstanceBridge_SerializeState | C++ → C# |
FuncGCHandleBridge_FreeGCHandle | GCHandleBridge_FreeGCHandle | C++ → C# |
FuncDebuggingUtils_GetCurrentStackInfo | DebuggingUtils_GetCurrentStackInfo | C++ → C# |
| (runtime interop funcs passed at init) | NativeFuncs | C# → C++ |
All C++ → C# calls go through GDMonoCache::managed_callbacks.<FuncName>(...). C# → C++ calls use native interop stubs registered at GDMono::initialize() time via godotsharp::get_runtime_interop_funcs().
Sources: modules/mono/mono_gd/gd_mono_cache.h70-148 modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs modules/mono/mono_gd/gd_mono.cpp677-730
signal_awaiter_utils.cpp provides gd_mono_connect_signal_awaiter(), which wraps a C# await on a signal by connecting a SignalAwaiterCallable (a CallableCustom) to the signal with CONNECT_ONE_SHOT. When the signal fires, GDMonoCache::managed_callbacks.SignalAwaiter_SignalCallback resumes the awaiter on the C# side (modules/mono/signal_awaiter_utils.cpp36-46).
Defined in modules/mono/godotsharp_defs.h:
| Constant | Value |
|---|---|
BINDINGS_NAMESPACE | "Godot" |
BINDINGS_NAMESPACE_COLLECTIONS | "Godot.Collections" |
CORE_API_ASSEMBLY_NAME | "GodotSharp" |
EDITOR_API_ASSEMBLY_NAME | "GodotSharpEditor" |
TOOLS_ASM_NAME | "GodotTools" |
All generated C# wrapper classes are placed in the Godot namespace. Collections (Array, Dictionary) live in Godot.Collections.
hostfxr + collectible AssemblyLoadContext for hot reload. Requires .NET SDK 8.0+.hostfxr in self-contained mode (no SDK required at runtime).coreclr_initialize) or MonoSGen, loading assemblies from the PCK via a preload hook (modules/mono/mono_gd/gd_mono.cpp522-605).try_load_native_aot_library(), loading a platform .so/.dll that exports godotsharp_game_main_init (modules/mono/mono_gd/gd_mono.cpp489-517).Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.