Skip to content

Fix plugin failing to load on Windows (OBS 32.2+) - #125

Open
kaymyst wants to merge 5 commits into
fzwoch:masterfrom
kaymyst:master
Open

Fix plugin failing to load on Windows (OBS 32.2+)#125
kaymyst wants to merge 5 commits into
fzwoch:masterfrom
kaymyst:master

Conversation

@kaymyst

@kaymyst kaymyst commented Aug 6, 2026

Copy link
Copy Markdown

Summary

Fixes #124. OBS 32.2 (obsproject/obs-studio#11569) dropped %PATH% from the DLL search order on Windows, which broke obs-gstreamer in two ways:

  • obs-gstreamer.dll's own direct GStreamer/GLib imports could no longer be resolved at LoadLibrary time, so the module failed to load at all ("Failed to load Module").
  • GStreamer element support DLLs (loaded later via g_module_open()) could no longer be found, and the plugin registry scan fell back to a path guess that doesn't exist under the OBS plugin layout, so elements like videoconvert/rtspsrc were unavailable even once the module did load.

Fix, in two parts:

  • Delay-load the plugin's direct GStreamer/GLib imports (dlltool -y delay-import archives + mingw-w64-crt's __delayLoadHelper2), so the loader doesn't need them resolved until the first actual GStreamer call. docker/gen-delayimp-libs.sh generates the archives from the real GStreamer install at build time; meson.build links against them on Windows instead of the normal eager import libs.
  • Before making any GStreamer call, register the real GStreamer install's bin directory via AddDllDirectory() and point GST_PLUGIN_PATH at its lib\gstreamer-1.0, derived from the GSTREAMER_1_0_ROOT_MINGW_X86_64/_MSVC_X86_64 environment variables the official installer sets. If no GStreamer install is found at all, obs_module_load now logs a clear error and bails out cleanly instead of hitting the delay-load runtime's default failure path.

With this, obs-gstreamer.dll loads with nothing but the plugin file itself in bin\64bit - no PATH changes, no manually copying the GStreamer runtime next to the plugin.

Test plan

  • Cross-compiled for Windows via the docker/mingw image (bumped to GStreamer 1.18.6 - 1.18.1 packages were removed upstream) and confirmed objdump shows the 11 direct GStreamer/GLib DLLs as delay-imports, not regular imports, with only KERNEL32.dll/msvcrt.dll/obs.dll remaining as regular imports.
  • Validated the mingw delay-load mechanism itself (a minimal DLL + delay-loaded call, unrelated to GStreamer) under Wine on this exact toolchain (ld.bfd 2.35.2), since there's a known dlltool/ld.bfd delay-import bug upstream - confirmed it's 32-bit only and doesn't affect this x86_64-only build.
  • Confirmed on a real OBS 32.2 install with a system-wide GStreamer install: plugin loads successfully with zero files other than obs-gstreamer.dll in the plugin's bin\64bit, and videoconvert (and other elements) resolve correctly with no "no element" errors.

Stephane Lefevre added 4 commits August 6, 2026 11:27
…ws build script

The 1.18.1 MinGW MSI packages have been removed from
gstreamer.freedesktop.org, breaking the docker/mingw image build. 1.18.6
is the closest still-available release in that series.

Also add docker/build-windows.sh, which reproduces the .gitlab-ci.yml
windows job's meson cross-compile steps for building the plugin locally
via the mingw image.
…dows)

obsproject/obs-studio#11569 dropped %PATH% from the DLL search order on
Windows, breaking obs-gstreamer in two ways (issue fzwoch#124):

- GStreamer element support DLLs, loaded later via g_module_open(), can
  no longer be found even with a system-wide GStreamer install.
- If the GStreamer core DLLs are instead copied next to obs-gstreamer.dll
  to work around the load failure, GStreamer's own plugin-scan path guess
  (relative to libgstreamer-1.0-0.dll's location) resolves to a
  nonexistent lib\gstreamer-1.0 under the plugin directory, so the
  element registry comes up completely empty.

Register the real GStreamer install's bin directory via AddDllDirectory()
and point GST_PLUGIN_PATH at its lib\gstreamer-1.0, derived from the
GSTREAMER_1_0_ROOT_* environment variables the official installer sets.
Diagnosis and fix approach from kaymyst in the issue thread.

Fixes fzwoch#124
…zwoch#124)

The earlier AddDllDirectory()/GST_PLUGIN_PATH fix (b2fcb64) still required
manually copying the GStreamer runtime's bin\ DLLs next to
obs-gstreamer.dll, because those DLLs are obs-gstreamer.dll's own direct
imports and the loader resolves them before any of the plugin's own code
(including that fix) ever runs.

Convert the direct links against libgstreamer-1.0-0.dll and the other 10
GStreamer/GLib DLLs to delay-imports (dlltool -y delay-import archives +
mingw-w64-crt's bundled __delayLoadHelper2), so the loader doesn't need
them until the first actual GStreamer call - by which point
configure_gstreamer_windows_paths() has already registered the real
install's directories. obs-gstreamer.dll now loads with nothing but the
plugin file itself in the plugin's bin\64bit folder; no manual DLL
copying needed. Confirmed working (base load and element registry, e.g.
videoconvert) against a real OBS 32.2 + system-installed GStreamer setup.

The known dlltool/ld.bfd delay-import relocation bug reported upstream is
32-bit only; this toolchain targets x86_64 exclusively, and a minimal
delay-load proof of concept (plain DLL + delay-loaded call, no GStreamer
involved) ran correctly under this exact toolchain before converting the
real plugin.

docker/gen-delayimp-libs.sh generates the delay-import archives from the
real GStreamer install's DLLs at build time (gendef + dlltool -y);
docker/build-windows.sh runs it before meson setup and passes the output
dir via -Dgst_delayimp_dir. configure_gstreamer_windows_paths() now
returns false (and obs_module_load logs a clear error and bails) when no
GStreamer install is found at all, so a genuinely missing install still
fails cleanly instead of hitting the delay-load runtime's default
DebugBreak-on-failure path.
PATH-based setup is no longer needed - the plugin now locates the
GStreamer run-time itself via GSTREAMER_1_0_ROOT_MINGW_X86_64.
@fzwoch

fzwoch commented Aug 7, 2026

Copy link
Copy Markdown
Owner

That is a fair approach. It does add a lot of noise to the repo for some 'stupid' hack though. Also it specifically fights intentional behavior of the OBS team. So I'm kind of reluctant to merge it in.

That being said, if it fixes the issue people are having it is worth pointing at your branch and binary for people to use in their setup.

@fzwoch fzwoch closed this Aug 7, 2026
@fzwoch fzwoch reopened this Aug 7, 2026
@fzwoch

fzwoch commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Whoops. Didn't mean to close this and wasn't finished typing either.

Picking up the changes of this PR, another approach would be just a have a complete separate plugin to mitigate the changed behavior in OBS 32.2. I'll post the idea in #124

Comment thread gstreamer.c Outdated
L"GSTREAMER_1_0_ROOT_MSVC_X86_64",
};

SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugins should not be calling this, this is owned by the process.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4044627 - dropped the SetDefaultDllDirectories() call entirely. OBS 32.2 already makes that call itself (it's what actually dropped %PATH% from the process-wide search order), so the plugin doesn't need to re-assert it - AddDllDirectory() alone is enough to add to the search path OBS already established.

Comment thread gstreamer.c Outdated
if (GetFileAttributesW(core_dll) == INVALID_FILE_ATTRIBUTES)
continue;

if (!AddDllDirectory(bin_path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value from AddDllDirectory must be saved and passed to RemoveDllDirectory after the loading has completed, otherwise it permanently alters the DLL search path, which belongs to the process.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4044627 - the cookie is now saved in a static DLL_DIRECTORY_COOKIE and released via RemoveDllDirectory() in a new obs_module_unload(). Kept it alive for the plugin's whole lifetime rather than removing it right after this function returns, since GStreamer element modules can be g_module_open()'d lazily at any later point (e.g. the first time a given codec is actually used), not just during the initial load.

Drop the plugin's own SetDefaultDllDirectories() call - OBS 32.2 already
makes it, and a plugin shouldn't redecide that process-wide policy.
Save the AddDllDirectory() cookie and release it in obs_module_unload()
instead of leaking it for the process lifetime.
@kaymyst

kaymyst commented Aug 9, 2026

Copy link
Copy Markdown
Author

Pushed a fix for the review feedback above (4044627). Updated Windows test build if anyone wants to try it: https://github.com/kaymyst/obs-gstreamer/releases/tag/pr125-windows-test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed to load Module on OBS Studio 32.2.0 Beta 2

3 participants