OBS扩展模块DLL-加载模块
2024-06-20
24
0
设置了一系列的模块的路径之后,需要在这些路径下搜索模块dll,并将其加载到内存中。
模块的加载入口位于:OBSBasic::OBSInit(),其函数调用关系如下:
- OBSBasic::OBSInit()
- obs_load_all_modules2(&mfi); //加载模块,失败信息存储在struct obs_module_failure_info mfi;
- obs_log_loaded_modules(); //仅是打印划块的文件名
- obs_post_load_modules(); //如果模块的post_load函数存在,则调用
从这里来看,具有三个函数,但只有obs_load_all_modules2是真正的加载模块,其余2个一个是打印日志,一个是有post_load函数的调用。
obs_load_all_modules2
void obs_load_all_modules2(struct obs_module_failure_info *mfi)
{
/
struct fail_info fail_info = {0};
memset(mfi, 0, sizeof(*mfi));
//线程调试统计信息
profile_start(obs_load_all_modules2_name);
//执行回调load_all_callback
obs_find_modules2(load_all_callback,, &fail_info);
#ifdef _WIN32 //只32位系统,符号库信息
profile_start(reset_win32_symbol_paths_name);
reset_win32_symbol_paths();
profile_end(reset_win32_symbol_paths_name);
#endif
profile_end(obs_load_all_modules2_name);
mfi->count = fail_info.fail_count;
mfi->failed_modules =strlist_split(fail_info.fail_modules.array, ';', false);
dstr_free(&fail_info.fail_modules);
}
对于模块中的每一个路径,调用find_modules_in_path查找模块,并执行load_all_callback加载。
void obs_find_modules2(obs_find_module_callback2_t callback, void *param)
{
if (!obs)
return;
for (size_t i = 0; i < obs->module_paths.num; i++) {
struct obs_module_path *omp = obs->module_paths.array + i;
find_modules_in_path(omp, callback, param);
}
}
find_modules_in_path函数用于查找该路径下的所有模块,并执行回调函数callback=load_all_callback
static void find_modules_in_path(struct obs_module_path *omp,
obs_find_module_callback2_t callback,
void *param)
{
struct dstr search_path = {0};
char *module_start;
bool search_directories = false;
os_glob_t *gi;
dstr_copy(&search_path, omp->bin);
//通过路径最后的"%module%" 标识是模块类型
module_start = strstr(search_path.array, "%module%");
if (module_start) {
//取除后面的"%module%" 字会串
dstr_resize(&search_path, module_start - search_path.array);
search_directories = true;
}
//后面加上/,路径结束符
if (!dstr_is_empty(&search_path) && dstr_end(&search_path) != '/')
dstr_cat_ch(&search_path, '/');
//最终变为/*.dll
dstr_cat_ch(&search_path, '*');
if (!search_directories)
dstr_cat(&search_path, get_module_extension());
//调用os_glob搜索路径下的dll,并返回到gi中
if (os_glob(search_path.array, 0, &gi) == 0)
{
//对于每一个文件,调用process_found_module
for (size_t i = 0; i < gi->gl_pathc; i++)
{
if (search_directories == gi->gl_pathv[i].directory)
process_found_module(omp,
gi->gl_pathv[i].path,
search_directories,
callback,
param);
}
os_globfree(gi);
}
dstr_free(&search_path);
}
process_found_module中调用回调函数。
static void process_found_module(struct obs_module_path *omp, const char *path,
bool directory,
obs_find_module_callback2_t callback,
void *param)
{
struct obs_module_info2 info;
struct dstr name = {0};
struct dstr parsed_bin_path = {0};
const char *file;
char *parsed_data_dir;
bool bin_found = true;
file = strrchr(path, '/');
file = file ? (file + 1) : path;
if (strcmp(file, ".") == 0 || strcmp(file, "..") == 0)
return;
dstr_copy(&name, file);
char *ext = strrchr(name.array, '.');
if (ext)
dstr_resize(&name, ext - name.array);
if (!directory) {
dstr_copy(&parsed_bin_path, path);
} else {
bin_found = parse_binary_from_directory(&parsed_bin_path,omp->bin, name.array);
}
parsed_data_dir = make_data_directory(name.array, omp->data);
if (parsed_data_dir && bin_found) {
info.bin_path = parsed_bin_path.array;//dll路径
info.data_path = parsed_data_dir; //配置文件路径
info.name = name.array; //dll名称
//调用回调函数
callback(param, &info);
}
bfree(parsed_data_dir);
dstr_free(&name);
dstr_free(&parsed_bin_path);
}
回调函数load_all_callback
static void load_all_callback(void *param, const struct obs_module_info2 *info)
{
struct fail_info *fail_info = param;
obs_module_t *module;
bool is_obs_plugin;
bool can_load_obs_plugin;
get_plugin_info(info->bin_path, &is_obs_plugin, &can_load_obs_plugin);
if (!is_obs_plugin) {
blog(LOG_WARNING, "Skipping module '%s', not an OBS plugin", info->bin_path);
return;
}
if (!is_safe_module(info->name)) {
blog(LOG_WARNING, "Skipping module '%s', not on safe list",
info->name);
return;
}
if (!can_load_obs_plugin) {
blog(LOG_WARNING,
"Skipping module '%s' due to possible "
"import conflicts",
info->bin_path);
goto load_failure;
}
int code = obs_open_module(&module, info->bin_path, info->data_path);
switch (code) {
case MODULE_MISSING_EXPORTS:
blog(LOG_DEBUG,
"Failed to load module file '%s', not an OBS plugin",
info->bin_path);
return;
case MODULE_FILE_NOT_FOUND:
blog(LOG_DEBUG,
"Failed to load module file '%s', file not found",
info->bin_path);
return;
case MODULE_ERROR:
blog(LOG_DEBUG, "Failed to load module file '%s'",
info->bin_path);
goto load_failure;
case MODULE_INCOMPATIBLE_VER:
blog(LOG_DEBUG,
"Failed to load module file '%s', incompatible version",
info->bin_path);
goto load_failure;
case MODULE_HARDCODED_SKIP:
return;
}
if (!obs_init_module(module))
free_module(module);
UNUSED_PARAMETER(param);
return;
load_failure:
if (fail_info) {
dstr_cat(&fail_info->fail_modules, info->name);
dstr_cat(&fail_info->fail_modules, ";");
fail_info->fail_count++;
}
}
除过一大堆的错误处理,主要其实还是get_plugin_info函数的调用,用于返回DLL的信息。如果是插件,则调用obs_open_module。
get_plugin_info函数返回的参数很有意思。
bool is_obs_plugin;
bool can_load_obs_plugin;
get_plugin_info(info->bin_path, &is_obs_plugin, &can_load_obs_plugin);
get_plugin_info函数的代码具体过程,通过文件映射将dll映射到内存中:
- is_obs_plugin:通过判断导出表中的函数中是否有obs_module_load判断是否obs插件
- can_load_obs_plugin:排除掉qt的dll
obs_open_module
dll校验是OBS插件后。调用obs_open_module加载模块插件。
//obs-module.c
int code = obs_open_module(&module, info->bin_path, info->data_path);
obs_open_module函数的执行流程如下:
- 使用os_dlopen函数LoadLibrary
使用load_module_exports函数通过GetProcessAddress获取dll导出的函数指针。
struct obs_module { char *mod_name; //没有扩展名dll的文件名 const char *file;//文件名 xxx.dll char *bin_path; //完整的dll的PathName char *data_path;//配置文件ini路径 void *module; //LoadLibrary返回的HMODULE实例 bool loaded; //obs_init_module函数中调用bool (*load)(void);的返回值 bool (*load)(void);//必须存在 void (*unload)(void); void (*post_load)(void); void (*set_locale)(const char *locale);// 参数obs->locale bool (*get_string)(const char *lookup_string, const char **translated_string); void (*free_locale)(void); uint32_t (*ver)(void);//必须存在 void (*set_pointer)(obs_module_t *module);//必须存在 const char *(*name)(void); const char *(*description)(void); const char *(*author)(void); struct obs_module *next;//链表,下一个。第一个存在obs->first_module; };
导出的表函数指针与函数名的对应关系如下:
static int load_module_exports(struct obs_module *mod, const char *path) { mod->load = os_dlsym(mod->module, "obs_module_load"); if (!mod->load) return req_func_not_found("obs_module_load", path); mod->set_pointer = os_dlsym(mod->module, "obs_module_set_pointer"); if (!mod->set_pointer) return req_func_not_found("obs_module_set_pointer", path); mod->ver = os_dlsym(mod->module, "obs_module_ver"); if (!mod->ver) return req_func_not_found("obs_module_ver", path); /* optional exports */ mod->unload = os_dlsym(mod->module, "obs_module_unload"); mod->post_load = os_dlsym(mod->module, "obs_module_post_load"); mod->set_locale = os_dlsym(mod->module, "obs_module_set_locale"); mod->free_locale = os_dlsym(mod->module, "obs_module_free_locale"); mod->name = os_dlsym(mod->module, "obs_module_name"); mod->description = os_dlsym(mod->module, "obs_module_description"); mod->author = os_dlsym(mod->module, "obs_module_author"); mod->get_string = os_dlsym(mod->module, "obs_module_get_string"); return MODULE_SUCCESS; }
obs_init_module初始化模块
obs_init_module主要是调用模块的回调函数load。
//obs-module.c
bool obs_init_module(obs_module_t *module)
{
if (!module || !obs)
return false;
if (module->loaded)
return true;
const char *profile_name = profile_store_name(obs_get_profiler_name_store(), "obs_init_module(%s)", module->file);
profile_start(profile_name);
module->loaded = module->load();
if (!module->loaded)
blog(LOG_WARNING, "Failed to initialize module '%s'", module->file);
profile_end(profile_name);
return module->loaded;
}
模块对象的存储
加载的所有模块,使用struct obs_module表示,其使用链表存储在obs->first_module。如对于obs_post_load_modules的调用是这样的.
void obs_post_load_modules(void)
{
for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next)
if (mod->post_load)
mod->post_load();
}