IN THIS ARTICLE
Gem Module System
For Gems which ship with a code component, the resulting compiled library associated with the Gem is called a module. All Gem modules, regardless of whether they’re used in a tool application such as the Editor or the runtime or dedicated servers, plug into the system in the same way. All modules have known entry point functions which are expected to be implemented and declared to be #extern C
, on a class which derives from
AZ::Module
.
When an
AzFramework::Application
loads a Gem module, it uses an
AZ::ModuleManager
to do so. When the module manager receives a request to load a module, it loads the dynamic library and calls a series of specified entry points. Since project configuration files specify the Gems used by a project, normally this load is done automatically as part of an application call to its Start(...)
method. For finer control over module loading, developers can always use a ModuleManager
instance supplied by O3DE rather than let the application
bootstrapping process take care of it. Using manual methods allows developers to set their own specialized pre- and post-conditions on module loads that might be unrelated to Gem dependencies.
How O3DE loads and initializes Gems
Module entry point functions
All modules are required to provide the following functions, declared with the extern "C" AZ_DLL_EXPORT
specifiers:
void InitializeDynamicModule(void* env)
- The function called immediately after the module is loaded, passing it a pointer to an “environment instance” that can be queried for information such as EBuses, event handlers, and the memory allocator.AZ::Module* CreateModuleClass()
- Creates an instance of the actual class representing the module, and returns it.void DestroyModuleClass(AZ::Module*)
- Destroys the provided instance of the module.void UninitializeDynamicModule()
- The function called immediately before the module is unloaded. The module should clean up any static resources and environment information which are no longer needed, but were set up inInitializeDynamicModule()
.
Important:AlthoughCreateModuleClass()
andDestroyModuleClass(AZ::Module*)
are called only once by the module loader (when creating an instance after load, and destroying the instance before unloading), this is not guaranteed behavior since end users may manually load modules. If your module requires singleton behavior, make sure to enforce it on your own to prevent developers from accidentally instantiating multiple objects.
These module entry point functions are defined by the AZ_DECLARE_MODULE_CLASS(...)
macro that’s called in the Gem’s AZ::Module
class.
The AZ_DECLARE_MODULE_CLASS(…) macro
Since many modules would use the same general boilerplate code to generate the entry point code, O3DE provides the AZ_DECLARE_MODULE_CLASS(...)
macro, which generates standard entry point function implementations. The syntax for the macro is AZ_DECLARE_MODULE_CLASS(<UUID>, <ModuleClassName>)
. By convention, the <UUID>
used for modules associated with Gems should be Gem_<GemName>
.
The AZ_DECLARE_MODULE_CLASS(...)
macro is defined in the AZ::Module
class that’s located in the /Code/Framework/AzCore/AzCore/Module
directory of the engine source.
Example module class
The following C++ class is the most minimal possible O3DE module: It derives from AZ::Module
, declares the necessary overrides, and generates the entry point functions. It provides no additional functionality. Note that this is an implementation, not a definition. This module implementation belongs to a Gem called Example
for the purposes of following good naming conventions.
class ExampleModule
: public AZ::Module
{
public:
AZ_RTTI(ExampleModule, "{CFC64EAF-7566-4D30-AAF4-A6FF19BF87DB}", AZ::Module);
ExampleModule()
: AZ::Module()
{
}
};
AZ_DECLARE_MODULE_CLASS(Gem_Example, Example)
Monolithic builds and Gems
The module system can also function in monolithic mode. Enabling monolithic builds causes dynamic modules to become static modules, and be linked directly to the application executables instead of dynamically loaded on startup. This allows developers to ship a single (large) server or client application executable. Monolithic builds can optimize startup time, as well as avoid performance issues or platform restrictions that might prevent the loading of dynamic libraries.