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:

  1. Dynamic loading: Calls os_dlopen() to load the shared object.
  2. Symbol resolution: Invokes load_module_exports() (lines 38-57) to locate required symbols including obs_module_load, obs_module_set_pointer, and obs_module_ver.
  3. Version checking: Validates the module's compiled LIBOBS_API_VER against 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 loadingModule pointer to attribute subsequent registrations to this module.
  • Invokes the exported obs_module_load() function.
  • Upon successful return (true), the module remains loaded; returning false aborts 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 the obs_module_t handle for localization helpers.
  • MODULE_EXPORT resolves to extern "C" EXPORT, ensuring C-compatible symbol names for dlsym lookup.

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 via obs_module_ver().
  • Initialization: obs_init_module() executes obs_module_load(), during which modules call registration functions that automatically populate obs_module_t tracking arrays.
  • Registration: Functions like obs_register_source_s() associate types with the loading module using the global loadingModule pointer.
  • Lifecycle management: obs_module_t structures 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →