Windows 核心编程 学习笔记(第二部分)

Windows 核心编程 学习笔记(第二部分)

Apr 28, 2014
Coding
Windows, C++

8. 进程 #

(1) 进程定义 #

进程通常被定义为一个正在运行的程序的实例,它由两个部分组成:

  • 操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程统计信息的地方
  • 地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态分配的内存。如线程堆栈和堆分配空间。

当创建进程时,系统会自动创建它的第一个线程,称为主线程。

(2) Windows支持两种类型的应用程序 #

  • 基于图像用户界面的GUI应用程序
  • 基于控制台用户界面的CUI应用程序

链接程序根据开关(/SUBSYSTEM:CONSOLE 或 /SUBSYSTEM:WINDOWS)将相应的子系统嵌入产生的可执行程序。

(3) Windows应用程序的入口地址 #

Windows应用程序入口有四种,WimMain\wWinMain\main\wmain。但实际上操作系统并不调用你编写的入口函数。它调用的是 C\C++ Runtime Startup 函数,该函数负责对 C\C++ Runtime Library 进行初始化,这样就可以调用malloc和free之类的函数。它还能确保已经声明的任何全局对象和静态C++对象能够子啊代码执行以前正确的创建。

应用程序类型 入口点 嵌入可执行文件的启动函数
需要ANSI字符和字符串的GUI应用程序 WinMain WinMainCRTStartup
需要Unicode字符和字符串的GUI应用程序 wWinMain wWinMainCRTStartup
需要ANSI字符和字符串的CUI应用程序 main mainCRTStartup
需要Unicode字符和字符串的CUI应用程序 wmain wmainCRTStartup

链接程序负责在它链接可执行文件时选择相应的 C\C++ Rumtime Startup 函数。

如果设定了/SUBSYSTEM:WINDOWS,则链接程序期望找到一个WinMain或wWinMain函数,然后分别选择WinMainCRTStartup 或 wWinMainCRTStartup 函数。/SUBSYSTEM:CONSOLE 同理。

如果程序没有设定/SUBSYSTEM:WINDOWS 或 /SUBSYSTEM:CONSOLE,链接程序会根据代码中(main,wmain,WinMain,wWinMain)自动确定应该链接到哪个子系统。

如果要写一个控制台程序,却新建了一个Win32项目,可有如下4种解决方法:

  • [1] 将main函数改为WinMain【不好,WinMain就不是我们想要的控制台程序了】
  • [2] 新建一个控制台项目,将源码拷贝过去 【不好,麻烦】
  • [3] 将Project Setting 的Link选项中,将/SUBSYSTEM:WINDOWS 改为 /SUBSYSTEM:CONSOLE。【较好】
  • [4] 将Project Setting 的Link选项中,删除/SUBSYSTEM:WINDOWS,让链接程序自动选择。【推荐】

下面是在 VC6.0 和 VS2010 中具体设置的位置

img.png

img.png

Startup 函数功能

四个 Runtime Startup 函数基本功能相同,区别只在处理ANSI字符串还是Unicode字符串,以及在对C\C++ Runtime Library进行初始化后调用哪个入口函数。这四个 Startup函数都包含在crt0.c源文件中,它们的功能如下:

  • 检索指向新进程的完整命令行的指针
  • 检索指向新进程的环境变量的指针
  • 对C\C++ Runtime 的全局变量进行初始化。如果包含了StdLib.h文件,代码就能访问这些变量。
  • 为所有全局和静态的C++类对象调用构造函数。
  • 之后,调用应用程序的入口函数(main,wmain,WinMain,wWinMain)。调用的方式分别如下:

wWinMain

GetStartupInfo(&StartupInfo);
int nMainRetVal = wWinMain(GetModuleHandle(NULL),NULL,pszCommandLineUnicode,
StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWDEFAULT);

WinMain

GetStartupInfo(&StartupInfo);
int nMainRetVal = WinMain(GetModuleHandle(NULL),NULL,pszCommandLineAnsi,
StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWDEFAULT);

wmain

int nMainRetVal = wmain(__argc,__wargv,_wenviron);

main

int nMainRetVal = main(__argc,__argv,_environ);
  • 入口函数返回后,Startup函数调用C\C++ Runtime 的 exit函数,并将nMainRetVal传递给它。exit函数功能如下:
    • [1] 调用由_onexit函数的调用而注册的任何函数。
    • [2] 为所有的全局的和静态的C++类对象调用析构函数
    • [3] 调用操作系统的ExitProcess函数,将nMainRetVal传递给它。这使得该操作系统能够撤销进程并设置它的exit代码。

9. WinMain\wWinMain 函数 #

int WINAPI WinMain( HINSTANCE hInstance, //当前应用程序实例的句柄
                    HINSTANCE  hPrevInstance, //当前应用程序前一个实例的句柄,必须置为NULL
                    LPSTR  lpCmdLine, //命令行参数    [wWinMain的区别 LPWSTR]
                    int  nCmdShow //窗口显示标志
);

(1) 进程的实例句柄 #

加载到进程地址空间的每一个可执行文件或DLL文件均被赋予一个独一无二的实例句柄。

可执行文件的实例句柄(当前进程的实例)作为 (w)WinMain 的第一个参数 hInstance 传递进入该函数。对于加载资源的函数调用来说,通常需要该句柄,如 LoadIcon(HINSTANCE hInstance, PCTSTR pszIcon) 第一个参数指明哪个文件(可执行文件或DLL文件)包含你想加载的资源。

HINSTANCE 和 HMODULE 完全相同,16位Windows中不同

WinMain 的 hInstance 参数实际值是系统将可执行文件的映像加载到进程的地址空间时使用的基地址。这个基地址的值有链接程序决定,不同的链接程序可以使用不同的默认基地址。

  • Visual C++ 6.0 是 0x00400000
  • Visual Studio 2010 是 不确定的。

img.png

GetModuleHandle函数

使用GetModuleHandle 函数可以返回可执行文件或DLL加载到进程地址空间时所用的句柄(即基地址)

HMODULE WINAPI GetModuleHandle(
__in_opt  LPCTSTR lpModuleName //可执行文件或DLL文件的名称,NULL返回当前程序实例句柄
);

GetModuleHandle 只查看调用进程的地址空间,一个模块(可执行文件或DLL文件)虽然已经加载到系统了,但是没有加载到本进程,GetModuleHandle依然返回NULL。

(2) 命令行 #

创建进程时,会传递一个命令行,该命令行的第一个参数就是程序的完整名称。当 C\C++ Runtime Startup 函数运行时,它会跳过程序的名称,将其余部分传递给 (w)WinMain 函数。

使用GetCommandLine()可以获取完整的命令行参数

LPSTR pCmdLine = GetCommandLine();     //完整的命令行参数

分割命令行的各部分可以使用全局变量 __argc (或 __wargv) 或者使用 CommandLineToArgvW (只有Unicode版本)

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){
    LPSTR arg = (LPSTR) malloc(sizeof(char)*100);
    for(int i=0;i<__argc;i++){
        sprintf(arg,"%s",__argv[i]);
        MessageBox(NULL,arg,"提示",MB_OK);
    }
    return 0;
}
int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE,PWSTR lpCmdLine,int nShowCmd){
    int nNumArgs;
    PWSTR *ppArgv = CommandLineToArgvW(lpCmdLine,&nNumArgs);

    for(int i=0;i<nNumArgs;i++){
        MessageBox(NULL,ppArgv[i],L"参数",MB_OK);
    }

    HeapFree(GetProcessHeap(),0,ppArgv);  //释放内存

    return 0;
}

10. 进程的环境变量 #

每个进程都有一个与它相关的环境块。环境块是进程的地址空间中分配的一个内存块。每个环境块都包含一组字符串,其形式如下:

VarName1=VarValue1\0
VarName2=VarValue2\0
…
\0

形式为:环境变量名称=环境变量具体值\0。环境块最后还有一个\0.

环境变量中空格是有意义的,Var1=ABC 和 Var2= ABC 是不一样的

子进程环境变量 #

子进程可以继承父进程的环境变量,实际上是对父进程环境块的拷贝,父进程和子进程不共享环境块,两者可以各自操作自己的环境块。

父进程可以控制子进程继承哪些环境变量。

获取和设置环境变量的API #

通过 GetEnvironmentVariable 函数可以获取环境变量的值,通过 SetEnvironmentVairable 函数设置一项环境变量。

DWORD WINAPI GetEnvironmentVariable(   //获取环境变量的值,成功则返回缓存中实际存储的字符数目,失败返回0。GetLastError返回ERROR_ENVVAR_NOT_FOUND
    __in_opt   LPCTSTR lpName,   //环境变量的名称
    __out_opt  LPTSTR lpBuffer,  //接受环境变量值的缓存
    __in       DWORD nSize       //缓存的大小(单位字符)
);
BOOL WINAPI SetEnvironmentVariable( //设置一个环境变量
    __in      LPCTSTR lpName, //环境变量的名称
    __in_opt  LPCTSTR lpValue   //环境变量的值
);
int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE,PWSTR lpCmdLine,int nShowCmd){

    SetEnvironmentVariable(_T("Var1"),_T("First Variable"));
    SetEnvironmentVariable(_T("Var2"),_T("Second Variable"));

    TCHAR buf[256];
    GetEnvironmentVariable(_T("Var1"),buf,sizeof(buf)/sizeof(TCHAR));
    MessageBox(NULL,buf,_T("提示"),MB_OK); //First Variable
    return 0;
}

11. 进程的错误模式 #

进程的错误模式是指与每个进程相关联的一组标志,用于告诉系统,进程对严重的错误应该如何作出反映,包括磁盘介质故障、未处理的异常情况、文件查找失败和数据没有对齐等。进程可以通过调用SetErrorMode函数通知系统如何处理故障。

UINT SetErrorMode(UINT uMode);

uMode是下面参数或参数的组合(|)

标志 说明
0 使用系统默认,即显示所有的错误对话框
SEM_FAILCRITICALERRORS 系统不显示关键错误句柄消息框,并将错误返回给调用进程
SEM_NOGOFAULTERRORBOX 系统不显示一般保护故障消息框。本标志只应该由采用异常情况处理程序来
SEM_NOOPENFILEERRORBOX 当系统找不到文件时,它不显示消息框
SEM_NOALIGNMENTFAULTEXCEPT 系统自动排除内存没有对齐的故障,并使应用程序看不到这些故障。本标识对x86处理器不起作用。

默认情况下,子进程继承父进程的错误模式标志。

11. 进程的当前驱动器和目录 #

当不提供全路径名时,Windows会在当前驱动器的当前目录中查找文件和目录。操作系统内部会跟踪进程的当前驱动和和目录。(按照进程维护的,所以所有线程共享当前驱动器和目录)

通过 SetCurrentDirectory 和 GetCurrentDirectory 函数可以设置和获取当前目录。

也可以使用C Runtime 函数 _chdir 来更改当前目录,该函数内部调用了 SetCurrentDirectory。

12. 操作系统版本 #

通过GetVersionEx函数获取系统的版本号,通过 GetProductInfo 获取对应版本号的具体发行版本(Ultimate\HomeBasic等)

BOOL WINAPI GetVersionEx(
    __inout  LPOSVERSIONINFO lpVersionInfo
);

typedef struct _OSVERSIONINFOEX {
    DWORD dwOSVersionInfoSize;    //结构体的大小,设置为sizeof(OSVERSIONINFOEX)
    DWORD dwMajorVersion;         //系统的主版本号
    DWORD dwMinorVersion;         //系统的次版本号
    DWORD dwBuildNumber;          //系统的Build号
    DWORD dwPlatformId;           //当前操作系统的平台ID。VER_PLATFORM_WIN32_NT(Windows NT/2000)
    TCHAR szCSDVersion[128];      //当前系统最新的Service Pack描述。如"Service Pack 3"
    WORD  wServicePackMajor;      //Service Pack的主版本号
    WORD  wServicePackMinor;      //Service Pace的次版本号
    WORD  wSuiteMask;             //指示系统安装的产品套件
    BYTE  wProductType;           //系统附加信息。
    BYTE  wReserved;              //保留
} OSVERSIONINFOEX, *POSVERSIONINFOEX, *LPOSVERSIONINFOEX;
Operating system Version number
Windows 7 6.1
Windows Server 2008 R2 6.1
Windows Server 2008 6.0
Windows Vista 6.0
Windows Server 2003 R2 5.2
Windows Server 2003 5.2
Windows XP 5.1
Windows 2000 5.0
#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>

#pragma comment(lib, "User32.lib")

#define BUFSIZE 256

typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
typedef BOOL (WINAPI *PGPI)(DWORD, DWORD, DWORD, DWORD, PDWORD);

BOOL GetOSDisplayString( LPTSTR pszOS)
{
    OSVERSIONINFOEX osvi;
    SYSTEM_INFO si;
    PGNSI pGNSI;
    PGPI pGPI;
    BOOL bOsVersionInfoEx;
    DWORD dwType;

    ZeroMemory(&si, sizeof(SYSTEM_INFO));
    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    if( !(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi)) ) {
        return 1;
    }

    // Call GetNativeSystemInfo if supported or GetSystemInfo otherwise.
    pGNSI = (PGNSI) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),  "GetNativeSystemInfo");
    if(NULL != pGNSI)
        pGNSI(&si);
    else GetSystemInfo(&si);

    if ( VER_PLATFORM_WIN32_NT==osvi.dwPlatformId &&  osvi.dwMajorVersion > 4 )
    {
        StringCchCopy(pszOS, BUFSIZE, TEXT("Microsoft "));

        // Test for the specific product.
        if ( osvi.dwMajorVersion == 6 )
        {
            if( osvi.dwMinorVersion == 0 )
            {
                if( osvi.wProductType == VER_NT_WORKSTATION )
                    StringCchCat(pszOS, BUFSIZE, TEXT("Windows Vista "));
                else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2008 " ));
            }

            if ( osvi.dwMinorVersion == 1 )
            {
                if( osvi.wProductType == VER_NT_WORKSTATION )
                    StringCchCat(pszOS, BUFSIZE, TEXT("Windows 7 "));
                else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2008 R2 " ));
            }

            pGPI = (PGPI) GetProcAddress(
                    GetModuleHandle(TEXT("kernel32.dll")),  "GetProductInfo");

            pGPI( osvi.dwMajorVersion, osvi.dwMinorVersion, 0, 0, &dwType);  //或者直接调用GetProductInfo()

            switch( dwType )
            {
                case PRODUCT_ULTIMATE:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Ultimate Edition" ));
                    break;
                case PRODUCT_PROFESSIONAL:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Professional" ));
                    break;
                case PRODUCT_HOME_PREMIUM:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Home Premium Edition" ));
                    break;
                case PRODUCT_HOME_BASIC:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Home Basic Edition" ));
                    break;
                case PRODUCT_ENTERPRISE:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Enterprise Edition" ));
                    break;
                case PRODUCT_BUSINESS:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Business Edition" ));
                    break;
                case PRODUCT_STARTER:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Starter Edition" ));
                    break;
                case PRODUCT_CLUSTER_SERVER:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Cluster Server Edition" ));
                    break;
                case PRODUCT_DATACENTER_SERVER:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Datacenter Edition" ));
                    break;
                case PRODUCT_DATACENTER_SERVER_CORE:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Datacenter Edition (core installation)" ));
                    break;
                case PRODUCT_ENTERPRISE_SERVER:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Enterprise Edition" ));
                    break;
                case PRODUCT_ENTERPRISE_SERVER_CORE:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Enterprise Edition (core installation)" ));
                    break;
                case PRODUCT_ENTERPRISE_SERVER_IA64:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Enterprise Edition for Itanium-based Systems" ));
                    break;
                case PRODUCT_SMALLBUSINESS_SERVER:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Small Business Server" ));
                    break;
                case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Small Business Server Premium Edition" ));
                    break;
                case PRODUCT_STANDARD_SERVER:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Standard Edition" ));
                    break;
                case PRODUCT_STANDARD_SERVER_CORE:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Standard Edition (core installation)" ));
                    break;
                case PRODUCT_WEB_SERVER:
                    StringCchCat(pszOS, BUFSIZE, TEXT("Web Server Edition" ));
                    break;
            }
        }

        if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2 )
        {
            if( GetSystemMetrics(SM_SERVERR2) )
                StringCchCat(pszOS, BUFSIZE, TEXT( "Windows Server 2003 R2, "));
            else if ( osvi.wSuiteMask & VER_SUITE_STORAGE_SERVER )
                StringCchCat(pszOS, BUFSIZE, TEXT( "Windows Storage Server 2003"));
            else if ( osvi.wSuiteMask & VER_SUITE_WH_SERVER )
                StringCchCat(pszOS, BUFSIZE, TEXT( "Windows Home Server"));
            else if( osvi.wProductType == VER_NT_WORKSTATION &&
                     si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
            {
                StringCchCat(pszOS, BUFSIZE, TEXT( "Windows XP Professional x64 Edition"));
            }
            else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2003, "));

            // Test for the server type.
            if ( osvi.wProductType != VER_NT_WORKSTATION )
            {
                if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_IA64 )
                {
                    if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Datacenter Edition for Itanium-based Systems" ));
                    else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Enterprise Edition for Itanium-based Systems" ));
                }

                else if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64 )
                {
                    if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Datacenter x64 Edition" ));
                    else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Enterprise x64 Edition" ));
                    else StringCchCat(pszOS, BUFSIZE, TEXT( "Standard x64 Edition" ));
                }

                else
                {
                    if ( osvi.wSuiteMask & VER_SUITE_COMPUTE_SERVER )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Compute Cluster Edition" ));
                    else if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Datacenter Edition" ));
                    else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Enterprise Edition" ));
                    else if ( osvi.wSuiteMask & VER_SUITE_BLADE )
                        StringCchCat(pszOS, BUFSIZE, TEXT( "Web Edition" ));
                    else StringCchCat(pszOS, BUFSIZE, TEXT( "Standard Edition" ));
                }
            }
        }

        if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 )
        {
            StringCchCat(pszOS, BUFSIZE, TEXT("Windows XP "));
            if( osvi.wSuiteMask & VER_SUITE_PERSONAL )
                StringCchCat(pszOS, BUFSIZE, TEXT( "Home Edition" ));
            else StringCchCat(pszOS, BUFSIZE, TEXT( "Professional" ));
        }

        if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 )
        {
            StringCchCat(pszOS, BUFSIZE, TEXT("Windows 2000 "));

            if ( osvi.wProductType == VER_NT_WORKSTATION )
            {
                StringCchCat(pszOS, BUFSIZE, TEXT( "Professional" ));
            }
            else  {
                if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                    StringCchCat(pszOS, BUFSIZE, TEXT( "Datacenter Server" ));
                else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                    StringCchCat(pszOS, BUFSIZE, TEXT( "Advanced Server" ));
                else StringCchCat(pszOS, BUFSIZE, TEXT( "Server" ));
            }
        }

        // Include service pack (if any) and build number.

        if( _tcslen(osvi.szCSDVersion) > 0 )
        {
            StringCchCat(pszOS, BUFSIZE, TEXT(" ") );
            StringCchCat(pszOS, BUFSIZE, osvi.szCSDVersion);
        }

        TCHAR buf[80];

        StringCchPrintf( buf, 80, TEXT(" (build %d)"), osvi.dwBuildNumber);
        StringCchCat(pszOS, BUFSIZE, buf);

        if ( osvi.dwMajorVersion >= 6 )
        {
            if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64 )
                StringCchCat(pszOS, BUFSIZE, TEXT( ", 64-bit" ));
            else if (si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_INTEL )
                StringCchCat(pszOS, BUFSIZE, TEXT(", 32-bit"));
        }

        return TRUE;  }

    else
    {   printf( "This sample does not support this version of Windows.\n");
        return FALSE;
    }
}

int __cdecl _tmain()
{
    TCHAR szOS[BUFSIZE];

    if( GetOSDisplayString( szOS ) )
        _tprintf( TEXT("\n%s\n"), szOS ); //Microsoft Windows 7 Ultimate Edition Service Pack 1 (build 7601), 32-bit
}

还可以通过 VerifyVersionInfo 函数来确定当前操作系统是否满足要求。

//设置Windows 7 的版本号和平台ID
OSVERSIONINFOEX osver;
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwMajorVersion = 6;     //Windows 7 版本号为6.1  osver.dwMinorVersion = 1;     
                              //Windows Server 2008 R2 也是6.1,通过PlatformId区分
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);   //要判断平台ID号是否相等

//测试系统是否为Windows 7
if(VerifyVersionInfo(&osver,VER_MAJORVERSION|VER_MINORVERSION|VER_PLATFORMID,dwlConditionMask)){
//此操作系统是Windows 7
}
else{
//此操作系统不是Window 7
}

13. CreateProcess函数 #

BOOL WINAPI CreateProcess(
    __in_opt     LPCTSTR lpApplicationName,  //要执行的模块的名称,如"cmd.exe",可以为NULL,则名称为lpCommandLine的第一个参数
    __inout_opt  LPTSTR lpCommandLine,       //命令行参数
    __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes, //指向进程安全属性结构体的指针,用于确定指向新进程的句柄能否被它的子进程继承
    __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,  //指向线程安全属性结构体的指针,用于确定指向新线程的句柄能否被它的子进程继承
    __in         BOOL bInheritHandles, //是否继承父进程的内核对象句柄
    __in         DWORD dwCreationFlags,       //标志
    __in_opt     LPVOID lpEnvironment,        //运行环境,NULL则使用父进程的环境
    __in_opt     LPCTSTR lpCurrentDirectory,  //当前目录,NULL则与父进程相同
    __in         LPSTARTUPINFO lpStartupInfo, //
    __out        LPPROCESS_INFORMATION lpProcessInformation//
);

当调用CreateProcess时,系统会创建一个进程内核对象,初始计数值为1,该内核对象不是进程本身,只是用来管理进程的一个较小的数据结构;然后系统会为新进程创建一个虚拟地址空间,并将可执行文件和必要的DLL文件的代码和数据加载到该地址空间中;然后系统为新进程的主线程创建一个线程内核对象,初始计数值为1;然后调用C\C++ Runtime Startup函数,使主线程开始运行,最终调用WinMain等入口函数;最后成功则返回TRUE;

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>

int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE,PWSTR lpCmdLine,int nShowCmd){

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si,sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi,sizeof(pi));

    //创建进程
    if(!CreateProcess(_T("C:\\Program Files\\The KMPlayer\\KMPlayer.exe"),//lpApplicationName
        NULL,  //命令行
        NULL,  //进程句柄不可继承
        NULL,  //线程句柄不可继承
        FALSE, //不继承父进程的内核对象
        0,     //标志
        NULL,  //使用父进程的环境
        NULL,  //当前目录与父进程相同。
        &si,    &pi
    )
    ){
        MessageBox(NULL,_T("CreateProcess Failed"),_T("Info"),MB_OK);
    }

    //等待进程结束
    WaitForSingleObject(pi.hProcess,INFINITE);

    //关闭句柄
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}

14. 进程的终止 #

终止进程的运行,有四种方式

(1) 入口函数的返回 (推荐) #

这是保证所有线程资源能够得到正确释放的唯一方法。

(2) ExitProcess 函数 #

VOID WINAPI ExitProcess(  //退出一个进程及其所有线程
__in  UINT uExitCode //退出码
);

C\C++ Runtime Startup 函数在释放完 C Runtime 资源后,调用了ExitProcess函数退出进程。(所以方法(1) 才可行)

直接调用ExitProcess,会导致 C\C++ Runtime 资源没有释放。

#include <windows.h>
#include <stdio.h>

class CSomeObj{
public:
    CSomeObj(){printf("Constructor\n");}
    ~CSomeObj(){printf("Destructor\n");}
};

int main(){
    CSomeObj obj;      //如果不使用ExitProcess,通过入口函数返回,则obj的构造和析构都会得到正确的调用
    //ExitProcess(0);  //如果使用ExitProcess退出,则obj的析构函数不会被调用,obj的资源就无法释放。
    return 0;
}

(3) TerminateProcess 函数 #

BOOL WINAPI TerminateProcess( //终止进程及其所有线程
    __in  HANDLE hProcess, //要终止的进程句柄
    __in  UINT uExitCode   //退出码
);

TerminateProcess可以用来终止本进程或另一个进程。

TernimateProcess是一个异步函数,它会通知系统终止某个进程,但是函数返回时,无法保证该进程已经被终止。要确保终止可以使用WaitForSingleObject函数。

15. 子进程 #

PROCESS_INFORMATION pi;
DWORD dwExitCode;

BOOL fSuccess = CreateProcess(,&pi);
if(fSuccess){
    //关闭新进程的主线程句柄(越早越好,因为它不需要)
    CloseHandle(pi.hThread);

    //等待新进程结束
    WaitForSingleObject(pi.hProcess,INFINITE);

    //获取新进程的退出代码
    GetExitCodeProcess(pi.hProcess,&dwExitCode);

    //关闭新进程的进程句柄
    CloseHandle(pi.hProcess);
}

CreateProcess后,立即关闭了子进程的主线程内核对象句柄pi.hThread,这不会导致子进程主线程的终止,只是递减了其引用计数。这样做的好处是如果子进程的主线程生成了另一个线程,然后主线程退出了,由于父进程没有拥有其句柄,系统就可以释放其内核对象资源。

如果只是启动子进程,然后让子进程独立运行。

PROCESS_INFORMATION pi;
DWORD dwExitCode;

BOOL fSuccess = CreateProcess(,&pi);
if(fSuccess){
    //关闭句柄
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
}