其他语言

位置:IT落伍者 >> 其他语言 >> 浏览文章

Windows via C/C++ —进程(一)


发布日期:2022年08月03日
 
Windows via C/C++ —进程(一)

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

一个进程要能完成某些功能它必须要有一个线程运行该线程负责执行包含在进程地址空间听代码一个进程可以包含多个线程它们可以同时运行每个线程都有自己的CPU寄存器组和自己的堆栈当一个进程创建时系统会自己创建它的第一个线程称为基本线程(primary thread)这个线程可以创建另外的线程如果进程地址空间中没有线程运行系统自动销毁进程及其地址空间 编写第一个Windows程序 Windows程序分成GUI和CUI后者虽然也包含在窗口中但窗口只能包含文本如CMDEXE用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++对象在代码退出时被销毁这些函数WinMainCRTStartupwWinMainCRTStartupmainCRTStartup wmainCRTStartup等(加w的是宽字符版本)这四个函数在crtexec文件中可以找到这些启动函数完成如下功能

取得新进程的全部命令行参数的指针

取得新进程的环境变量的指针

初始化C/C++运行时全局变量你的代码如果包含StdLibh就可以访问这些变量如_osver _winmajor _winminor _winver __argc __argv __wargv _environ _wenviron _pgmptr _wpgmptr等

用C进行时函数malloct和calloc和底层I/O函数初始化堆

为所有全局和静态C++类对象调用构造函数当然这些初始化完成后便调用你的启动函数代码如下(UNICODE版本) GetStartupInfo(&StartupInfo); int nMainRetVal = WinMain((HINSTANCE)&__ImageBase NULL pszCommandLineAnsi (StartupInfodwFlags & STARTF_USESHOWWINDOW) ? StartupInfowShowWindow : SW_SHOWDEFAULT); int nMainRetVal = wmain(argc argv envp); 如果要在_tmain的入口访问环境变量则_tmain应写成 int _tmain(int argc TCHAR* argv[] TCHAR* env[])

当你的进入点函数退出时启动函数调用C运行时的exit函数它传递返回值能它并完成如下功能

调用由_onexit函数调用的任何函数

为所有全局和静态C++类对象调用析构函数

如果以DEBUG方式生成则C/C++运行时内存的任何洩漏都会由 _CrtDumpMemoryLeaks函数列出

调用操作系统的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链接器默认是x你可通过/BASE:address进行更改函数GetModuleHandle可取得一个可执行或DLL文件的装载句柄它用文件名作为参数如果参数是NULL则返回调用者的句柄但如果代码中DLL中运行要获取句柄有两种办法

使用链接器提供的__ImageBase伪变量(C运行时代码就以这种方式)

调用GetModuleHandleEx

下面是这些代码 #include<stdioh>

#include<windowsh>

extern C const IMAGE_DOS_HEADER __ImageBase;

void DumpModule()

{

HMODULE hModule = GetModuleHandle(NULL);

printf(用GetModuleHandle(NULL) = x%x\r\n hModule);

printf(用__ImageBase = x%x\r\n (HINSTANCE)&__ImageBase);

hModule = NULL;

GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS

(PCTSTR)DumpModule &hModule);

printf(用GetModuleHandleEx = x%x\r\n hModule);

}

int main(int argc TCHAR* argv[])

{

DumpModule();

return ;

}

上面代码可以看出不管在哪调用GetModuleHandle它总返回可执行文件的基址另外在生成的WinMain函数的第二个参数也是HINSTANCE它是指前一程序的实例句柄这个参数现在不应该使用

进程命令行当新进程创建时它会传递一个命令行它从来不会为空(至少有可执行文件名)当C运行时执行GUI程序时它是用GetCommandLine函数获取命令行参数 跳过可执行文件名把剩下的传递给pszCommandLine这块缓沖不应该写入任何东西要取得命令行参数可以使用CommandLineToArgvW函数它在Shelldll中声明于ShellAPIh调用方式如下

int nNumArgs; PWSTR *ppArgv = CommandLineToArgvW(GetCommandLineW() &nNumArgs);

// 使用参数 if (*ppArgv[] == Lx)

{

}

// 释放内存块(那个函数是在内部分配内存块的) HeapFree(GetProcessHeap() ppArgv);

进程的环境变量环境变量块分配于进程的地址空间中 形式如下 =::=::\ VarName=VarValue\ VarName=VarValue\ VarName=VarValue\ VarNameX=VarValueX\ \ 注意=::=::\有些字符串可能以=开始所以这些字符不能用于环境变量中有两种方式可以读取这些环境变量

用GetEnvironmentStrings函数

void DumpEnvStrings()

{

PTSTR pEnvBlock = GetEnvironmentStrings();

// Parse the block with the following format:

// =::=::\

// =

// var=value\

//

// var=value\\

// Note that some other strings might begin with =

// Here is an example when the application is started from a network share

// [] =::=::\

// [] =C:=C:\Windows\System

// [] =ExitCode=

// TCHAR szName[MAX_PATH];

TCHAR szValue[MAX_PATH];

PTSTR pszCurrent = pEnvBlock;

HRESULT hr = S_OK;

PCTSTR pszPos = NULL;

int current = ;

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)+);

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(\))

pszCurrent++;

pszCurrent++;

// Check if it was not the last string

if (*pszCurrent == TEXT(\))

break;

};

// 不要忘记了要释放内存

FreeEnvironmentStrings(pEnvBlock);

}

第二种获取环境变量的方法只用于CUI程序它是通过main函数的第三个参数传入的

void DumpEnvVariables(PTSTR pEnvBlock[])

{

int current = ;

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 (LPARAM) TEXT(Environment));

如果您的程序还需要获取环境变量的功能现在可以使用新函数 GetEnvironmentVariable void PrintEnvironmentVariable(PCTSTR pszVariableName)

{

PTSTR pszValue = NULL;

// 获取存储值所需要的缓沖区大小

DWORD dwResult = GetEnvironmentVariable(pszVariableName pszValue );

if (dwResult != )

{

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 ); PTSTR pszBuffer = new TCHAR[chValue]; chValue = ExpandEnvironmentStrings(TEXT(PATH=%PATH%) pszBuffer chValue); _tprintf(TEXT(%s\r\n) pszBuffer); delete[] pszBuffer; 最后要添加删除和修改一个环境变量值可用SetEnvironmentVariable函数

进程的其它一些属性

错误模式每个进程都关联一个标识它指示系统如何报告错误可用函数SetErrorMode进行设置

当前驱动器和目录每个进程也都有自己的当前驱动器和目录它由系统跟蹤下面API可获取或设置这两个值 GetCurrentDirectory和SetCurrentDirectory

进程当前目录系统通过环境变量跟蹤进程的每个驱动器的当前目录如 =C:=C:\Utility\Bin =D:=D:\Program Files

系统版本原来Microsoft用GetVersion来获取版本串后来改成GetVersionEx现在前者几乎不用现在是检测系统是否是Vista的代码

// 准备OSVERSIONINFOEX结构 OSVERSIONINFOEX osver = { };

osverdwOSVersionInfoSize = sizeof(osver);

osverdwMajorVersion = ;

osverdwMinorVersion = ;

osverdwPlatformId = VER_PLATFORM_WIN_NT;

//准备条件掩码 DWORDLONG dwlConditionMask = ;

// 必须初始化成 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系统

}

               

上一篇:实例解析C++/CLI之静态构造函数

下一篇:Visual C++开发数据库基础之ADO篇