Windows via C/C++ —— 进程(一)读书笔记

一个进程通常是一个运行的程序的实例,它由两个部分构成: 1. 一个内核对象,操作系统用它管理进程,并保存进程的统计信息。 2. 一个地址空间,它包含所有的可执行或DLL模块的代码和数据,也包含了动态分配的内存如栈和堆等。

一个进程要能完成某些功能,它必须要有一个线程运行,该线程负责执行包含在进程地址空间听代码。一个进程可以包含多个线程,它们可以同时运行,每个线程都有自己的CPU寄存器组和自己的堆栈。当一个进程创建时,系统会自己创建它的第一个线程,称为基本线程(primary thread)。这个线程可以创建另外的线程。如果进程地址空间中没有线程运行,系统自动销毁进程及其地址空间。 一. 编写第一个Windows程序 Windows程序分成GUI和CUI,后者虽然也包含在窗口中,但窗口只能包含文本,如CMD.EXE。用vs创建程序时,链接器使用/SUBSYSTEM来决定程序类型。生成的类型信息会放在执行映像的头部。当系统加载执行映像时,它会查找这个信息,如果是控制台程序,系统会启动一个控制台窗口。而如果是GUI程序,则系统不会启动控制台窗口而只是加载该映像。一旦程序启动,系统便不会关心程序类型了。 Windows程序的进入点函数有下面两个: int WINAPI _tWinMain(    HINSTANCE hInstanceExe,    HINSTANCE,    PTSTR pszCmdLine,    int nCmdShow);

int _tmain(    int argc,    TCHAR *argv[],    TCHAR *envp[]); 但是操作系统不会调用你写的进入点函数!它是调用C/C++启动函数,后者由链接时的 -entry:XXX设置。这个函数会初始化C/C++运行库,这样你可以调用如malloc或free等,你还确保你声明的任何全局或静态C++对象在代码退出时被销毁。这些函数:WinMainCRTStartup,wWinMainCRTStartup,mainCRTStartup, wmainCRTStartup等(加w的是宽字符版本)。这四个函数在crtexe.c文件中可以找到。这些启动函数完成如下功能: 1. 取得新进程的全部命令行参数的指针 2. 取得新进程的环境变量的指针 3. 初始化C/C++运行时全局变量。你的代码如果包含StdLib.h就可以访问这些变量,如_osver, _winmajor, _winminor, _winver, __argc, __argv, __wargv, _environ, _wenviron, _pgmptr, _wpgmptr等。 4. 用C进行时函数malloct和calloc和底层I/O函数初始化堆 5. 为所有全局和静态C++类对象调用构造函数。 当然这些初始化完成后便调用你的启动函数。代码如下(UNICODE版本): GetStartupInfo(&StartupInfo); int nMainRetVal = WinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineAnsi,    (StartupInfo.dwFlags & STARTF_USESHOWWINDOW)       ? StartupInfo.wShowWindow : SW_SHOWDEFAULT); int nMainRetVal = wmain(argc, argv, envp); 如果要在_tmain的入口访问环境变量,则_tmain应写成 int _tmain(int argc, TCHAR* argv[], TCHAR* env[]) 当你的进入点函数退出时,启动函数调用C运行时的exit函数,它传递返回值能它并完成如下功能: 1. 调用由_onexit函数调用的任何函数 2. 为所有全局和静态C++类对象调用析构函数 3. 如果以DEBUG方式生成,则C/C++运行时内存的任何泄漏都会由 _CrtDumpMemoryLeaks函数列出 4. 调用操作系统的ExitProcess函数,把返回值传递给它。 但是由于安全原因,这些变量都已经变成过时的,你应该调用相应的Windows API函数来获取这些变量。 二. 进程实例句柄 装载到进程地址空间的每个可执行的或DLL文件都会被赋予一个唯一的实例句柄,你的可执行文件的实例是作为WinMain的第一个参数传递hInstance,该句柄值通常用于装载资源等,如 HICON LoadIcon(HINSTANCE hInstance,  PCTSTR pszIcon); 在上面的调用中,第一个参数就表示哪个文件(哪个DLL)包含了你要加载的资源。许多程序把hInstance保存作为一个全局变量。 SDK文档中有些函数需要HMODULE类型的参数,实际上它们是一样的。如 DWORD GetModuleFileName(    HMODULE hInstModule,    PTSTR pszPath,    DWORD cchPath); hInstance的真实值是可执行文件映像装载的基址,vs链接器默认是0x00400000,你可通过/BASE:address进行更改。 函数GetModuleHandle可取得一个可执行或DLL文件的装载句柄,它用文件名作为参数,如果参数是NULL,则返回调用者的句柄。但如果代码中DLL中运行,要获取句柄有两种办法: 1. 使用链接器提供的__ImageBase伪变量(C运行时代码就以这种方式) 2. 调用GetModuleHandleEx

下面是这些代码: #include<stdio.h> #include<windows.h>

extern "C" const IMAGE_DOS_HEADER __ImageBase;

void DumpModule(){     HMODULE hModule = GetModuleHandle(NULL);     printf("用GetModuleHandle(NULL) = 0x%x/r/n", hModule);         printf("用__ImageBase = 0x%x/r/n", (HINSTANCE)&__ImageBase);         hModule = NULL;     GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,         (PCTSTR)DumpModule, &hModule);     printf("用GetModuleHandleEx = 0x%x/r/n", hModule); }

int main(int argc, TCHAR* argv[]){     DumpModule();     return 0; } 上面代码可以看出,不管在哪调用GetModuleHandle,它总返回可执行文件的基址。 另外,在生成的WinMain函数的第二个参数也是HINSTANCE,它是指前一程序的实例句柄,这个参数现在不应该使用。

三. 进程命令行 当新进程创建时,它会传递一个命令行。它从来不会为空(至少有可执行文件名)。当C运行时执行GUI程序时,它是用GetCommandLine函数获取命令行参数, 跳过可执行文件名,把剩下的传递给pszCommandLine。这块缓冲不应该写入任何东西。 要取得命令行参数,可以使用CommandLineToArgvW函数,它在Shell32.dll中,声明于ShellAPI.h。调用方式如下: int nNumArgs; PWSTR *ppArgv = CommandLineToArgvW(GetCommandLineW(), &nNumArgs);

// 使用参数 if (*ppArgv[1] == L'x') {    ... } // 释放内存块(那个函数是在内部分配内存块的) HeapFree(GetProcessHeap(), 0, ppArgv);

四. 进程的环境变量 环境变量块分配于进程的地址空间中, 形式如下: =::=::/ ... VarName1=VarValue1/0 VarName2=VarValue2/0 VarName3=VarValue3/0 ... VarNameX=VarValueX/0 /0 注意=::=::/,有些字符串可能以=开始,所以这些字符不能用于环境变量中。 有两种方式可以读取这些环境变量: 1. 用GetEnvironmentStrings函数: void DumpEnvStrings() {    PTSTR pEnvBlock = GetEnvironmentStrings();

   // Parse the block with the following format:    //    =::=::/    //    =...    //    var=value/0

   //    ...    //    var=value/0/0    // Note that some other strings might begin with '='.    // Here is an example when the application is started from a network share.    //    [0] =::=::/    //    [1] =C:=C:/Windows/System32    //    [2] =ExitCode=00000000    //    TCHAR szName[MAX_PATH];    TCHAR szValue[MAX_PATH];    PTSTR pszCurrent = pEnvBlock;    HRESULT hr = S_OK;    PCTSTR pszPos = NULL;    int current = 0;

   while (pszCurrent != NULL) {       // 跳过无意义字符串如"=::=::/"       if (*pszCurrent != TEXT('=')) {          // 查找 '=' 分隔符          pszPos = _tcschr(pszCurrent, TEXT('='));

         // 现在指针指向值的第一个字符          pszPos++;

         // 复制变量名          size_t cbNameLength = // 不包含' ='             (size_t)pszPos - (size_t)pszCurrent - sizeof(TCHAR);          hr = StringCbCopyN(szName, MAX_PATH, pszCurrent, cbNameLength);          if (FAILED(hr)) {             break;          }

         // Copy the variable value with the last NULL character          // and allow truncation because this is for UI only.          hr = StringCchCopyN(szValue, MAX_PATH, pszPos, _tcslen(pszPos)+1);          if (SUCCEEDED(hr)) {             _tprintf(TEXT("[%u] %s=%s/r/n"), current, szName, szValue);          } else // something wrong happened; check for truncation.          if (hr == STRSAFE_E_INSUFFICIENT_BUFFER) {             _tprintf(TEXT("[%u] %s=%s.../r/n"), current, szName, szValue);          } else { // This should never occur.             _tprintf(                TEXT("[%u] %s=???/r/n"), current, szName                );             break;          }       } else {          _tprintf(TEXT("[%u] %s/r/n"), current, pszCurrent);       }

      // Next variable please.       current++;

      // Move to the end of the string.       while (*pszCurrent != TEXT('/0'))          pszCurrent++;       pszCurrent++;

      // Check if it was not the last string.       if (*pszCurrent == TEXT('/0'))          break;    };

   // 不要忘记了要释放内存    FreeEnvironmentStrings(pEnvBlock); }

第二种获取环境变量的方法只用于CUI程序,它是通过main函数的第三个参数传入的。 void DumpEnvVariables(PTSTR pEnvBlock[]) {    int current = 0;    PTSTR* pElement = (PTSTR*)pEnvBlock;    PTSTR pCurrent = NULL;    while (pElement != NULL) {       pCurrent =

      if (pCurrent == NULL) {          // 没有环境变量了          pElement = NULL;       } else {          _tprintf(TEXT("[%u] %s/r/n"), current, pCurrent);          current++;          pElement++;       }    } } 在环境变量的这种表示中,可以预见它是把空格计算在内的。例如下面是两个环境变量: XYZ =Home (在XYZ后面有一空格,环境变量的名字就是XYZ加一空格) XYZ=Home 当用户登录系统时,系统从注册表的两个地方获取环境: HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager/Environment HKEY_CURRENT_USER/Environment 这些值可以在系统属性中进行修改。当然程序也可以通过注册表函数对这些值进行修改,一般这些修改后的值需要重新登录后才会对所有程序产生影响,但有些程序可以通过接收WM_SETTINGCHANGE消息立即获取新值,如Explorer, 任务管理器和控制面板等。这种你如果更新了这些值,就可以通过下面的方式对这些程序进行通知: SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) TEXT("Environment"));

如果您的程序还需要获取环境变量的功能,现在可以使用新函数:GetEnvironmentVariable 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);       _tprintf(TEXT("%s=%s/n"), pszVariableName, pszValue);       free(pszValue);    } else {       _tprintf(TEXT("'%s'=<unknown value>/n"), pszVariableName);    } }

如果在环境变量中包含一些其它的环境变量值,如%USERPROFILE%/Documents,这里USERPROFILE变量便是其它定义的,要获取完整的变量值(展开的变量值),可用ExpandEnvironmentStrings函数。如: DWORD chValue =    ExpandEnvironmentStrings(TEXT("PATH='%PATH%'"), NULL, 0); PTSTR pszBuffer = new TCHAR[chValue]; chValue = ExpandEnvironmentStrings(TEXT("PATH='%PATH%'"), pszBuffer, chValue); _tprintf(TEXT("%s/r/n"), pszBuffer); delete[] pszBuffer; 最后要添加,删除和修改一个环境变量值,可用SetEnvironmentVariable函数。

五. 进程的其它一些属性 1. 错误模式:每个进程都关联一个标识,它指示系统如何报告错误。可用函数SetErrorMode进行设置。 2. 当前驱动器和目录:每个进程也都有自己的当前驱动器和目录,它由系统跟踪,下面API可获取或设置这两个值  GetCurrentDirectory和SetCurrentDirectory 3. 进程当前目录:系统通过环境变量跟踪进程的每个驱动器的当前目录。如 =C:=C:/Utility/Bin =D:=D:/Program Files 4. 系统版本:原来Microsoft用GetVersion来获取版本串,后来改成GetVersionEx。现在前者几乎不用。 现在是检测系统是否是Vista的代码: // 准备OSVERSIONINFOEX结构 OSVERSIONINFOEX osver = { 0 }; osver.dwOSVersionInfoSize = sizeof(osver); osver.dwMajorVersion = 6; osver.dwMinorVersion = 0; osver.dwPlatformId = VER_PLATFORM_WIN32_NT;

//准备条件掩码 DWORDLONG dwlConditionMask = 0;// 必须初始化成0 VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL); VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL); VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL);

//进行版本测试 if (VerifyVersionInfo(&osver, VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID,    dwlConditionMask)) {    //该版本是Vista } else {    //该系统不是Vista系统 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值