cplugin: allow loading mpv_* symbols dynamically

Defining MPV_CPLUGIN_DYNAMIC_SYM during plugin compilation will replace mpv_*
functions with function pointers. Those pointer will be initialized when
loading the plugin.

It is recommended to use this symbol table when targeting Windows. The loader
does not have notion of global symbols. Loading cplugin into mpv process will
not allow this plugin to call any of the symbols that may be available in
other modules. Instead cplugin has to link explicitly to specific PE binary,
libmpv-2.dll/mpv.exe or any other binary that may have linked mpv statically.
This limits portability of cplugin as it would need to be compiled separately
for each of target PE binary that includes mpv's symbols. Which in practice
is unrealictis, as we want one cplugin to be loaded without those restrictions.

Instead of linking to any PE binary, we create function pointer for all mpv's
exported symbols. For convinience names of entrypoints are redefined to those
pointer so no changes are required in cplugin source code, except defining
MPV_CPLUGIN_DYNAMIC_SYM. Those function pointer are exported to make them
available for mpv to init with correct values during runtime, before calling
`mpv_open_cplugin`.

Note that those pointer are decorated with `selectany` attribute, so no need
to worry about multiple definitions, linker will keep only single instance.

This fixes cplugin usability on Windows. Without any API changes, only
recompilation with -DMPV_CPLUGIN_DYNAMIC_SYM is needed.
This commit is contained in:
Kacper Michajłow 2023-08-17 16:19:26 +02:00 committed by Dudemanguy
parent 4435b1a0d5
commit eab3842d8b
5 changed files with 241 additions and 4 deletions

View File

@ -21,15 +21,15 @@ C PLUGINS
You can write C plugins for mpv. These use the libmpv API, although they do not
use the libmpv library itself.
They are available on Linux/BSD platforms only and enabled by default if the
compiler supports linking with the ``-rdynamic`` flag.
They are enabled by default if compiler supports linking with the ``-rdynamic``
flag on Linux/BSD platforms. On Windows the are always enabled.
C plugins location
------------------
C plugins are put into the mpv scripts directory in its config directory
(see the `FILES`_ section for details). They must have a ``.so`` file extension.
They can also be explicitly loaded with the ``--script`` option.
(see the `FILES`_ section for details). They must have a ``.so`` or ``.dll``
file extension. They can also be explicitly loaded with the ``--script`` option.
API
---
@ -67,6 +67,10 @@ The current implementation requires that your plugins are **not** linked against
libmpv. What your plugins use are not symbols from a libmpv binary, but
symbols from the mpv host binary.
On Windows to make symbols from the host binary available, you have to define
MPV_CPLUGIN_DYNAMIC_SYM when compiling cplugin. This will load symbols
dynamically, before calling ``mpv_open_cplugin()``.
Examples
--------

View File

@ -28,10 +28,19 @@
#ifdef _WIN32
#define MPV_EXPORT __declspec(dllexport)
#define MPV_SELECTANY __declspec(selectany)
#elif defined(__GNUC__) || defined(__clang__)
#define MPV_EXPORT __attribute__((visibility("default")))
#define MPV_SELECTANY
#else
#define MPV_EXPORT
#define MPV_SELECTANY
#endif
#ifdef __cpp_decltype
#define MPV_DECLTYPE decltype
#else
#define MPV_DECLTYPE __typeof__
#endif
#ifdef __cplusplus
@ -1888,6 +1897,127 @@ MPV_EXPORT int mpv_get_wakeup_pipe(mpv_handle *ctx);
#endif
/**
* Defining MPV_CPLUGIN_DYNAMIC_SYM during plugin compilation will replace mpv_*
* functions with function pointers. Those pointer will be initialized when
* loading the plugin.
*
* It is recommended to use this symbol table when targeting Windows. The loader
* does not have notion of global symbols. Loading cplugin into mpv process will
* not allow this plugin to call any of the symbols that may be available in
* other modules. Instead cplugin has to link explicitly to specific PE binary,
* libmpv-2.dll/mpv.exe or any other binary that may have linked mpv statically.
* This limits portability of cplugin as it would need to be compiled separately
* for each of target PE binary that includes mpv's symbols. Which in practice
* is unrealistic, as we want one cplugin to be loaded without those restrictions.
*
* Instead of linking to any PE binary, we create function pointers for all mpv's
* exported symbols. For convenience names of entrypoints are redefined to those
* pointer, so no changes are required in cplugin source code, except of defining
* MPV_CPLUGIN_DYNAMIC_SYM. Those function pointer are exported to make them
* available for mpv to init with correct values during runtime, before calling
* `mpv_open_cplugin`.
*
* Note that those pointers are decorated with `selectany` attribute, so no need
* to worry about multiple definitions, linker will keep only single instance.
*/
#ifdef MPV_CPLUGIN_DYNAMIC_SYM
#define MPV_DEFINE_SYM_PTR(name) \
MPV_SELECTANY MPV_EXPORT \
MPV_DECLTYPE(name) *pfn_##name;
MPV_DEFINE_SYM_PTR(mpv_client_api_version)
#define mpv_client_api_version pfn_mpv_client_api_version
MPV_DEFINE_SYM_PTR(mpv_error_string)
#define mpv_error_string pfn_mpv_error_string
MPV_DEFINE_SYM_PTR(mpv_free)
#define mpv_free pfn_mpv_free
MPV_DEFINE_SYM_PTR(mpv_client_name)
#define mpv_client_name pfn_mpv_client_name
MPV_DEFINE_SYM_PTR(mpv_client_id)
#define mpv_client_id pfn_mpv_client_id
MPV_DEFINE_SYM_PTR(mpv_create)
#define mpv_create pfn_mpv_create
MPV_DEFINE_SYM_PTR(mpv_initialize)
#define mpv_initialize pfn_mpv_initialize
MPV_DEFINE_SYM_PTR(mpv_destroy)
#define mpv_destroy pfn_mpv_destroy
MPV_DEFINE_SYM_PTR(mpv_terminate_destroy)
#define mpv_terminate_destroy pfn_mpv_terminate_destroy
MPV_DEFINE_SYM_PTR(mpv_create_client)
#define mpv_create_client pfn_mpv_create_client
MPV_DEFINE_SYM_PTR(mpv_create_weak_client)
#define mpv_create_weak_client pfn_mpv_create_weak_client
MPV_DEFINE_SYM_PTR(mpv_load_config_file)
#define mpv_load_config_file pfn_mpv_load_config_file
MPV_DEFINE_SYM_PTR(mpv_get_time_us)
#define mpv_get_time_us pfn_mpv_get_time_us
MPV_DEFINE_SYM_PTR(mpv_free_node_contents)
#define mpv_free_node_contents pfn_mpv_free_node_contents
MPV_DEFINE_SYM_PTR(mpv_set_option)
#define mpv_set_option pfn_mpv_set_option
MPV_DEFINE_SYM_PTR(mpv_set_option_string)
#define mpv_set_option_string pfn_mpv_set_option_string
MPV_DEFINE_SYM_PTR(mpv_command)
#define mpv_command pfn_mpv_command
MPV_DEFINE_SYM_PTR(mpv_command_node)
#define mpv_command_node pfn_mpv_command_node
MPV_DEFINE_SYM_PTR(mpv_command_ret)
#define mpv_command_ret pfn_mpv_command_ret
MPV_DEFINE_SYM_PTR(mpv_command_string)
#define mpv_command_string pfn_mpv_command_string
MPV_DEFINE_SYM_PTR(mpv_command_async)
#define mpv_command_async pfn_mpv_command_async
MPV_DEFINE_SYM_PTR(mpv_command_node_async)
#define mpv_command_node_async pfn_mpv_command_node_async
MPV_DEFINE_SYM_PTR(mpv_abort_async_command)
#define mpv_abort_async_command pfn_mpv_abort_async_command
MPV_DEFINE_SYM_PTR(mpv_set_property)
#define mpv_set_property pfn_mpv_set_property
MPV_DEFINE_SYM_PTR(mpv_set_property_string)
#define mpv_set_property_string pfn_mpv_set_property_string
MPV_DEFINE_SYM_PTR(mpv_del_property)
#define mpv_del_property pfn_mpv_del_property
MPV_DEFINE_SYM_PTR(mpv_set_property_async)
#define mpv_set_property_async pfn_mpv_set_property_async
MPV_DEFINE_SYM_PTR(mpv_get_property)
#define mpv_get_property pfn_mpv_get_property
MPV_DEFINE_SYM_PTR(mpv_get_property_string)
#define mpv_get_property_string pfn_mpv_get_property_string
MPV_DEFINE_SYM_PTR(mpv_get_property_osd_string)
#define mpv_get_property_osd_string pfn_mpv_get_property_osd_string
MPV_DEFINE_SYM_PTR(mpv_get_property_async)
#define mpv_get_property_async pfn_mpv_get_property_async
MPV_DEFINE_SYM_PTR(mpv_observe_property)
#define mpv_observe_property pfn_mpv_observe_property
MPV_DEFINE_SYM_PTR(mpv_unobserve_property)
#define mpv_unobserve_property pfn_mpv_unobserve_property
MPV_DEFINE_SYM_PTR(mpv_event_name)
#define mpv_event_name pfn_mpv_event_name
MPV_DEFINE_SYM_PTR(mpv_event_to_node)
#define mpv_event_to_node pfn_mpv_event_to_node
MPV_DEFINE_SYM_PTR(mpv_request_event)
#define mpv_request_event pfn_mpv_request_event
MPV_DEFINE_SYM_PTR(mpv_request_log_messages)
#define mpv_request_log_messages pfn_mpv_request_log_messages
MPV_DEFINE_SYM_PTR(mpv_wait_event)
#define mpv_wait_event pfn_mpv_wait_event
MPV_DEFINE_SYM_PTR(mpv_wakeup)
#define mpv_wakeup pfn_mpv_wakeup
MPV_DEFINE_SYM_PTR(mpv_set_wakeup_callback)
#define mpv_set_wakeup_callback pfn_mpv_set_wakeup_callback
MPV_DEFINE_SYM_PTR(mpv_wait_async_requests)
#define mpv_wait_async_requests pfn_mpv_wait_async_requests
MPV_DEFINE_SYM_PTR(mpv_hook_add)
#define mpv_hook_add pfn_mpv_hook_add
MPV_DEFINE_SYM_PTR(mpv_hook_continue)
#define mpv_hook_continue pfn_mpv_hook_continue
MPV_DEFINE_SYM_PTR(mpv_get_wakeup_pipe)
#define mpv_get_wakeup_pipe pfn_mpv_get_wakeup_pipe
#endif
#ifdef __cplusplus
}
#endif

View File

@ -731,6 +731,27 @@ MPV_EXPORT void mpv_render_context_report_swap(mpv_render_context *ctx);
*/
MPV_EXPORT void mpv_render_context_free(mpv_render_context *ctx);
#ifdef MPV_CPLUGIN_DYNAMIC_SYM
MPV_DEFINE_SYM_PTR(mpv_render_context_create)
#define mpv_render_context_create pfn_mpv_render_context_create
MPV_DEFINE_SYM_PTR(mpv_render_context_set_parameter)
#define mpv_render_context_set_parameter pfn_mpv_render_context_set_parameter
MPV_DEFINE_SYM_PTR(mpv_render_context_get_info)
#define mpv_render_context_get_info pfn_mpv_render_context_get_info
MPV_DEFINE_SYM_PTR(mpv_render_context_set_update_callback)
#define mpv_render_context_set_update_callback pfn_mpv_render_context_set_update_callback
MPV_DEFINE_SYM_PTR(mpv_render_context_update)
#define mpv_render_context_update pfn_mpv_render_context_update
MPV_DEFINE_SYM_PTR(mpv_render_context_render)
#define mpv_render_context_render pfn_mpv_render_context_render
MPV_DEFINE_SYM_PTR(mpv_render_context_report_swap)
#define mpv_render_context_report_swap pfn_mpv_render_context_report_swap
MPV_DEFINE_SYM_PTR(mpv_render_context_free)
#define mpv_render_context_free pfn_mpv_render_context_free
#endif
#ifdef __cplusplus
}
#endif

View File

@ -233,6 +233,13 @@ typedef int (*mpv_stream_cb_open_ro_fn)(void *user_data, char *uri,
MPV_EXPORT int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data,
mpv_stream_cb_open_ro_fn open_fn);
#ifdef MPV_CPLUGIN_DYNAMIC_SYM
MPV_DEFINE_SYM_PTR(mpv_stream_cb_add_ro)
#define mpv_stream_cb_add_ro pfn_mpv_stream_cb_add_ro
#endif
#ifdef __cplusplus
}
#endif

View File

@ -40,6 +40,8 @@
#include "core.h"
#include "client.h"
#include "libmpv/client.h"
#include "libmpv/render.h"
#include "libmpv/stream_cb.h"
extern const struct mp_scripting mp_scripting_lua;
extern const struct mp_scripting mp_scripting_cplugin;
@ -302,6 +304,76 @@ bool mp_load_scripts(struct MPContext *mpctx)
#define MPV_DLOPEN_FN "mpv_open_cplugin"
typedef int (*mpv_open_cplugin)(mpv_handle *handle);
static void init_sym_table(struct mp_script_args *args, void *lib) {
#define INIT_SYM(name) \
{ \
void **sym = (void **)dlsym(lib, "pfn_" #name); \
if (sym) { \
if (*sym && *sym != &name) \
MP_ERR(args, "Overriding already set function " #name "\n"); \
*sym = &name; \
} \
}
INIT_SYM(mpv_client_api_version);
INIT_SYM(mpv_error_string);
INIT_SYM(mpv_free);
INIT_SYM(mpv_client_name);
INIT_SYM(mpv_client_id);
INIT_SYM(mpv_create);
INIT_SYM(mpv_initialize);
INIT_SYM(mpv_destroy);
INIT_SYM(mpv_terminate_destroy);
INIT_SYM(mpv_create_client);
INIT_SYM(mpv_create_weak_client);
INIT_SYM(mpv_load_config_file);
INIT_SYM(mpv_get_time_us);
INIT_SYM(mpv_free_node_contents);
INIT_SYM(mpv_set_option);
INIT_SYM(mpv_set_option_string);
INIT_SYM(mpv_command);
INIT_SYM(mpv_command_node);
INIT_SYM(mpv_command_ret);
INIT_SYM(mpv_command_string);
INIT_SYM(mpv_command_async);
INIT_SYM(mpv_command_node_async);
INIT_SYM(mpv_abort_async_command);
INIT_SYM(mpv_set_property);
INIT_SYM(mpv_set_property_string);
INIT_SYM(mpv_del_property);
INIT_SYM(mpv_set_property_async);
INIT_SYM(mpv_get_property);
INIT_SYM(mpv_get_property_string);
INIT_SYM(mpv_get_property_osd_string);
INIT_SYM(mpv_get_property_async);
INIT_SYM(mpv_observe_property);
INIT_SYM(mpv_unobserve_property);
INIT_SYM(mpv_event_name);
INIT_SYM(mpv_event_to_node);
INIT_SYM(mpv_request_event);
INIT_SYM(mpv_request_log_messages);
INIT_SYM(mpv_wait_event);
INIT_SYM(mpv_wakeup);
INIT_SYM(mpv_set_wakeup_callback);
INIT_SYM(mpv_wait_async_requests);
INIT_SYM(mpv_hook_add);
INIT_SYM(mpv_hook_continue);
INIT_SYM(mpv_get_wakeup_pipe);
INIT_SYM(mpv_render_context_create);
INIT_SYM(mpv_render_context_set_parameter);
INIT_SYM(mpv_render_context_get_info);
INIT_SYM(mpv_render_context_set_update_callback);
INIT_SYM(mpv_render_context_update);
INIT_SYM(mpv_render_context_render);
INIT_SYM(mpv_render_context_report_swap);
INIT_SYM(mpv_render_context_free);
INIT_SYM(mpv_stream_cb_add_ro);
#undef INIT_SYM
}
static int load_cplugin(struct mp_script_args *args)
{
void *lib = dlopen(args->filename, RTLD_NOW | RTLD_LOCAL);
@ -312,6 +384,9 @@ static int load_cplugin(struct mp_script_args *args)
mpv_open_cplugin sym = (mpv_open_cplugin)dlsym(lib, MPV_DLOPEN_FN);
if (!sym)
goto error;
init_sym_table(args, lib);
return sym(args->client) ? -1 : 0;
error: ;
char *err = dlerror();