# Module Loading Lifecycle and obs_module_t Management in OBS Studio

> Understand the OBS Studio module loading lifecycle discovery validation and initialization. Learn how obs_module_t is managed and modules are registered for enhanced plugin development.

- Repository: [OBS Project/obs-studio](https://github.com/obsproject/obs-studio)
- Tags: internals
- Published: 2026-03-03

---

**OBS Studio manages plugin modules through a three-phase lifecycle (discovery, validation, initialization) implemented in [`libobs/obs-module.c`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/master/libobs/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`](https://github.com/obsproject/obs-studio/blob/master/libobs/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`](https://github.com/obsproject/obs-studio/blob/master/libobs/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`](https://github.com/obsproject/obs-studio/blob/main/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:

```c
/* 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/main/obs-module.c)](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-module.c):

```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`](https://github.com/obsproject/obs-studio/blob/main/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:

```c
/* 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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/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.