4.1 编写第一个Windows应用程序
(1)进程的组成:(两个部分)
①进程也是一个内核对象(即进程内核对象),操作系统用它来管理进程,也是系统保存进程统计信息的地方。
②进程是一个地址空间,包含可执行文件或DLL模块的代码和数据,还包含动态内存分配,如线程堆栈或堆的分配。
(2)应用程序类型和相应的入口点函数
应用程序类型 |
C\C++入口点函数 |
嵌入可执行文件的 启动函数 |
链接器开关 |
处理ANSI字符(串)的GUI程序 |
_tWinMain(WinMain) |
WinMainCRTStartup |
/SUBSYSTEM:WINDOWS |
处理Unicode字符(串)的GUI程序 |
_tWinMain(wWinMain) |
wWinMainCRTStartup |
|
处理ANSI字符(串)的CUI程序 |
_tmain(Main) |
mainCRTStartup |
/SUBSYSTEM:CONSOLE |
处理Unicode字符(串)的CUI程序 |
_tmain(Wmain) |
wmainCRTStartup |
★两者界线是模糊的,即可以创建向控制台输出文字的GUI或能显示GUI的控制台应用程序。
(3)C/C++嵌入的启动函数
①C/C++应用程序中,进程启动过程wWinMainCRTStartup()→_tmainCRTStartup()→wWinMain()。由此可见操作系统并不调用我们写的入口点函数(如_tWinMain),而是调用C/C++运行期的启动函数(如wWinMainCRTStartup)。
②wWinMainCRTStartup的作用:——启动函数要进行一些额外的操作:
A、检索指向新进程的完整命令行的指针。
B、检索指向新进程的环境变量的指针。
C、对C/C + +运行期的全局变量进行初始化。如果包含了Stdlib.h 文件,代码就能访问这些变量。(如__environ、__argv等,见课本第69页,表4-2)
D、对C 运行期内存单元分配函数(malloc 和calloc)和其他低层输入/输出程序使用的堆进行初始化。这样就可以调用malloc和free之类的C库函数。
E、为所有全局和静态C++类对象调用构造函数。以确保已经声明的任何C++全局对象和静态对象能够在代码执行以前正确地创建。
F、如果是GUI程序,这里还将调用GetStartupinfo(&StartupInfo)函数,以获取一个进程的启动信息。
GetStartupInfo(&StartupInfo);
Int nMainRetVal= WinMain((HINSTANCE)&__ImageBase,NULL,pszCommandLine,
(StartupInfo.dwFlags &STARTF_USESHOWWINDOW)?StartupInfo.wShowWindow:SW_SHWODEFAULT);
其中__ImageBase是链接器定义的伪变量,表示映射到内存地位置。
G、调用我们写的入口函数(如WinMain)等函数,该入口函数返回后,嵌入的启动函数会调用C运行库exit函数,该函数将调用由_onexit添加的回调函数(调用顺序与添加顺序相反),然后清除全局对象和静态变量,最后调用系统ExitProcess函数,退出进程。
(4)如果链接器选项的“入口点”不设置的话(默认情况),C/C++程序入口点是被嵌入到可执行文件的WinMainCRTStartup启动函数(注意,不是WinMain)。如果设定入口点为我们指定的函数,这时不再嵌入那些启动函数。因此,所有的初始化和退出时清理全局变量的操作也得由我们自己来完成。(这对于纯API应用程序来说,有很好用的)
【OnExit程序】WinMainCRTStartup的执行流程
#include <tchar.h> #include <Windows.h> #include <strsafe.h> #define GRS_USEPRINTF() TCHAR pBuf[1024]={} //可变参数...与可变宏__VA_ARGS__ //宏定义中参数列表的最后一个参数为省略号(也就是三个点)。 //预定义宏__VA_ARGS__,当宏替换时,用来替换省略号所代表的字符串 #define GRS_PRINTF(...) \ StringCchPrintf(pBuf, 1024, __VA_ARGS__); \ WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), pBuf, lstrlen(pBuf), NULL, NULL); int fn1(void); int fn2(void); int fn3(void); int fn4(void); //注意以下创建的是GUI应用程序,但却使用控制台来输出! int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR pszCmdLine, int nShowCmd) { AllocConsole(); //为进程分配一个新的控制台(注意一个进程只可以拥用一个控制台的关联) GRS_USEPRINTF(); //因为WinMainCRTStartup函数会调用WinMain函数,但WinMain执行完毕后, //WinMainCRTStartup会继续调用由_onexit注册的回调函数且调用顺序与 //注册函数的顺序相反,所以当WinMain退出后,fn4、fn3、fn2、fn1会依次被调用。 //但注意,这4个函数只有在退出WinMain后才被调用。 _onexit(fn1); _onexit(fn2); _onexit(fn3); _onexit(fn4); GRS_PRINTF(_T("注意哦,我是第1个被输出的语句!\n")); GRS_PRINTF(_T("回调函数的注册顺序:fn1->fn2->fn3->fn4\n")); GRS_PRINTF(_T("回调函数的执行顺序:")); return 0; //注意,该函数退出后,仍会执行退出回调函数fnX等函数 } int fn1() { GRS_USEPRINTF(); GRS_PRINTF(_T("->fn1\n")); _tsystem(_T("PAUSE")); FreeConsole(); return 0; } int fn2() { GRS_USEPRINTF(); GRS_PRINTF(_T("->fn2")); return 0; } int fn3() { GRS_USEPRINTF(); GRS_PRINTF(_T("->fn3")); return 0; } int fn4() { GRS_USEPRINTF(); GRS_PRINTF(_T("fn4")); return 0; }
4.1.1 进程实例句柄
(1)GetModuleHandle函数——获取可执行文件或DLL文件被加载到进程地址空间的位置
参数 |
描述 |
LPCTSTR lpModuleName |
为NULL,获得主调进程的可执行文件的基地址,注意在DLL内部用这参数获得到是仍是宿主进程的基地址,而不是DLL模块的基地址! 不为NULL,如GetModuleHandle(TEXT("kernel32.dll")),将获取Kernel32模块的基地址。(注意,这是在DLL外部调用的,与上面不同!) |
返回值 |
成功——返回模块的基地址 失败——返回NULL,可以GetLastError获取错误代码信息。 |
两大特征 |
①只检查主调进程的地址空间,如果主调进程没有使用任何通话对话框函数,则GetModuleHandle(TEXT("ComDlg32.dll"))将返回NULL,即使其他进程己经将该库加载到内存里。 ②如果传递NULL参数,将返回可执行文件的基地址,即使在一个DLL文件内部调用,返回值仍然是可执行文件的基地址,而非DLL文件的基地址! |
(2)GetModuleHandleEx函数
参数 |
描述 |
DWORD dwFlags |
①为0,则当调用该函数时,模块的引用计数自动增加,调用者在使用完模块句柄后,必须调用一次FreeLibrary。 ②如果是GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,则同GetModuleHandle相同,不增加引用计数 ③如果是GET_MODULE_HANDLE_EX_FLAG_PIN,则模块一直映射在调用该函数的进程中,直到该进程结束,不管调用多少次FreeLibrary ④如果是GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,则lpModuleName是模块中的一个地址。 |
LPCTSTR lpModuleName |
与GetModuleHandle参数一样 |
HMODULE *phModule |
存储要找的句柄 |
返回值 |
成功——返回非0 失败——返回0,可以GetLastError获取错误代码信息。 |
备注 |
_ImageBase、GetModuleHandleEx与GetModuleHandle不同,会根据代码放置的模块,来决定是获得可执行文件或DLL库的基地址 |
注意:进程的实例句柄,可以在“链接器”→“高级”→“基址”中指定(如0x00400000),同时 “随机基址”设为“否”
【DumpModule程序】获取进程与DLL库的基地址
//动态链接库
//DumpModule.h
#pragma once; #ifdef _cplusplus #ifdef API_EXPORT #define EXPORT extern "C" __declspec(dllexport) //当头文件供动态库本身使用时 #else #define EXPORT extern "C" __declspec(dllimport) //当头文件供调用库的程序使用时 #endif #else #ifdef API_EXPORT #define EXPORT __declspec(dllexport) //当头文件供动态库本身使用时 #else #define EXPORT __declspec(dllimport) //当头文件供调用库的程序使用时 #endif #endif EXPORT void DumpModule();
//DumpDll.c
#define API_EXPORT #include <windows.h> #include "DumpModule.h" #include <stdio.h> extern const IMAGE_DOS_HEADER __ImageBase; HINSTANCE hDll; //入口和退出点 int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) { hDll = hInstance; return TRUE; } //在DLL中用两种不同的方法来获取可执行程序及DLL库基地址 //注意两点: //1、无论GetModuleHandle放在DLL中,还是可执行文件中,获取的都是可执行文件的基地址 //2、__ImageBase、GetMoudleHandleEx,会根据代码放置位置,也决定是获得可执行文件 //或DLL库的基地址。 EXPORT void DumpModule() { //先获取可执行文件的基地址 printf("可执行文件的基地址:"); //利用GetModuleHandle来获取 HMODULE hModule = GetModuleHandle(NULL); printf("GetModuleHandle(NULL) = 0x%X\r\n\r\n", hModule); printf("利用3种方法获取文件或(DLL库)的基地址,\r\n"); printf("因以下代码在DLL中调用,所以获取的是DLL的基地址:\r\n"); //先获取DLL或被加载的基地址 printf("方法1:通过DllMain参数获取的基地址 = 0x%X\r\n",hDll); //方法2:利用链接器的伪变量__ImageBase来获取 printf("方法2:伪变量__ImageBase = 0x%X\r\n", (HINSTANCE)&__ImageBase); //方法3:利用GetModuleEx函数来获取 hModule = NULL; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCTSTR)DumpModule, &hModule); //将DumpModule自身的地址传进去 printf("方法3:GetModuleHandleEx =0x%X\r\n", hModule); }
//测试程序
#include <windows.h> #include "..\\4_DumpModuleDll\\DumpModule.h" #include <tchar.h> #pragma comment(lib,"..\\..\\Debug\\4_DumpModuleDll.lib") int _tmain(int arc, TCHAR* argv[]) { DumpModule(); return 0; }
4.1.4 进程环境变量
(1)环境变量的格式
=::=::\... //不能作为环境变量使用,与进程当前目录有关
varName1=VarValue1\0 //注意等号前、后的空格也算是变量名或值的一部分。
varName2=varValue2\0
varName3=varValue3\0
varNameX=varValueX\0
\0 //字符串的最后以\0结束,含前面的字符,最后共有2个\0
(2)GetEnvironmentStrings(获取完整的环境块)和FreeEnvironmentStrings释放函数
(3)CUI程序中入口参数TCHAR* env[]指向环境块的字符串指针数组,但以等号开头的无无效字符串己被移除且数组最后一个元素为NULL。
(4)获取或设置环境变量的值
环境变量 |
注册表项 |
GetEnvironmentVariable |
获取环境变量的值或判断环境变量是否存在 |
SetEnvironmentVariable |
添加、修改环境 删除环境变量:当第2个参数pszValue=NULL时,表示删除 |
(5)扩展环境变量字符串,并使用当前用户定义的值来替换这些环境变量字符串
ExpandEnvironmentStrings函数 |
|
参数 |
描述 |
PCTSTR pszSrc |
指向包含“可替换环境变量字符串”的指针 |
PTSTR pszDst |
用于接收扩展字符串的一个缓冲区地址 |
DWORD chSize |
上述缓冲区的可容纳的最大字符数 |
返回值 |
保存到缓冲区实际的字符数。如果chSize小于此值,%%变量不会扩展,通常用两次调用ExpandEnvironmentStrings(第1次将pszDst设为NULL,chSize设为0,返回实际的字符数,第2次根据返回值分配缓冲区大小,chSize填入第1次的返回值) |
(6)注册表中的环境变量列表
环境变量 |
注册表项 |
系统环境变量 |
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment |
当前用户环境变量 |
HKEY_CURRENT_USER\Environment |
(7)与环境变量块有关的消息:——当更新注册表中的环境变量,希望通知应用程序时,可发送:SendMessage(HWND_BROADCAST,WM_SETTINTCHANGE,0,(LPARAM)TEXT("Enviroment"));
(8)通常子进程会继承父进程的环境块,但父进程可以控制哪些环境变量允许被继承。但子进程不会继承父进程的当前目录,同时子进程的环境块是父进程的副本,换言之在子进程中添加、删除或修改变量并不影响父进程的环境块。
【Environ程序】获取环境变量示例
ExpandEnvironmentStrings、GetEnvironmentVariable函数
#include <windows.h> #include <tchar.h> #include <strsafe.h> //利用控制台程序下的环境变量参数来显示环境变量。 //注意:pEnvBlock是个数组,元素类型是指向TCHAR字符串的指针。 void DumpEnvVariables(PTSTR pEnvBlock[]) { int current = 0; PTSTR* pElement = (PTSTR*)pEnvBlock; PTSTR pCurrent = NULL; while (pElement!=NULL) { pCurrent = (PTSTR)(*pElement); if (pCurrent ==NULL) { pElement = NULL; //没有更多的环境变量了 } else { _tprintf(TEXT("[%u] %s\r\n"), current, pCurrent); current++; pElement++; } } } //利用GetEnvironmentStrings函数来获得环境变量 void DumpEnvStrings() { PTSTR pEnvBlock = GetEnvironmentStrings(); //解析环境变量块的格式 // =::=::\ // =... // varName=varValue\0 // ... // varNameX=varValue\0\0 //注意最后一个变量后面有两个的\0 //注意:某些字符串是以'='开始的,下列是从网络共享中启动应用程序的例子 //[0] =::=::\ //[1] =C:=C:\Windows\System32 //[2] =ExitCode=00000000 TCHAR szName[MAX_PATH]; TCHAR szValue[MAX_PATH]; PTSTR pszCurrent = pEnvBlock; PCTSTR pszPos = NULL; HRESULT hr = S_OK; int current = 0; while (pszCurrent != NULL) { // 跳过如下格式的无意义字符串 // "=::=::\" if (*pszCurrent !=TEXT('=')) { //查找'='分隔符,即用来分隔变量名和值的等号 pszPos = _tcschr(pszCurrent, TEXT('=')); //pszPos指向等号 pszPos++; //指向“值”的第1个字符 //拷贝变量名 size_t cbNameLength = //长度不包含等号 (size_t)pszPos - (size_t)pszCurrent - sizeof(TCHAR); hr = StringCbCopyN(szName, MAX_PATH, pszCurrent, cbNameLength); //注意:字节函数 if (FAILED(hr)) break; //拷贝变量的值(最后一个字符带NULL),同时允许截断字符串 hr = StringCchCopyN(szValue, MAX_PATH, pszPos, _tcslen(pszPos) + 1); //字符函数 if (SUCCEEDED(hr)) { _tprintf(TEXT("[%u] %s=%s\r\n"), current, szName, szValue); } //发生某种错误,可能被截断 else if (hr == STRSAFE_E_INSUFFICIENT_BUFFER) //指定的缓冲区太小时 { _tprintf(TEXT("[%u] %s=%s...\r\n"), current, szName, szValue); } else { //这项可能永远不会发生 _tprintf(TEXT("[%u] %s=???\r\n"), current, szName); break; } } else { _tprintf(TEXT("[%u] %s\r\n"), current, pszCurrent); } //下一个变量 current++; //将pszCurrent移到下一个变量的位置 while (*pszCurrent != TEXT('\0')) pszCurrent++; pszCurrent++; //判断是否是最后一个字符 if (*pszCurrent == TEXT('\0')) break; } FreeEnvironmentStrings(pEnvBlock);//不要忘了释放环境变量内存 } //获得环境变量示例 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); } } //ExpandEnvironmentStrings示例 void ExpandDemo(PTSTR pszExpandString) { //第1次调用获取所示缓冲区大小(字符数) DWORD chValue = ExpandEnvironmentStrings(pszExpandString, NULL, 0); PTSTR pszBuffer = new TCHAR[chValue]; //第2次调用,将指定的“可替换字符串”替换为相应的值 chValue = ExpandEnvironmentStrings(pszExpandString, pszBuffer, chValue); _tprintf(TEXT("%s\r\n"), pszBuffer); delete[] pszBuffer; } //使用带参env[]参数的main函数 int _tmain(int argc,TCHAR* argv[],TCHAR* env[]) { //DumpEnvVariables(env); //利用env[]显示环境变量 DumpEnvStrings(); //利用GetEnvironmentStrings //ExpandDemo(TEXT("PATH='%PATH%'")); //_tprintf(TEXT("\r\n")); //PrintEnvironmentVariable(TEXT("ProgramFiles")); return 0; }
4.1.5 进程的关联性(亲缘性)
(1)可以通过调用SetProcessAffinityMask将进程中线程的调度运行限定在多核系统的某几个特定的CPU上(CPU子集)
(2)此函数的第二个参数dwProcessAffinityMask是一个位向量,每一个二进制位对应表示相应序号的CPU,当该位为1时表示使用这个序号的CPU,否则就是不使用这个CPU。
(3)CPU序号从0~31,对应无符号32位值的相应位。
(4)通过调用GetSystemInfo方法可以得到系统中CPU的个数(多核CPU被认为是独立的CPU)。在Vista以上系统中通过调用GetLogicalProcessorInformation方法可以得到关于CPU的详细信息,包括NUMA、SMT、Catch等信息
(5)子进程继承了父进程的关联性(第7章会讨论)
【GetLogicCPU程序】——调用GetLogicalProcessorInformation获取CPU信息的例子
#include <windows.h> #include <tchar.h> #include <strsafe.h> #define GRS_USEPRINTF() TCHAR pBuf[1024]={} #define GRS_PRINTF(...) \ StringCchPrintf(pBuf, 1024, __VA_ARGS__);\ WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), pBuf, lstrlen(pBuf), NULL, NULL); #define GRS_ALLOC(cbSize) HeapAlloc(GetProcessHeap(),0,cbSize); #define GRS_CALLOC(cbSize) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,cbSize); #define GRS_SAFEFREE(p) if (NULL !=p){HeapFree(GetProcessHeap(),0,p);p=NULL;} //GetLogicalProcessorInformation函数 /* BOOL WINAPI GetLogicalProcessorInformation( _Out_ PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer, _Inout_ PDWORD ReturnLength ); */ typedef BOOL (WINAPI * LPFN_GLPI)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); DWORD CountBits(ULONG_PTR bitMask) { DWORD LSHIFT = sizeof(ULONG_PTR)* 8 - 1; //32位计算机中指针长度为4,64位中为8字节 DWORD bitSetCount = 0; DWORD bitTest = 1 << LSHIFT; for (DWORD i = 0; i <=LSHIFT;++i) { bitSetCount += ((bitMask&bitTest) ? 1 : 0); bitTest >>= 1; } return bitSetCount; } int _tmain() { GRS_USEPRINTF(); LPFN_GLPI Glpi; Glpi = (LPFN_GLPI)GetProcAddress(GetModuleHandle(_T("kernel32")), "GetLogicalProcessorInformation"); if (NULL == Glpi) { GRS_PRINTF(_T("\n不支持GetLogicalProcessorInformation函数!")); return 1; } BOOL bDone = FALSE; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pBuffer = NULL; DWORD iRetLength = 0; while (!bDone) { DWORD rc = Glpi(pBuffer, &iRetLength); if (FALSE ==rc) { if (GetLastError()== ERROR_INSUFFICIENT_BUFFER) { GRS_SAFEFREE(pBuffer); pBuffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)GRS_CALLOC(iRetLength); if (NULL == pBuffer) { GRS_PRINTF(_T("\n错误:分配内存失败!")); return 2; } } else { GRS_PRINTF(_T("\n错误:%d"), GetLastError()); return 3; } } else bDone = TRUE; } DWORD procCoreCount = 0; DWORD procCacheCount = 0; DWORD procNumaCount = 0; DWORD procPackageCount = 0; DWORD byteOffset = 0; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = pBuffer; while (byteOffset < iRetLength) { switch (ptr->Relationship) { case RelationNumaNode: procNumaCount++; break; case RelationProcessorCore: //单个CPU的核数 if (ptr->ProcessorCore.Flags) { //超线程或SMT启用时,处理器在同一核上运行 procCoreCount++; } else { //处理器有不同的核 procCoreCount += CountBits(ptr->ProcessorMask); } break; case RelationCache: //每个缓存中,是一个CACHE_DESCRIPTOR结构 procCacheCount++; break; case RelationProcessorPackage: //CPU数量 procPackageCount++; break; default: GRS_PRINTF(_T("\n错误:不支持LOGICAL_PROCESSOR_RELATIONSHIP值.\n")); break; } byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ptr++; } GRS_PRINTF(_T("GetLogicalProcessorInformation结果:\n")); GRS_PRINTF(_T("NUMA节点数量:\t%d\n"),procNumaCount); GRS_PRINTF(_T("核的数量:\t%d\n"), procCoreCount); GRS_PRINTF(_T("CPU的数量:\t%d\n"), procPackageCount); GRS_PRINTF(_T("缓存的数量:\t%d\n"), procCacheCount); GRS_SAFEFREE(pBuffer); return 0; }
4.1.6 进程的错误模式
(1)调用SetErrorMode告诉系统如何处理错误(如磁盘介质、未处理异常、文件查找以及数据对齐等错误。
(2)SetErrorMode函数的参数——可根据下列标志按位或运算
标志 |
描述 |
SEM_FAILCRITICALERRORS |
系统不显示严重错误处理程序(critical-error-handler)消息框,而是将错误返回主调给进程。 |
SEM_NOGPFAULTERRORBOX |
系统不显示常规保护错误(general-protection-fault)消息框。此标志只应该由调试程序设置; 该调试程序用一个异常处理程序来自行处理常规保护错误。 |
SEM_NOOPENFILEERRORBOX |
系统查找文件失败时,不显示消息框 |
SEM_NOALIGNMENTFAULTEXCEPT |
系统自动修复内存对齐错误,并使应用程序看不到这些错误。此标志对x86/x64处理器无效 |
(3)默认情况下,子进程会继承父进程的错误模式标志。在创建子进程时,可以在CreateProcess中指定CREATE_DEFAULT_ERROR_MODE来阻止子进程继承其错误模式
4.1.7 进程当前所在的驱动器和目录(Current Drive and Directory),即工作目录
(1)系统内部以进程为单位,跟踪记录着一个进程的当前驱动器及该驱动器下的当前目录。
(2)当某个线程改变了当前驱动器或目录,对于该进程所有的线程来说,此信息都被更改了。
(3)GetCurrentDirectory、SetCurrentDirectory获取和设置当前驱动器和目录
①GetCurrentDirectory函数
参数 |
描述 |
DWORD cchCurDir |
以下缓冲区能容纳的最大字符数(一般用MAX_PATH) |
PTSTR pszCurDir |
用来接收字符串的缓冲区 |
返回值 |
如果提供cchCurdir不够大或为pszCurDir为NULL,则返回保存此文件夹所需要的字符改写(含末尾的\0) 如果调用成功,返回字符串的实际长度(不含\0) |
②SetCurrentDirectory只会设置当前工作目录,不会同时增加到环境块中。可以使用C运行库函数_chdir来设置当前工作目录,_chdir内部调用了SetCurrentDirectory,同时还调用了SetEnvironmentVariable来添加或修改环境变量。
4.1.8 进程当前目录(Current Directories,注意后面加s,表示每个驱动器下都有一个当前目录,如果没有设置,默认为该驱动器的根目录)
(1)系统跟踪记录着进程的当前驱动器和目录,但并没记录每个驱动器的当前目录。但可以通过环境变量来提供这种支持。设硬盘上有3个盘符,分别为C、D、E,环境变量如下
=C:=C:\Utiltity\Bin
=D:=D:\Program Files
则表示进程C在驱动器的当前目录为\Utility\Bin,在D驱动器的当前目录为\Program Files。而E没被记录在环境变量中,所以当前目录默认为E的根目录。
(2)进程在查找文件时的顺序(如果没指定完整的路径名时)
①查找当前工作目录,即系统内部跟踪记录的、可用GetCurrentDirectory获取的那个。
②如果没有,则从环境块中查找指定驱动器号下的当前目录。如果找到不该驱动器号,则以盘符的根目录作为当前目录来查找文件。
(3)Windows文件函数(如CreateFile)不会添加或更改驱动器号的环境变量。(注意,SetCurrentDirectory是可以去改变的哦)。
(4)子进程不会自动继承父进程的当前目录(current directories),而是默认为各驱动器的根目录。
(5)获得指定驱动器当前完整的目录GetFullPathName
参数 |
描述 |
LPCTSTR lpFileName |
驱动器名或文件名:如TEXT("C:")或TEXT("XXX.exe"),文件名后缀默认为.exe |
DWORD nBufferLength |
接收缓冲区大小(以字符数为单位),一般为MAX_PATH |
LPTSTR lpBuffer |
接收缓冲区 |
LPTSTR *lpFilePart |
指向上述缓冲区接收到的完整路径(含文件名)中文件名开始的地址指针 |
返回值 |
成功:返回实际接收到的字符数(不含\0) 失败:0,可GetLastError获得更多信息 |
如:DWORD cchLength = GetFullPathName(TEXT("C:"),MAX_PATH,szCurDir,NULL);
4.1.9 系统版本
(1)GetVersionEx函数和OSVERSIONINFOEX结构体
字段 |
描述 |
dwOSVersionInfoSize |
结构体大小,sizeof(OSVERSIONINFOEX)或sizeof(OSVERSIONINFO) |
dwMajorVersion |
主版本号 |
dwMinorVersion |
次版本号 |
dwBuildNumber |
当前系统的构建版本号 |
dwPlatformID |
当前系统支持的套件(suite) VER_PLATFORM_WIN32s:Win32s VER_PLATFROM_WIN32_WINDOWS:Win95或Win98 VER_PLATFORM_WIN32_NT:Win NT、Win2000、WinXp、Win Server2003以及Win Vista |
szCSDVersion |
此字段包含额外的文本,提供了与己安的操作系统有关的更多信息 |
wServicePackMajor |
最新安装的Service Pack的主版本号 |
wServicePackMinor |
最新安装的Service Pack的次版本号 |
wSuiteMask |
当前系统上可用的Suite(s),如 VER_SUITE_SINGLEUSERTS:每个用户一个终端服务会话 VER_SUITE_PERSONAL:用来区别Vista的Home或Professional版本 |
wProductType |
指出安装的是以下操作系统产品中的哪一个:VER_NT_WORKSTATION、 VER_NT_SERVER、VER_NT_DOMAIN_CONTROLLER |
wReserved |
保留,供将来使用 |
(2)比较主机系统的版本与应用程序要求的版本:VerifyVersionInfo函数
参数 |
描述 |
POSVERSIONINFOEX lpVersionInfo |
指向OSVERSIONINFOEX结构体,要初始化结构体大小字段及其他要检查的字段,如要检查操作系统是否是Vista时,要将dwMajorVersion指定为6,dwMinorVersion指定为0,dwPaltformID设为VER_PLATFORM_WIN32_NT。 |
DWORD dwTypeMask |
指出了我们要初始化哪些成员,可以按以将以下标志按位或运算: VER_MINORVERSION、VER_MAJORVERSION、 VER_BUILDNUMBER、VER_PLATFORMID、 VER_SERVICEPACKMINOR、 VER_SERVICEPACKMAJOR、 VER_SUITENAME、VER_PRODUCT_TYPE |
DWORDLONG dwlConditionMask |
64位值,决定如何将系统版本信息与我们希望的版本信息进行比较,可以用VER_SET_CONDITION宏来组合出恰当的位组,如系统的主版本号是大于或小于或等于我们要求的版本号,还有次版本号、及其他字段的情况等。 |
返回值 |
非0:表示满足要求 0:不符合要求或调用函数不正确,可以调用GetLastError来进一步判断。如果返回ERROR_OLD_WIN_VERSION,表示函数调用是正确的,但不符合我们的要求。 |
★VER_SET_CONDITION宏
参数 |
描述 |
ULONGLONG dwlConditionMask |
64位的条件掩码变量,用来保存后面的dyTypeBitMask和dwConditionMask 位操作的结果 |
DWORD dwTypeBitMask |
指定要比较的是哪个成员,为了比较多个成员,必须多次调用VET_SET_CONDITION宏,一个成员调用一次,所有结果保存在dwlConditionMask变量中。 |
BYTE dwConditionMask |
要进行何种比较: VER_EQUAL:等于;VER_GREATER:大于;VER_LESS:小于 VER_GREATER_EQUAL:大于等于;VER_LESS_EQUAL:小于等于 注意:在比较VER_SUITENAME信息时,要用 VER_AND:所有套件产品都必须安装 VER_OR:至少安装了其中的一个套件产品 |
【附表】Windows版本号参考
Operating System |
Version |
PlatformID |
Windows 8.1 |
6.3 |
VER_PLATFORM_WIN32_NT |
Windows 8 |
6.2 |
VER_PLATFORM_WIN32_NT |
Windows 7 |
6.1 |
VER_PLATFORM_WIN32_NT |
Windows Server 2008 R2 |
6.1 |
VER_PLATFORM_WIN32_NT |
Windows Server 2008 |
6.0 |
VER_PLATFORM_WIN32_NT |
Windows Vista |
6.0 |
VER_PLATFORM_WIN32_NT |
Windows Server 2003 R2 |
5.2 |
VER_PLATFORM_WIN32_NT |
Windows Server 2003 |
5.2 |
VER_PLATFORM_WIN32_NT |
Windows XP 64-Bit Edition |
5.2 |
VER_PLATFORM_WIN32_NT |
Windows XP |
5.1 |
VER_PLATFORM_WIN32_NT |
Windows 2000 |
5.0 |
VER_PLATFORM_WIN32_NT |
Windows NT 4.0 |
4.0 |
VER_PLATFORM_WIN32_NT |
Windows NT 3.51 |
3.51 ? |
VER_PLATFORM_WIN32_NT |
Windows Millennium Edition |
4.90 |
VER_PLATFORM_WIN32_WINDOWS (=1) |
Windows 98 |
4.10 |
VER_PLATFORM_WIN32_WINDOWS |
Windows 95 |
4.0 |
VER_PLATFORM_WIN32_WINDOWS |
Windows 3.1 |
3.1 ? |
VER_PLATFORM_WIN32s (=0) |
(3)建议用来替代GetVersion和GetVersionEx的API(须包含versionhelpers.h)
参数 |
描述 |
IsWindowsXPOrGreater |
版本号是高于或等于WinXP? |
IsWindowsXPSP1OrGreater |
版本号是高于或等于WinXP(SP1)? |
IsWindowsXPSP2OrGreater |
版本号是高于或等于WinXP(SP2)? |
IsWindowsXPSP3OrGreater |
版本号是高于或等于WinXP(SP3)? |
IsWindowsVistaOrGreater |
版本号是高于或等于Win Vista? |
IsWindowsVistaSP1OrGreater |
版本号是高于或等于Win Vista(sp1)? |
IsWindowsVistaSP2OrGreater |
版本号是高于或等于Win Vista(sp2)? |
IsWindows7OrGreater |
版本号是高于或等于Win 7? |
IsWindows7SP1OrGreater |
版本号是高于或等于Win 7(sp1)? |
IsWindows8OrGreater |
版本号是高于或等于Win 8? |
IsWindows8Point1OrGreater |
版本号是高于或等于Win 8.1? |
IsWindows10OrGreater |
版本号是高于或等于Win 10? |
IsWindowsServer |
是否是服务器版本 |
IsWindowsVersionOrGreater |
当前系统的版本号是否匹配或高于目前编译器己知版本 |
【VersionInfo程序】测试主机系统是不是Win8.1版本
#include <windows.h> #include <stdio.h> #include <VersionHelpers.h> void IsWindows8() { //初始化OSVERSIONINFOEX结构体,并设置为Win8.1 OSVERSIONINFOEX osver = { 0 }; osver.dwOSVersionInfoSize = sizeof(osver); osver.dwMajorVersion = 6; osver.dwMinorVersion = 3; osver.dwPlatformId = VER_PLATFORM_WIN32_NT; //准备条件掩码变量 DWORDLONG dwlConditionMask = 0; //多次调用VER_SET_CONDITION宏来组合条件掩码变量 VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL); //主版本号 VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL); //次版本号 VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL); //平台ID //版本测试 if (VerifyVersionInfo(&osver,VER_MAJORVERSION| VER_MINORVERSION|VER_PLATFORMID, dwlConditionMask)) { printf("当前操作系统正好是Win8.1!\n"); } else printf("当前操作系统不是Win8.1!\n"); } int main() { //利用VerifyVersionInfo函数判断 printf("利用VerifyVersionInfo函数判断:\n"); IsWindows8(); //利用VersionHelpers.h中的函数判断 printf("\n利用VersionHelpers.h中的函数:\n"); if (IsWindows8Point1OrGreater()) { printf("当前操作系统等于或高于Win8.1版本!\n"); } else { printf("当前操作系统低于Win8.1版本!\n"); } return 0; }