Windows Via C/C++ Part Ⅰ Chapter4: 进程—第一个Windows程序(2)

进程的实例句柄

  加载到进程地址空间的每一个可执行文件或DLL文件都被赋于一个唯一的实例句柄。wWinMain/WinMain的第一个参数hInstance就是可执行文件的实例句柄。实例句柄通常用在加载资源的函数中,比如从某个可执行文件的映像中加载图标资源时,可以调用函数:

HICON LoadIcon(HINSTANCE hInstance, PCTSTR pszIcon);

LoadIcon的第一个参数指明了包含要加载资源的文件(可执行文件或DLL)。许多应用将wWinMain/WinMain的hInstanace参数存储在全局变量中,以便程序的代码访问。

  Windows API中的有些函数需要类型为HMODULE的参数,比如GetModuleFileName:

DWORD GetModuleFileName(HMODULE hInstModule, PTSTR pszPath, DWORD cchPath);

在Win32下,HMODULE和HINSTANCE是同一数据类型,如果某函数的文档说明自身需要HMODULE类型的参数,你可以传递HINSTANCE类型的值,反之亦然。HMODULE和HINSTANCE只是在16位Windows系统中是不同的。

  wWinMain/WinMain hInstance参数的实际值是系统将可执行文件加载到进程地址空间里的基地址,比如系统打开可执行文件并将其内容加载到0x0040 0000,那么hInstance参数的值就是0x0040 0000。可执行文件被加载时的基地址是由链接器决定的,不同的链接器可能产生不同的默认基地址。Visual Studio的链接器使用0x0040 0000作为默认基地址,这是因为在win98下0x0040 0000是可执行文件被加载到的最低地址。你可以通过链接器选项/BASE:[address]来更改Visual Studio的这一默认行为。

  函数GetModuleHandle可以返回指定的DLL或可执行文件载入进程空间时的基地址/句柄:

HMODULE GetModuleHandle(PCTSTR pszModule);

pszModule参数是是0结尾的字符串,表示加载到当前进程空间的DLL/可执行文件的名称。函数找不到pszModule指定的文件时返回NULL。向pszModule传递NULL时函数返回当前可执行文件加载到当前进程中的基地址。在DLL中调用GetModuleHandle(NULL)返回的依然是调用DLL的可执行文件在进程中的基地址,而不是DLL自身加载到进程的基地址,获得DLL加载到进程的基地址可以使用使用两种方法。一是使用链接器定义的伪变量__ImageBase,它指向当前运行的模块(DLL或EXE)在进程中的基地址,CRT启动函数调用wWinMain/WinMain时就是将__ImageBase作为wWinMain/WinMain的hInstance参数传入。

  另一种方法是调用GetModuleHandleEx函数,此时应该用GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS作为第一个参数,用当前函数的地址做为第二个参数。第三个参数是指向HMODULE的指针,函数将用它返回获得的句柄/基址值。下面的代码展示了这两种方法:

extern "C" const IMAGE_DOS_HEADER __ImageBase; void DumpModule() { // Get the base address of the running application. // Can be different from the running module if this code is in a DLL. HMODULE hModule = GetModuleHandle(NULL); _tprintf(TEXT("with GetModuleHandle(NULL) = 0x%x/r/n"), hModule); // Use the pseudo-variable __ImageBase to get // the address of the current module hModule/hInstance. _tprintf(TEXT("with __ImageBase = 0x%x/r/n"), (HINSTANCE)&__ImageBase); // Pass the address of the current method DumpModule // as parameter to GetModuleHandleEx to get the address // of the current module hModule/hInstance. hModule = NULL; GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCTSTR)DumpModule, &hModule); _tprintf(TEXT("with GetModuleHandleEx = 0x%x/r/n"), hModule); } int _tmain(int argc, TCHAR* argv[]) { DumpModule(); return(0); }

  使用GetModuleHandle时有两点需要注意。第一,它只检查当前进程的地址空间,假如别的进程加载了ComDlg32.dll而当前进程又没有调用其中的任何方法,GetModuleHandle("ComDlg32")会返回NULL。第二,GetModuleHandle(NULL)仅返回调用该函数的可执行文件在当前进程中的基址,如果该调用发生在DLL中,函数仍然返回调用DLL的可执行文件的基址。

进程的前一个实例句柄

  前面已经提过,C/C++运行时启动函数调用wWinMain/WinMain时总是向其hPrevInstance函数传递NULL。hPrevInstance仅用于16位的Windows中,考虑到向后兼容,Win32保留了该参数。你不应该在代码中尝试引用该参数,但这时编译器会产生“未引用参数”的警告,为些你可以这样定义wWinMain/WinMain:

int WINAPI _tWinMain(HINSTANCE hInstanceExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow);

使用Visual Studio向导生成的C++ GUI项目使用UNREFERENCED_PARAMETER宏避免了该警告:

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); ... }

进程的命令行

  命令行是进程创建时所需的参数,它至少包含执行进程代码的EXE文件的路径。然而在后面讨论CreateProcess函数时我们会看到,进程的入口点函数_tWinMain接收的命令行可能为空,这是因为CRT入口点函数在调用wWinMain/WinMain前,会使用GetCommandLine函数获得进程的完整命令行并跳过可执行文件的路径部分,将剩下的内容传递给wWinMain/WinMain做为其命令行参数。

  应用程序可以使用任何方法解析命令行参数,你甚至可以覆盖pszCmdLine指向的缓冲区中的数据,但要记得不能超出缓冲区大小。通常你应该将其视为只读缓冲区,这是因为GetCommandLine函数总是返回指向相同地址的指针,一旦改写了这块缓冲区,你将无法得到进程真正的命令行参数。注意GetCommandLine返回的命令行参数包括EXE文件的完整路径。

  命令行参数可能包含若干分隔的符号,你可以使用C/C++运行时全局变量__argc和__argv获得这些符号,或者使用ShellAPI.h中定义的函数CommandLineToArgvW将Unicode字符串中独立的符号分离出来:

PWSTR* CommandLineToArgvW(PWSTR pszCmdLine, int* pNumArgs);

CommandLineToArgvW只接受Unicode字符串,它将解析后的结果存储在一个指针数组中并返回,同时将数组大小写入参数pNumArgs指向的整数中。CommandLineToArgvW函数会在其内部分配内存,你可以等待系统在进程结束时回收,也可以自己手动释放。手动释放时应该调用HeapFree方法:

int nNumArgs; PWSTR *ppArgv = CommandLineToArgvW(GetCommandLineW(), &nNumArgs); // Use the arguments… if (*ppArgv[1] == L'x') { ... } // Free the memory block HeapFree(GetProcessHeap(), 0, ppArgv);

你可能感兴趣的:(windows,Module,null,dll,exe,winapi)