进程实例句柄
- 加载到进程地址空间的每一个执行文件或者DLL文件都被赋予了一个独一无二的实例句柄。
- 可执行文件的实例被当作(w)WinMain函数的第一个参数hInstanceExe传入。在需要加载资源的函数调用中,一般都需要提供此句柄的值。
比如从一个可执行文件的映像中加载一个图标资源,就需要调用下面这个函数
1 2 3 |
HICON LoadIcon( HINSTANCE hInstance, PCTSTR pszIcon); |
- 有的函数需要一个HMODULE类型的参数。事实上,HMODULE和HINSTANCE完全是一回事。之所以有两种类型,是因为在16位的Windows中,这两种类型表示不同不同类型的数据。
如下:
1 2 3 4 |
DWORD GetMoudleFileName( HMODULE hInstModule, PTSTR pszPath, DWORD cchPath); |
- (w)WinMain的hInstanceExe参数实际上是一个内存基地址。系统将可执行文件的映像加载到进程地址空间中的这个位置。
- 需要注意的是,可执行文件的映像具体加载到哪一个基地址,是由链接器决定的。不同的链接器使用不同的默认基地址。由于历史原因,Visual Studio链接器使用的默认的基地址是0x00400000 。这是因为在运行Windows 98的时候,可执行文件的映像能加载到的最低的一个地址。另,可以使用Microsoft链接器的/BASE:address链接器开关,可以更改要将应用程序加载到哪个基地址。
- 可用下面的函数知道一个可执行文件或DLL文件被加载到进程地址空间的什么位置,该函数返回一个句柄/基地址:
也就是下面介绍的第一个方法:实质上是利用了链接器提供的伪变量__ImageBase,这个伪变量指向了当前正在运行的模块的基地址。
1 2 3 4 5 6 |
// 传递一个以0结尾的字符串 // 该字符串指定了已在主调进程的地址空间中加载的一个可执行文件或DLL文件的名称 // 如果系统找到了指定的可执行文件或DLL文件名称,就会返回可执行文件或DLL文件映像加载到的基地址 // 如果没有找到,系统就会返回NULL。 HMODULE GetModuleHandle(PCTSTR pszModule); |
1 2 3 4 |
// 除了上面的用法,该还是还有一个另外的用法,如下: // 参数传入NULL,可以返回主调进程的可执行文件的基地址 HMODULE GetModuleHandle(PCTSTR pszModule); |
- 如果代码是在一个DLL中运行,可利用两种方法来了解代码是在什么模块中运行:
- 第一种方法:利用链接器提供的伪变量__ImageBase,它指向当前正在运行的模块的基地址。(这是C运行库启动代码在调用我们的(w)WinMain函数时所做的事情)。
- 第二种方法:调用GetModuleHandleEx,将GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS作为它的第一个参数,将当前函数的返回地址作为第二个参数,最后一个参数是一个指向HMODULE的指针。
GetModuleHandleEx会用传入函数(也就是第二个参数)所在DLL的基地址来填写该指针。 - GetModuleHandle函数的两大特征就是:
第一它只检查主调进程的地址空间,如果主调进程没有使用任何通用对话框函数,那么一旦调用了这个函数,并向其传递了ComDlg32,就会导致返回NULL,即使ComDlg32也许已经加载到其他进程的地址空间了,也会返回NULL。
第二是调用这个函数并向它传递NULL值,会返回进程的地址空间中的可执行文件的基地址。也就是说,如果是在一个DLL中调用了这个GetModuleHandle(NULL),那么返回值仍然是可执行文件的基地址,而不是DLL文件的基地址。
进程前一个实例的句柄
- 前面所述,C/C++运行库启动代码总是向(w)WinMain的hPrevInstance参数(第二个参数)传递NULL。这个参数是给到16位操作系统用的,便于我们移植16位应用程序,所以它被保留了。
- 因而需要注意的是,绝对不要再程序中引用这个参数。一般不会给第二个参数指定参数名。如下:
1 2 3 4 |
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, PSTR pszCmdLine, int nCmdShow); |
- 由于没有知道参数名,所以编译器不会报告“参数没有被引用到”的警告。
- 但是,在Visual Studio中,用到了别的解决方案:在使用向导生成的GUI程序中,它们利用了一个叫UNREFERENCED_PARAMETER的宏来消除了这种警告。如下所示:
1 2 3 4 5 6 7 |
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); } |
进程的命令行
- 系统在创建一个新进程时,会传一个命令行给它。
- 这个命令行几乎总是非空的,至少,用于创建新进程的可执行文件的名称是命令行上的第一个标记(token)。
- C运行库的启动代码开始执行一个GUI程序时,会调用Windows函数GetCommandLine来获取进程的完整命令行,忽略了可执行文件的名称,然后将执行命令行剩余部分的一个指针传给了WinMain的pszCmdLine参数。
- 应用程序自己可以决定分析和解释命令行字符串的方式。
- 另外,实际上应用程序还可以pszCmdLine参数所执行的内存缓冲区写数据的。但是需要注意的是,在写数据时,任何时候都应该注意不能越界。一个比较好的做法是,将命令行缓冲区复制到一个本地缓冲区,再对本地缓冲区做写操作。
- 应用程序自己也可以通过调用GetCommandLine函数来获取完整的命令行。
- 我们还可以通过一个在ShellAPI.h中声明并由Shell32.dll导出的函数CommandLineToArgvW将任何Unicode字符串分解成单独的标记
1 2 3 4 5 6 7 8 9 10 11 |
int nNumArgs; PWSTR* ppArgv = CommandLineToArgvW(GetCommandLineW(), &nNumArgs); // 使用参数 if (*ppArgv[1] == L'X') { // do something } // 释放空间 HeapFree(GetProcessHeap(), 0, ppArgv); |
- 上面的函数返回是一个Unicode字符串指针数组的地址,这个空间是由CommandLineToArgvW在内部分配的。许多应用程序不会释放这块空间,等在操作系统在终止进程时释放这块空间。这是完全没问题的。
但是也可以手动释放,正确做法是用上面的HeapFree。
进程的坏境变量
- 每个进程都有一个与它关联的环境块。这个环境块是在进程地址空间内分配的一块内存。如下:
1 2 3 4 5 6 7 |
=::=::\ ... VarName1=VarValue1\0 VarName2=VarValue2\0 VarName3=VarValue3\0 VarName4=VarValue4\0 VarNamex=VarValuex\0 \0 |
- 每个字符串的第一部分是一个环境变量的名称,后面跟一个等号,等号之后是希望赋给此变量的值。
- 环境变量里面的空格是有意义的。
访问环境块的方式
- 使用GetEnvironmentStrings获取完整的环境块,得到的环境块就是上面示例的格式。
如果不再需要这个函数返回的内存块时,可以使用FreeEnvironmentStrings来释放它。
1 |
PTSTR pEnvBlock = GetEnvironmentStrings(); |
- 第二种方法是CUI程序专用的。通过应用程序main入口点函数所接收的TCHAR*env[]这个参数来实现。
env是一个字符串指针数组。每个指针指向一个不同的环境变量。在数组中,指向最后一个环境变量的指针后面,还会有一个NULL指针来表明数组的末尾。
登录过程与环境变量
- 用户登录Window时,系统会创建Shell进程,并将一组环境字符串与其关联。
- 系统通过检查注册表中的两个注册表项来获得初始的环境字符串。
- 第一个注册表项包含应用于系统的所有环境变量的列表
1 |
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment |
- 第二个注册表项包含应用于当前登录用户的所有环境变量的列表
1 |
HKEY_CURRENT_USER\Environment |
- 用户可以通过(高级系统设置-环境变量这个地方)来添加、删除或更改所有这些变量。修改系统变量得有管理员权限。
- 应用程序的话还可以使用各种注册表函数来修改这些注册表项。不过为了让改动对所有应用程序生效,用户必须注销并重新登录。
部分应用程序如资源管理器、任务管理器、控制面板等可以在其主窗口收到WM_SETTINGCHANGE消息时,用新的注册表项来更新它们的环境块。
改了注册表项,并希望应用程序立即更新它们的环境块,可如下:
1 |
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)TEXT("Environment")); |
关于子进程
- 通常,子进程会继承一组环境变量,这些环境变量和父进程的环境变量相同。
- 不过,父进程可以控制哪些环境变量允许子进程继承。
- 注意,这里的继承指的是子进程获得的是父进程的环境块的一个副本,这个副本是子进程专用的。子进程和父进程并不共享同一个环境块。
- 子进程可以在自己的环境块中添加、删除、修改变量,并不会影响到父进程的环境块。
其他函数
- 判断环境变量在不在
- pszName指向预期的变量名
- pszValue指向保存变量值的缓冲区
- cchValue指出缓冲区大小
- 找到了变量了,返回的是复制到缓冲区的字符数,没找到返回0。
- 可以通过将第三个参数传0来获得缓冲区应该的大小。
1 2 3 4 |
DWORD GetEnvironmentVariable( PCTSTR pszName, PTSTR pszValue, DWORD cchValue); |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void PrintEnvironmentVariable(PCTSTR pszVariableName) { PTSTR pszValue = NULL; DWORD dwResult = GetEnvironmentVariable(pszVariableName, pszvalue, 0); if (dwResult != 0) { DWORD size = dwResult * sizeof(TCHAR); pszValue = (PTSTR)malloc(size); GetEnvironmentVariable(pszVariableName, pszValue, size); // do something free(pszValue); } else { // report the error or do other things. } } |
- 对环境变量相关的字符串替换
- 可替换的环境变量的字符串一个字符串地址
- 用于接收扩展字符串的一个缓冲区的地址
- 这个缓冲区的最大大小
1 2 3 4 |
DWORD ExpendEnvironmentStrings( PCTSTR pszSrc, PTSTR pszDst, DWORD chSize); |
1 2 3 4 5 6 |
DWORD chValue = ExpendEnvironmentStrings(TEXT("PATH='%PATH%'"), NULL, 0); PTSTR pszBuffer = new TCHAR[chValue]; chValue = ExpendEnvironmentStrings(TEXT("PATH='%PATH%'"), pszBuffer, chValue); _tprintf(TEXT("%s\r\n"), pszBuffer); delete[] pszBuffer; |
- 添加一个环境变量
- 变量名
- 对应的值
- 如果指定的变量不存在,就添加这个变量
- 如果pszValue为NULL,会删除这个变量
1 2 3 |
BOOL SetEnvironmentVariable( PCTSTR pszName, PCTSTR pszValue); |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Windows核心编程_必备知识04/27
- ♥ WindowsETW进程监控相关03/17
- ♥ Spy++相关08/18
- ♥ Windows 窗口以及渲染相关06/15
- ♥ Soui五05/30
- ♥ Soui九07/25