Module Loading Lifecycle and obs_module_t Management in OBS Studio
OBS Studio manages plugin modules through a three-phase lifecycle (discovery, validation, initialization) implemented in libobs/obs-module.c, where each module exports standard symbols via OBS_DECLARE_MODULE() and registers types during obs_module_load().
The module loading lifecycle and obs_module_t management in OBS Studio is orchestrated by the libobs library, specifically within libobs/obs-module.c. This system handles dynamic shared objects as plugins, enforcing version compatibility and providing registration APIs for sources, outputs, and encoders.
The Three Phases of the Module Loading Lifecycle
The lifecycle defined in obs_load_all_modules() splits module initialization into distinct phases to ensure safe discovery and validation before execution.
Discovery Phase
During discovery, OBS iterates over all search paths stored in obs->module_paths (populated from the application's plugins directory). The function obs_find_modules2() builds a list of candidate shared library files. This phase is implemented in obs_load_all_modules() at obs-module.c#L559-L574.
Opening and Validation Phase
Each candidate module undergoes opening and symbol validation via obs_open_module() at obs-module.c#L40-L104. This phase performs three critical operations:
- Dynamic loading: Calls
os_dlopen()to load the shared object. - Symbol resolution: Invokes
load_module_exports()(lines 38-57) to locate required symbols includingobs_module_load,obs_module_set_pointer, andobs_module_ver. - Version checking: Validates the module's compiled
LIBOBS_API_VERagainst the runtime version to prevent ABI mismatches.
Initialization Phase
Once validated, obs_init_module() at obs-module.c#L33-L47 executes the module's entry point:
- Sets the global
loadingModulepointer to attribute subsequent registrations to this module. - Invokes the exported
obs_module_load()function. - Upon successful return (
true), the module remains loaded; returningfalseaborts initialization.
After all modules initialize, obs_post_load_modules() iterates through loaded modules and invokes obs_module_post_load() if exported, allowing cross-module dependencies to resolve.
Module Declaration and Symbol Export
Modules include libobs/obs-module.h and use helper macros to expose required symbols. The OBS_DECLARE_MODULE() macro (lines 75-92) generates the boilerplate for obs_module_t management:
/* libobs/obs-module.h – lines 75‑92 */
#define OBS_DECLARE_MODULE() \
static obs_module_t *obs_module_pointer; \
MODULE_EXPORT void obs_module_set_pointer(obs_module_t *module); \
void obs_module_set_pointer(obs_module_t *module) \
{ \
obs_module_pointer = module; \
} \
obs_module_t *obs_current_module(void) \
{ \
return obs_module_pointer; \
} \
MODULE_EXPORT uint32_t obs_module_ver(void); \
uint32_t obs_module_ver(void) \
{ \
return LIBOBS_API_VER; \
}
obs_module_set_pointer()stores theobs_module_thandle for localization helpers.MODULE_EXPORTresolves toextern "C" EXPORT, ensuring C-compatible symbol names fordlsymlookup.
Core Lifecycle Functions in libobs/obs-module.c
The management of obs_module_t structures occurs through several key functions:
obs_open_module() allocates the obs_module_t structure, loads the shared library via os_dlopen(), and populates the function pointers for load, unload, post_load, set_pointer, and ver.
obs_init_module() sets the thread-local loadingModule variable before invoking obs_module_load(). This global pointer allows registration functions to associate newly created types with the correct module.
obs_post_load_modules() iterates the linked list of loaded modules (obs->first_module) and calls obs_module_post_load() for each module that exports it.
Registering Types During obs_module_load
While obs_module_load() executes, modules register their capabilities using functions that automatically track the originating module via loadingModule:
| Registration function | Registers | Tracking mechanism |
|---|---|---|
obs_register_source_s |
struct obs_source_info |
Adds source ID to loadingModule->sources |
obs_register_output_s |
struct obs_output_info |
Adds output ID to loadingModule->outputs |
obs_register_encoder_s |
struct obs_encoder_info |
Adds encoder ID to loadingModule->encoders |
obs_register_service_s |
struct obs_service_info |
Adds service ID to loadingModule->services |
Each function validates required callbacks using CHECK_REQUIRED_VAL, copies the struct into libobs' internal arrays, and records the module association. For example, from [obs-module.c](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-module.c):
void obs_register_source_s(const struct obs_source_info *info, size_t size)
{
if (loadingModule) {
char *source_id = bstrdup(info->id);
da_push_back(loadingModule->sources, &source_id);
}
memcpy(&data, info, size);
da_push_back(obs->source_types, &data);
}
Localization and File Access Helpers
Modules using OBS_MODULE_USE_DEFAULT_LOCALE(name, default_locale) (defined in obs-module.h lines 15-38) gain automatic locale management. The macro initializes lookup tables that obs_module_text() and obs_module_get_string() use to retrieve translated strings from locale/<lang>.ini files in the module's data directory.
File path helpers provide platform-independent access to module resources:
/* Get path to a data file shipped with the module */
char *logo_path = obs_module_file("logo.png");
if (logo_path) {
// Use path, then free
bfree(logo_path);
}
/* Get path for module-specific configuration */
char *config_path = obs_module_config_path("my_module.ini");
if (config_path) {
bfree(config_path);
}
These wrap obs_find_module_file() and obs_module_get_config_path(), resolving paths relative to the module's installation directory.
Unloading and Cleanup
During OBS shutdown, free_module() invokes obs_module_unload() for each module that exports it. The function iterates through registered sources, outputs, encoders, and services to ensure proper destruction order.
Notably, the dynamic library handle is not closed via os_dlclose() to avoid potential crashes from dangling references or static destructors, as documented in the comment at line 92 of obs-module.c.
Summary
- Discovery:
obs_load_all_modules()scans plugin directories and builds candidate lists. - Validation:
obs_open_module()loads shared libraries and verifies API version compatibility viaobs_module_ver(). - Initialization:
obs_init_module()executesobs_module_load(), during which modules call registration functions that automatically populateobs_module_ttracking arrays. - Registration: Functions like
obs_register_source_s()associate types with the loading module using the globalloadingModulepointer. - Lifecycle management:
obs_module_tstructures maintain lists of registered types, localization data, and file paths, persisting until application shutdown without closing the DLL handles.
Frequently Asked Questions
What is the obs_module_t structure used for?
The obs_module_t structure represents a loaded plugin instance in memory. It stores the dynamic library handle, exported function pointers (load, unload, post_load), and dynamic arrays tracking which sources, outputs, encoders, and services the module registered. This structure enables libobs to attribute types to specific modules and manage their lifecycle during shutdown.
How does OBS Studio validate plugin compatibility?
During the opening phase in obs_open_module(), libobs resolves the obs_module_ver symbol from the shared library and compares the returned uint32_t against the runtime LIBOBS_API_VER. If the versions mismatch, the module is rejected before obs_module_load() executes, preventing ABI incompatibility crashes.
What happens if obs_module_load returns false?
If a module's obs_module_load() function returns false, obs_init_module() aborts the initialization sequence for that specific module. The module remains in the loaded list but is marked as failed, and its registered types (if any were registered before the failure) remain in libobs. However, standard practice dictates registering types only after confirming successful initialization.
Why doesn't OBS close the dynamic library handle on shutdown?
According to the implementation in free_module() (around line 92 in obs-module.c), OBS intentionally avoids calling os_dlclose() on the module's dynamic library handle. This prevents potential segmentation faults or undefined behavior that could occur if static destructors execute or if dangling references to module-allocated memory persist during the shutdown sequence.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →