如何將程式的執行檔案和靜態載入動態庫放在不同的目錄
一般windows程式的exe和dll需要放在同一個目錄,靜態載入才不會報錯,否則需要修改path環境變數,將所有沒有和exe放在同一目錄的dll的路徑加在path環境變數中。
有沒有一種方法不去手動修改path環境變數並且可以將exe和dll隨心所欲的改變路徑呢?我沒有發現,但是我們可以將修改環境變數這件事情交給我們的程式本身來處理,那麼從現象上來看就是我們不需要修改環境變數而可以將dll從exe目錄中拿走,放到你所希望的位置。
其實實現這個想法不難,反而很簡單。
假設我們的exe模組叫“A.exe”,依賴“B.dll”和“C.dll",你希望將“B.dll”放在”./B“目錄下,而把"C.dll"放在”./C"目錄下。我們的處理思路是,再寫一個殼程式,假如說叫“E.exe”,“E.exe”除了系統的庫之外不依賴任何自己開發或者第三方的庫,那麼理論上它放在哪裡都是可以啟動的。
在“E.exe”的程式碼中,我們設定環境變數,將“B.dll”和“C.dll"所在的路徑加到Path環境變數中,並啟動“A.exe”,那麼在啟動“A.exe”時就能自然而然載入到所依賴的庫了。在啟動“A.exe”完成之後將Path環境變數還原。所以在啟動“A.exe”前後,Path環境變數看似並沒有改變,但是我們棘手的問題卻解決了。
當然為了避免使用者手動去點選“A.exe”彈出錯誤視窗的不好的使用者體驗,可以將“A.exe”編譯成dll,提供一個能夠啟動程式功能的匯出API,在“E.exe”中動態載入“A.dll”並呼叫匯出API,達到啟動程式的目的。
但是exe重程式設計dll可能引起的問題有很多,比如MFC中就會存在很多的坑,那麼還有一種簡單的思路,那就是直接改名,將編譯完成的“A.exe”直接改名成“A.dll”,然後在“E.exe”中通過CreateProcess的方式啟動“A.dll”,然後就沒“E.exe”什麼事,可以退出歇著去了。
下面是我自己使用的“E.exe”的實現程式碼,相當簡單,值得注意的是,“E.exe”執行時最好不要出現視窗,不然會很難看,至於怎麼讓程式不出現視窗,應該網上可以找到很多教程。
//E.exe Main.cpp class ChangePath { public: ChangePath() { size_t len = 0; char sz[2048] = {}; getenv_s(&len, sz,2048,"PATH"); m_szPath = sz; std::string szAddPath = "";//你的dll所在的絕對路徑,使用“;”隔開 std::string szNewPath = m_szPath + ";"; szNewPath += szAddPath; _putenv_s("PATH",szNewPath.c_str()); } ~ChangePath() { _putenv_s("PATH",m_szPath.c_str()); } private: std::string m_szPath; }; ChangePath changepath;//RAII修改Path環境變數 void LoadInstance(void *param) { #if 1//假dll USES_CONVERSION; char apppath[1024] = {}; std::string exename = "C:/A.dll"; strcpy_s(apppath,exename.c_str()); char commandline[2048] = {}; strcat_s(commandline,_countof(commandline),apppath); LPWSTR pszCmdLinew = GetCommandLineW(); int argc = 0; CString FilePath = _T(""); LPWSTR *argv = CommandLineToArgvW(pszCmdLinew, &argc); if (argv != NULL) { for (int i = 1; i < argc; ++i) { strcat_s(commandline,_countof(commandline)," "); strcat_s(commandline,_countof(commandline),W2A(argv[i])); }; LocalFree(argv); } PROCESS_INFORMATION pi; STARTUPINFOA si = {sizeof(si)}; CreateProcessA(apppath,commandline,NULL,NULL, FALSE, 0, NULL, NULL, &si, &pi); #else//真dll typedef void (* InvokFunc)();//定義函式指標型別 HINSTANCE hInst; hInst=LoadLibrary(_T("A.dll"));//動態載入Dll int error = GetLastError(); InvokFunc invokFunc=(InvokFunc)GetProcAddress(hInst,"Entrance");//獲取Dll的匯出函式 error = GetLastError(); if(invokFunc) { invokFunc(); } ::FreeLibrary(hInst);//釋放Dll函式 #endif } int main() { LoadInstance(NULL); return 1; }