1. 程式人生 > >如何將程式的執行檔案和靜態載入動態庫放在不同的目錄

如何將程式的執行檔案和靜態載入動態庫放在不同的目錄

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