第4章 进程(1)

4.1 编写第一个Windows应用程序

(1)进程的组成:(两个部分)

①进程也是一个内核对象(即进程内核对象),操作系统用它来管理进程,也是系统保存进程统计信息的地方。

②进程是一个地址空间,包含可执行文件或DLL模块的代码和数据,还包含动态内存分配,如线程堆栈或堆的分配。

(2)应用程序类型和相应的入口点函数

应用程序类型

CC++入口点函数

嵌入可执行文件的

启动函数

链接器开关

处理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个被输出的语句!
"));
    GRS_PRINTF(_T("回调函数的注册顺序:fn1->fn2->fn3->fn4
"));
    GRS_PRINTF(_T("回调函数的执行顺序:")); 
    return 0;    //注意,该函数退出后,仍会执行退出回调函数fnX等函数
}

int fn1()
{
    GRS_USEPRINTF();
    GRS_PRINTF(_T("->fn1
"));
    _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

", hModule);
    

    printf("利用3种方法获取文件或(DLL库)的基地址,
");
    printf("因以下代码在DLL中调用,所以获取的是DLL的基地址:
");
    
    //先获取DLL或被加载的基地址
    printf("方法1:通过DllMain参数获取的基地址 = 0x%X
",hDll);

    //方法2:利用链接器的伪变量__ImageBase来获取
    printf("方法2:伪变量__ImageBase = 0x%X
", (HINSTANCE)&__ImageBase);
    //方法3:利用GetModuleEx函数来获取
    hModule = NULL;
    GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
        (PCTSTR)DumpModule, &hModule);  //将DumpModule自身的地址传进去
    printf("方法3:GetModuleHandleEx =0x%X
", 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   //注意等号前、后的空格也算是变量名或值的一部分。

  varName2=varValue2

  varName3=varValue3

  varNameX=varValueX

                      //字符串的最后以结束,含前面的字符,最后共有2个

(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_MACHINESYSTEMCurrentControlSetControlSession ManagerEnvironment

当前用户环境变量

HKEY_CURRENT_USEREnvironment

(7)与环境变量块有关的消息:——当更新注册表中的环境变量,希望通知应用程序时,可发送:SendMessage(HWND_BROADCAST,WM_SETTINTCHANGE,0,(LPARAM)TEXT("Enviroment"));

(8)通常子进程会继承父进程的环境块,但父进程可以控制哪些环境变量允许被继承。但子进程不会继承父进程的当前目录,同时子进程的环境块是父进程的副本,换言之在子进程中添加、删除或修改变量并不影响父进程的环境块。

【Environ程序】获取环境变量示例

利用控制台程序main的env[]参数
 
 利用GetEnvironmentStrings函数

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
"), current, pCurrent);
            current++;
            pElement++;
        }
    }
}

//利用GetEnvironmentStrings函数来获得环境变量
void DumpEnvStrings()
{
    PTSTR pEnvBlock = GetEnvironmentStrings();

    //解析环境变量块的格式
    // =::=::
    // =...
    // varName=varValue
    // ...
    // varNameX=varValue  //注意最后一个变量后面有两个的
    
    //注意:某些字符串是以'='开始的,下列是从网络共享中启动应用程序的例子
    //[0] =::=::
    //[1] =C:=C:WindowsSystem32
    //[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
"), current, szName, szValue);
            }
            //发生某种错误,可能被截断
            else if (hr == STRSAFE_E_INSUFFICIENT_BUFFER) //指定的缓冲区太小时
            {
                _tprintf(TEXT("[%u] %s=%s...
"), current, szName, szValue);
            }
            else
            {   //这项可能永远不会发生
                _tprintf(TEXT("[%u] %s=???
"), current, szName);
                break;
            }
        }
        else
        {
            _tprintf(TEXT("[%u] %s
"), current, pszCurrent);
        }

        //下一个变量
        current++;
        //将pszCurrent移到下一个变量的位置
        while (*pszCurrent != TEXT(''))
            pszCurrent++;
        pszCurrent++;

        //判断是否是最后一个字符
        if (*pszCurrent == TEXT(''))
            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
"), pszVariableName, pszValue);
        free(pszValue);
    }
    else
    {
        _tprintf(TEXT("'%s'=<unknown value>
"), 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
"), pszBuffer);
    delete[] pszBuffer;
}

//使用带参env[]参数的main函数
int _tmain(int argc,TCHAR* argv[],TCHAR* env[])
{
    //DumpEnvVariables(env); //利用env[]显示环境变量
    DumpEnvStrings(); //利用GetEnvironmentStrings
    //ExpandDemo(TEXT("PATH='%PATH%'"));
    //_tprintf(TEXT("
"));
    //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("
不支持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("
错误:分配内存失败!"));
                    return 2;
                }
            }
            else
            {
                GRS_PRINTF(_T("
错误:%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("
错误:不支持LOGICAL_PROCESSOR_RELATIONSHIP值.
"));
            break;
        }
        byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
        ptr++;
    }

    GRS_PRINTF(_T("GetLogicalProcessorInformation结果:
"));
    GRS_PRINTF(_T("NUMA节点数量:	%d
"),procNumaCount);
    GRS_PRINTF(_T("核的数量:	%d
"), procCoreCount);
    GRS_PRINTF(_T("CPU的数量:	%d
"), procPackageCount);
    GRS_PRINTF(_T("缓存的数量:	%d
"), 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,则返回保存此文件夹所需要的字符改写(含末尾的)

如果调用成功,返回字符串的实际长度(不含)

②SetCurrentDirectory只会设置当前工作目录,不会同时增加到环境块中。可以使用C运行库函数_chdir来设置当前工作目录,_chdir内部调用了SetCurrentDirectory,同时还调用了SetEnvironmentVariable来添加或修改环境变量。

4.1.8 进程当前目录(Current Directories,注意后面加s,表示每个驱动器下都有一个当前目录,如果没有设置,默认为该驱动器的根目录)

(1)系统跟踪记录着进程的当前驱动器和目录,但并没记录每个驱动器的当前目录。但可以通过环境变量来提供这种支持。设硬盘上有3个盘符,分别为C、D、E,环境变量如下

=C:=C:UtiltityBin

=D:=D:Program Files

则表示进程C在驱动器的当前目录为UtilityBin,在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,可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!
");
    }
    else
        printf("当前操作系统不是Win8.1!
");
}

int main()
{
    //利用VerifyVersionInfo函数判断
    printf("利用VerifyVersionInfo函数判断:
");
    IsWindows8();

    //利用VersionHelpers.h中的函数判断
    printf("
利用VersionHelpers.h中的函数:
");
    if (IsWindows8Point1OrGreater())
    {
        printf("当前操作系统等于或高于Win8.1版本!
");
    }
    else
    {
        printf("当前操作系统低于Win8.1版本!
");
    }
    return 0;
}
原文地址:https://www.cnblogs.com/5iedu/p/4655948.html