From 0487947d91cb4cf69e23cc53b2b4d0b5bf50d240 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 16 Apr 2022 17:55:20 +0200 Subject: [PATCH] Export chainloader functions in the plugin libs These can be called from the new chainlaoder libraries to use yabridge without needing copies of the full fat `libyabridge-{vst2,vst3}.so` libraries. --- src/plugin/bridges/common.h | 4 ++ src/plugin/vst2-plugin.cpp | 89 +++++++++++++++++++++-------- src/plugin/vst3-plugin.cpp | 111 +++++++++++++++++++++++++++++------- 3 files changed, 159 insertions(+), 45 deletions(-) diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index b52b86ba..61cfc5d2 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -385,6 +385,8 @@ class PluginBridge { // Also show a desktop notification so users running from // the GUI get a heads up + // FIXME: Go through these messages and update them to + // reflect the chainloading changes send_notification( "Failed to start the Wine plugin host", "Check yabridge's output for more information on what " @@ -423,6 +425,8 @@ class PluginBridge { " you may need rerun 'yabridgectl sync' first to"); generic_logger_.log(" update your plugins."); + // FIXME: Go through these messages and update them to reflect the + // chainloading changes send_notification( "Version mismatch", "If you just updated yabridge, then you may need " diff --git a/src/plugin/vst2-plugin.cpp b/src/plugin/vst2-plugin.cpp index 7b80d30b..f5056741 100644 --- a/src/plugin/vst2-plugin.cpp +++ b/src/plugin/vst2-plugin.cpp @@ -16,11 +16,7 @@ #include -#include -#include - #include "../common/linking.h" -#include "../common/logging/common.h" #include "bridges/vst2.h" using namespace std::literals::string_literals; @@ -32,10 +28,45 @@ namespace fs = ghc::filesystem; // (EnergyXT being the only known host on Linux that uses the `main` entry // point). +// These plugin libraries can be used in one of two ways: they can either be +// loaded directly (the yabridge <4.0 way), or they can be loaded indirectly +// from `yabridge-chainloader-*.so` (the yabridge >=4.0 way). The advantage of +// chainloading this library from a tiny stub library is that yabridge can be +// updated without having to also replace all of the library copies and that it +// takes up less space on filesystems that don't support reflinking, but the +// catch is that we no longer have one unique plugin bridge library per plugin. +// This means that we cannot store the current bridge instance as a global in +// this library (because it would then be shared by multiple chainloaders), and +// that we cannot use `dladdr()` within this library to get the path to the +// current plugin, because thatq would return the path to this shared plugin +// library instead. To accommodate for this, we'll provide the usual plugin +// entry points, and we'll also provide simple methods for initializing the +// bridge so that the chainloading library can hold on to the bridge instance +// instead of this library. + +void log_init_error(const std::exception& error, const fs::path& plugin_path) { + Logger logger = Logger::create_exception_logger(); + + logger.log(""); + logger.log("Error during initialization:"); + logger.log(error.what()); + logger.log(""); + + // Also show a desktop notification most people likely won't see the above + // message + send_notification( + "Failed to initialize VST2 plugin", + error.what() + + "\nIf you just updated yabridge, then you may need to rerun " + "'yabridgectl sync' first to update your plugins."s, + plugin_path); +} + /** - * The main VST2 plugin entry point. We first set up a bridge that connects to a - * Wine process that hosts the Windows VST2 plugin. We then create and return a - * VST plugin struct that acts as a passthrough to the bridge. + * The main VST2 plugin entry point for when this plugin library is used + * directly. We first set up a bridge that connects to a Wine process that hosts + * the Windows VST2 plugin. We then create and return a VST plugin struct that + * acts as a passthrough to the bridge. * * To keep this somewhat contained this is the only place where we're doing * manual memory management. Clean up is done when we receive the `effClose` @@ -43,9 +74,9 @@ namespace fs = ghc::filesystem; */ extern "C" YABRIDGE_EXPORT AEffect* VSTPluginMain( audioMasterCallback host_callback) { - // FIXME: Update this for the chainloading - const fs::path plugin_path = get_this_file_location(); + assert(host_callback); + const fs::path plugin_path = get_this_file_location(); try { // This is the only place where we have to use manual memory management. // The bridge's destructor is called when the `effClose` opcode is @@ -55,21 +86,7 @@ extern "C" YABRIDGE_EXPORT AEffect* VSTPluginMain( return &bridge->plugin_; } catch (const std::exception& error) { - Logger logger = Logger::create_exception_logger(); - - logger.log(""); - logger.log("Error during initialization:"); - logger.log(error.what()); - logger.log(""); - - // Also show a desktop notification most people likely won't see the - // above message - send_notification( - "Failed to initialize VST2 plugin", - error.what() + - "\nIf you just updated yabridge, then you may need to rerun " - "'yabridgectl sync' first to update your plugins."s, - plugin_path); + log_init_error(error, plugin_path); return nullptr; } @@ -83,3 +100,27 @@ extern "C" YABRIDGE_EXPORT AEffect* deprecated_main( YABRIDGE_EXPORT AEffect* deprecated_main(audioMasterCallback audioMaster) { return VSTPluginMain(audioMaster); } + +/** + * This function can be called from the chainloader to initialize a new plugin + * bridge instance. Since VST2 only has a single plugin entry point and plugins + * clean up after themselves in `effClose()`, this is the only function the + * chainloader will call. + */ +extern "C" YABRIDGE_EXPORT AEffect* yabridge_plugin_init( + audioMasterCallback host_callback, + const char* plugin_path) { + assert(host_callback); + assert(plugin_path); + + try { + Vst2PluginBridge* bridge = + new Vst2PluginBridge(plugin_path, host_callback); + + return &bridge->plugin_; + } catch (const std::exception& error) { + log_init_error(error, plugin_path); + + return nullptr; + } +} diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 256532e1..9a430e71 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "../common/linking.h" +#include "bridges/vst3.h" + // FIXME: The VST3 SDK as of version 3.7.2 now includes multiple local functions // called `InitModule` and `DeinitModule`: one in the new // `public.sdk/source/main/initmodule.cpp`, and the existing ones in the @@ -25,9 +28,6 @@ // NOLINTNEXTLINE(bugprone-suspicious-include) #include -#include "../common/linking.h" -#include "bridges/vst3.h" - using namespace std::literals::string_literals; namespace fs = ghc::filesystem; @@ -46,36 +46,63 @@ namespace fs = ghc::filesystem; // plugin symlinked to either the `x86_64-win` or the `x86-win` directory inside // of the bundle, even if it does not come in a bundle itself. +// These plugin libraries can be used in one of two ways: they can either be +// loaded directly (the yabridge <4.0 way), or they can be loaded indirectly +// from `yabridge-chainloader-*.so` (the yabridge >=4.0 way). The advantage of +// chainloading this library from a tiny stub library is that yabridge can be +// updated without having to also replace all of the library copies and that it +// takes up less space on filesystems that don't support reflinking, but the +// catch is that we no longer have one unique plugin bridge library per plugin. +// This means that we cannot store the current bridge instance as a global in +// this library (because it would then be shared by multiple chainloaders), and +// that we cannot use `dladdr()` within this library to get the path to the +// current plugin, because thatq would return the path to this shared plugin +// library instead. To accommodate for this, we'll provide the usual plugin +// entry points, and we'll also provide simple methods for initializing the +// bridge so that the chainloading library can hold on to the bridge instance +// instead of this library. + +/** + * The global plugin bridge instance. Only used if this plugin library is used + * directly. When the library is chainloaded, this will remain a null pointer. + */ std::unique_ptr bridge; +void log_init_exception(const std::exception& error, + const fs::path& plugin_path) { + Logger logger = Logger::create_exception_logger(); + + logger.log(""); + logger.log("Error during initialization:"); + logger.log(error.what()); + logger.log(""); + + // Also show a desktop notification most people likely won't see the above + // message + // FIXME: Go through these messages and update them to reflect the + // chainloading changes + send_notification( + "Failed to initialize VST3 plugin", + error.what() + + "\nIf you just updated yabridge, then you may need to rerun " + "'yabridgectl sync' first to update your plugins."s, + plugin_path); +} + // These functions are called by the `ModuleEntry` and `ModuleExit` functions on -// the first load and load unload +// the first load and load unload. The chainloader library has similar functions +// that call the `yabridge_module_` functions exported at the bottom of +// this file. bool InitModule() { assert(!bridge); - // FIXME: Update this for the chainloading const fs::path plugin_path = get_this_file_location(); - try { bridge = std::make_unique(plugin_path); return true; } catch (const std::exception& error) { - Logger logger = Logger::create_exception_logger(); - - logger.log(""); - logger.log("Error during initialization:"); - logger.log(error.what()); - logger.log(""); - - // Also show a desktop notification most people likely won't see the - // above message - send_notification( - "Failed to initialize VST3 plugin", - error.what() + - "\nIf you just updated yabridge, then you may need to rerun " - "'yabridgectl sync' first to update your plugins."s, - plugin_path); + log_init_exception(error, plugin_path); return false; } @@ -101,3 +128,45 @@ GetPluginFactory() { return bridge->get_plugin_factory(); } + +/** + * This function can be called from the chainloader to initialize a new plugin + * bridge instance. The caller should store the pointer and later free it again + * using the `yabridge_module_free()` function. If the bridge could not + * initialize due to an error, then the error will be logged and a null pointer + * will be returned. + */ +extern "C" YABRIDGE_EXPORT Vst3PluginBridge* yabridge_module_init( + const char* plugin_path) { + assert(plugin_path); + + try { + return new Vst3PluginBridge(plugin_path); + } catch (const std::exception& error) { + log_init_exception(error, plugin_path); + + return nullptr; + } +} + +/** + * Free a bridge instance returned by `yabridge_module_init`. + */ +extern "C" YABRIDGE_EXPORT void yabridge_module_free( + Vst3PluginBridge* instance) { + if (instance) { + delete instance; + } +} + +/** + * Our VST3 plugin's entry point. When building the plugin factory we'll host + * the plugin in our Wine application, retrieve its information and supported + * classes, and then recreate it here. + */ +extern "C" YABRIDGE_EXPORT Steinberg::IPluginFactory* +yabridge_module_get_plugin_factory(Vst3PluginBridge* instance) { + assert(instance); + + return instance->get_plugin_factory(); +}