有时我们需要在当前运行的dll或exe模块中去动态加载当前模块同路径中的另一个库,或者启动当前模块同路径中的另一个exe程序,一般需要获取当前模块的路径,然后去构造同路径下目标模块的绝对路径,然后通过该绝对路径去加载或启动该目标进程。
最近我们在一个SDK项目中遇到了类似的问题,客户的Java程序通过JNI调用我们的C++ 动态库umssdk.dll,然后在该dll动态库初始化的接口中自动启动同路径下的XxLauncher.exe程序。umssdk.dll库中启动XxLauncher.exe程序的代码如下:
// 启动XxLauncher.exe
void StartExe()
{
TCHAR achLog[256] = { 0 };
TCHAR szPath[MAX_PATH] = { 0 };
TCHAR *p = szPath;
// 获取当前模块的路径
::GetModuleFileName(NULL, szPath, MAX_PATH);
// 用当前模块的路径去构造目标模块XxLauncher.exe的绝对路径,然后使用该绝对路径去启动该exe程序
p += _tcslen(szPath);
while (*p-- != _T('\\'));
*(p + 2) = 0;
_tcscat(szPath, _T("XxLauncher.exe"));
SHELLEXECUTEINFO si;
RtlZeroMemory(&si, sizeof(SHELLEXECUTEINFO));
si.cbSize = sizeof(SHELLEXECUTEINFO);
si.lpFile = szPath;
si.nShow = SW_SHOWNORMAL;
si.lpVerb = _T("open");
BOOL bRet = ShellExecuteEx(&si);
if (!bRet)
{
int nErr = GetLastError();
int nHInsVal = (int)si.hInstApp;
}
}
代码中调用API函数GetModuleFileName获取当前dll模块的路径,然后用当前模块的路径去构造目标模块XxLauncher.exe的绝对路径,然后使用该绝对路径去启动该exe程序。
Java主程序通过JNI加载的umssdk.dll路径如下所示,要启动的程序XxLauncher.exe也在umssdk.dll同一个路径中,如下所示:
但客户Java程序在启动umssdk.dll库时,弹错如下的报错提示框:
居然跑到D:\aa_tools\5.0_idx_64\eclipse\jre\bin\路径下去启动XxLauncher.exe程序,难道这是受当前的工作路径影响导致的?
于是去查看启动XxLauncher.exe程序代码,代码中使用API函数GetModuleFileName去获取当前加载的umssdk.dll路径,隐约中想起来,当给GetModuleFileName函数第一个参数传递NULL时,GetModuleFileName获取的好像是启动当前进程的主程序路径,即Java主程序的路径。于是,到MSDN上查看GetModuleFileName函数的说明,如下:
当第一个参数传NULL时,GetModuleFileName获取的是确实是启动当前进程的主程序路径,就是Java主程序的路径。
如果要获取当前umssdk.dll库的路径,应该传入umssdk.dll模块的实例句柄,那如何获取umssdk.dll实例句柄呢?dll库的实例句柄可以在dll库的入口函数DllMain中获取,如下所示:
HMODULE g_hDLLInstance = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
g_hDLLInstance = hModule;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
即定义一个全局实例句柄变量g_hDLLInstance,在DllMain中将当前dll模块的句柄保存下来,然后在调用GetModuleFileName函数时使用该g_hDLLInstance变量接口,修改后的代码如下所示:
// 启动XxLauncher.exe
void StartExe()
{
TCHAR achLog[256] = { 0 };
TCHAR szPath[MAX_PATH] = { 0 };
TCHAR *p = szPath;
// 获取当前模块的路径
::GetModuleFileName(g_hDLLInstance, szPath, MAX_PATH);
// 用当前模块的路径去构造目标模块XxLauncher.exe的绝对路径,然后使用该绝对路径去启动该exe程序
p += _tcslen(szPath);
while (*p-- != _T('\\'));
*(p + 2) = 0;
_tcscat(szPath, _T("XxLauncher.exe"));
SHELLEXECUTEINFO si;
RtlZeroMemory(&si, sizeof(SHELLEXECUTEINFO));
si.cbSize = sizeof(SHELLEXECUTEINFO);
si.lpFile = szPath;
si.nShow = SW_SHOWNORMAL;
si.lpVerb = _T("open");
BOOL bRet = ShellExecuteEx(&si);
if (!bRet)
{
int nErr = GetLastError();
int nHInsVal = (int)si.hInstApp;
}
}
如果是exe模块,如何获取当前exe模块的实例句柄呢?其实很简单,在WinMain函数中记录一下即可,如下所示:
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
// 此处将当前应用程序的实例保存到g_hInstance,因为有的API调用时要使用该参数
g_hInstance = hInstance;
}