一般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;
}