在給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插件