在给Adobe Premiere/After Effects等后期制做软件开发第三方插件的时候,咱们总但愿插件依赖的动态库可以脱离插件的位置,单独存储到另一个地方。这样一方面能够与其余程序共享这些动态库,还能保证插件安装时很是的清爽。就Adobe Premiere Pro/After Effects来讲,插件文件是放到C:\Program Files\Adobe\Common\Plug-ins\7.0\MediaCore(Windows平台)的。这个是PremierePro和AfterEffects的公共插件目录,两者在启动的时候都会尝试去这个位置加载插件。与此同时,咱们但愿本身开发的插件所依赖的动态库放到另外的位置,另外也但愿插件显示连接的动态库可以尽可能少。由于若是是显式连接的话,这些插件依赖的动态库必须和插件保存在同一个位置。否则插件找不到这些依赖文件就会加载失败的。固然,咱们也能够在环境变量里面增长一条路径,可是这容易污染环境变量,或者与其余的程序库产生冲突。LoadLibrary在这个时候就产生做用了。LoadLibrary经过将指定路径的动态库加载到当前的调用进程,而后获取其导出的函数就能够正常使用了。对于像第三方插件这样的应用场景,LoadLibrary能够说是个不错的实现方式。可是正所以也有个弊端,咱们没法使用工具得知其的依赖库。windows
咱们在给Adobe Premiere Pro开发的一款插件中,正是使用了这种方法:
(1)首先从注册表中获取到咱们插件依赖的动态库文件所在的位置:api
1 bool GetInstallationPath(std::string& result) { 2 DWORD data_type; 3 CHAR value[1024]; 4 PVOID pv_data = value; 5 DWORD size = sizeof(value); 6 auto err = RegGetValue(HKEY_CLASSES_ROOT, "test_app\\plugin", "install_location", RRF_RT_ANY, &data_type, pv_data, &size); 7 if (err == ERROR_SUCCESS) { 8 std::string filepath(value); 9 std::regex_replace(std::back_inserter(result), filepath.begin(), filepath.end(), std::regex("[\\\\/]+[^\\\\/]+$"), ""); 10 return true; 11 } 12 return false; 13 }
(2)经过调用LoadLibrary来加载指定的依赖库app
std::string dirname; if (!GetInstallationPath(dirname)) { return false; } SetDllDirectory(dirname.c_str()); insmedia_dll.handle = LoadLibrary("core.dll");
如上述代码所示,咱们的插件惟一依赖的动态库叫core.dll。而core.dll文件存放的位置记录在注册表中。程序先从注册表中获取core.dll所在的文件夹,而后设置到DLL的搜索路径中。最后再调用LoadLibrary加载它。在最初开发及发布后,插件运行的很好。然而,在Adobe发布Premiere Pro CC 2020以后,插件就不工做了。这是为啥呢?根据过往的经验来看,插件加载不上只有一个缘由:依赖的动态库缺失或者是加载错了版本。那么,咱们就来看看究竟是哪一个依赖加载错了致使插件加载失败呢?经过在WinDBG里面调试看到了以下的差别:函数
看上图很显然,咱们的插件在加载ffmpeg的库文件时,先找到了PremierePro安装根目录里面的版本了。而PremierePro使用的ffmpeg版本显然跟咱们不同。正是由于这两个库的版本不对,致使咱们的插件加载失败了。那么,LoadLibrary这种方法显然仍是存在一些Bug了。咱们的core.dll还依赖OpenCV、ffmpeg等第三方库。看MSDN的解释是,LoadLibrary会先从调用进程的目录下搜索动态库的依赖。这样的行为显然不是咱们想要的。这个时候,咱们还有个选择:使用LoadLibraryEx。具体的使用方法仍然同样,只不过传给LoadLibraryEx的第一个参数是咱们要加载的动态库的绝对路径:工具
1 std::string dirname; 2 if (!GetInstallationPath(dirname)) { 3 return false; 4 } 5 6 std::string absolute_path = dirname + "\\InsMedia.dll"; 7 insmedia_dll.handle = LoadLibraryEx(absolute_path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); 8 if (!insmedia_dll.handle) { 9 return false; 10 }
注意到第三个参数为LOAD_WITH_ALTERED_SEARCH_PATH,经过指定LOAD_WITH_ALTERED_SEARCH_PATH,让系统DLL搜索顺序从DLL所在目录开始。这样就可以保证加载动态库的时候优先加载咱们打包的动态库。从而避免由于动态库加载错误致使插件失败。spa
从上图能够看到,全部依赖的动态库都变成了咱们本身提供的库文件了,插件也能正常加载了。完美!.net
1. https://blog.csdn.net/cuglifangzheng/article/details/50580279
2. https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya插件