Windows平臺LoadLibrary加載動態庫搜索路徑的問題

1、背景

在給Adobe Premiere/After Effects等後期製做軟件開發第三方插件的時候,咱們總但願插件依賴的動態庫可以脫離插件的位置,單獨存儲到另一個地方。這樣一方面能夠與其餘程序共享這些動態庫,還能保證插件安裝時很是的清爽。就Adobe Premiere Pro/After Effects來講,插件文件是放到C:\Program Files\Adobe\Common\Plug-ins\7.0\MediaCore(Windows平臺)的。這個是PremiereProAfterEffects的公共插件目錄,兩者在啓動的時候都會嘗試去這個位置加載插件。與此同時,咱們但願本身開發的插件所依賴的動態庫放到另外的位置,另外也但願插件顯示連接的動態庫可以儘可能少。由於若是是顯式連接的話,這些插件依賴的動態庫必須和插件保存在同一個位置。否則插件找不到這些依賴文件就會加載失敗的。固然,咱們也能夠在環境變量裏面增長一條路徑,可是這容易污染環境變量,或者與其餘的程序庫產生衝突。LoadLibrary在這個時候就產生做用了。LoadLibrary經過將指定路徑的動態庫加載到當前的調用進程,而後獲取其導出的函數就能夠正常使用了。對於像第三方插件這樣的應用場景,LoadLibrary能夠說是個不錯的實現方式。可是正所以也有個弊端,咱們沒法使用工具得知其的依賴庫。windows

2、使用實例

咱們在給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

3、參考連接

1. https://blog.csdn.net/cuglifangzheng/article/details/50580279
2. https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya插件

相關文章
相關標籤/搜索