Windows核心编程—学习笔记(3)

第4章 进程

  • 一般将进程定义成一个正在运行程序的一个实例,由2部分组成:内核对象和地址空间;
  • 在单CPU的计算机上,操作系统以轮询方式为每个单独的线程分配时间片;

4.1 编写第一个Windows应用程序

  • Windows支持两种类型的应用程序:GUI程序(图形用户界面)和CUI程序(控制台用户界面);
  • 使用VS创建项目时,集成开发环境会设置连接器开关,GUI程序的连接器开关是/SUBSYSTEM:CONSOLE,CUI程序的是/SUBSYSTEM:WINDOWS;当使用VS创建项目时错误选择了项目类型,可在项目属性中通过更改连接器开关来更改项目类型;
  • Windows应用程序必须有一个入口点函数,程序开始运行时,这个函数会被调用,C/C++程序员可以使用以下2种入口点函数:
int WINAPI _tWinMain(
    HINSTANCE hInstanceExe,     //可执行文件的实例,实际值是一个内存基地址
    HINSTANCE,                  //无参数名,编译器不会发出“参数没有被引用到”警告
    PTSTR,
    int nCmdShow);

int _tmain(
    int argc,
    TCHAR *argv[],
    TCHAR *envp[]);

应用程序类型和相应的入口函数:

应用程序类型 入口点函数(入口) 嵌入可执行文件的启动函数
处理ANSI字符串的GUI应用程序 _tWinMain(WinMain) WinMainCRTStartup
处理Unicode字符串的GUI应用程序 _tWinMain(wWinMain) wWinMainCRTStartup
处理ANSI字符串的CUI应用程序 _tMain(Main) mainCRTStartup
处理Unicode字符串的CUI应用程序 _tMain(WMain) wmainCRTStartup

C/C++运行库启动函数的用途简单总结如下:

  • 获取指向新进程的完整命令行的一个指针;
  • 获取指向新进程的环境变量的一个指针;
  • 初始化C/C++运行库的全局变量,如果包含StdLib.h,我们的代码就可以访问这些变量,变量总结如下:
变量名称 类型 描述和推荐使用的Windows函数
_osver unsigned int 操作系统的构建(build)版本号,请换用GetVersionEx
_winmajor unsigned int 以十六进制表示的Windows系统的主版本号,请换用GetVersionEx
_winminor unsigned int 以十六进制表示的Windows系统的次版本号,请换用GetVersionEx
_winver unsigned int (_winmajor<<8) + _winmajor,请换用GetVersionEx
_argc unsigned int 命令行上传递的参数个数,请换用GetCommandLine

_argv

_wargv

char

wchar_t

长度为_argc的一个数组,请换用GetCommandLine

_environ

_wenviron

char

wchar_t

一个 指针数组,请换用GetEnvironmentStrings或GetEnvironmentVarible

_pgmptr

_wpgmptr

char

wchar_t

正在运行程序的名称及其ANSI/Unicode完整路径,请换用GetModuleFileName
  • 初始化C运行库内存分配函数(malloc和calloc)和其他底层I/O例程使用的堆(heap);
  • 调用所有全局和静态C++类对象的构造函数;

  • 完成了这些初始化工作,C/C++启动函数就会调用应用程序的入口点函数

4.1.1 进程实例句柄

  • 加载到进程地址空间的每一个可执行文件或DLL都被赋予了唯一的实例句柄;
  • HMODULE和HINSTANCE完全是一回事,在16位Windows中,则表示不同类型的数据;
//获取可执行文件/DLL文件被加载到进程地址空间的位置,函数返回一个句柄/基地址
HMODULE GetModuleHandle(PCTSTR pszModule);  

4.1.2 进程前一个实例的句柄

C/C++运行库启动代码总是向(w)WinMain的hPrevInstance参数传递NULL,该参数用于16位Windows系统,不要在自己代码中引用这个参数;

4.1.3 进程命令行

系统在创建新进程时,会传一个命令行给它,这个命令行几乎总是非空的;

4.1.4 进程的环境变量

//每个进程都有环境块,环境块是在进程地址空间内分配的一块内存
=::=::\ ...
VarName1 = VarValue2\0        //环境变量的名称 = 赋予此变量的值
VarName2 = VarValue1\0
VarName3 = VarValue3\0 ...
VarNameX = VarValueX\0
\0
  • 调用GetEnvironmentStrings函数来获取完整的环境块,当不再需要GetEnvironmentStrings函数返回的内存块,应调用FreeEnvironmentStrings函数来释放它:
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);
  • 通过应用程序main入口点函数接收的TCHAR* env[]参数来获取环境块(CUI程序专用):空格是有意义的,等号前后的任何空格都会被考虑在内;
  • 假如更新了注册表项,并希望应用程序立即更新它们的环境块,可以进行如下调用:
SendMessage(HWND _BROADCASR,WM_SETTINGCHANGE,0,(LPARAM) TEXT("Environment"));

//成功找到变量名,返回字符数;未找到就返回0;
DWORD GetEnvironmentVariable(
      PCTSTR pszName,     //指向预期的变量名称
      PTSTR pszValue,     //指向保存变量值的缓冲区
      DWORD cchValue);    //指出缓冲区大小(字符数),可传入0
  • 许多字符串内部都有“可替换字符串”,如:%USERPROFILE%\Documents,两个%之间的内容就是一个“可替换字符串”,环境变量的值放在这里,执行字符串替换后,生成扩展字符串:
//Windows提供了相应的函数,返回值是保存扩展字符串所需的缓冲区大小
//如果参数chSize小于返回值,%%变量不会扩展,会被替换成空字符串
//通常要2次调用ExpandEnvironmentStrings函数
DWORD ExpandEnvironmentStrings(
      PTCSTR pszSrc,     //"可替换环境变量字符串"的字符串地址
      PTSTR pszSrc,      //接收扩展字符串的缓冲区地址
      DWORD chSize);     //缓冲区的最大大小(用字符数来表示)

//可添加、删除、修改一个变量的值
//如果指定的变量不存在就创建它,如果pszValue为NULL则从环境快中删除该变量
BOOL SetEnvironmentVarible(
     PCTSTR pszName,        //标识一个变量
     PCTSTR pszValue);      //想要修改成的值

4.1.5 进程的关联性

进程中的线程可以在主机的任何CPU上执行,也可以强迫线程在可用CPU的一个子集上运行,着称为“处理器的关联性”;

4.1.6 进程的错误模式

//进程调用该函数来告诉系统如何处理错误
UINT SetErrorMode(UINT fuErrorMode);

4.1.7 进程当前所在的驱动器和目录

//线程调用以下函数来获取和设置其所在进程的当前驱动器和目录
DWORD GetCurrentDirectory(DWORD cchCurDir,PTSTR pszCurDir);
BOOL SetCurrentDirectory(PCTSTR pszCurDir);

4.1.8 进程的当前目录

  • Windows的文件函数不会添加或更改驱动器号环境变量,它们只是读取这种变量;
  • 调用GetFullPathName来获得进程当前目录;

4.1.9 系统版本

OSVERSIONINFOEX结构;

//能比较主机系统的版本和应用程序要求的版本
BOOL VerifyVersionInfo(
     POSVERSIONINFOEX pVersionInformation,
     DWORD dwTypeMask,
     DWORDLONG dwlConditionMask);    //一个64位的值,决定着比较方式

4.2 CreateProcess函数

//创建一个进程,它注意不到任何初始化问题
BOOL CreateProcess(
     PCTSTR pszApplicationName,           //新进程要使用的可执行文件名称
     PTSTR pszCommandLine,                //传给新进程的命令行字符串
     PSECURITY_ATTRIBUTES psaProcess,     //
     PSECURITY_ATTRIBUTES psaThread,
     BOOL bInheritHandles,                //新进程对内核对象的继承
     DWORD fdwCreate,                     //标识影响新进程创建方式的标志
     PVOID pvEnvironment,                 //指向一块内存地址
     PCTSTR pszCurDir,
     PSTARTUPINFO psiStartInfo,
     pPROCESS_INFORMATION ppiProcInfo);

4.2.1 pszApplicationName和pszCommandLine参数

4.2.2 psaProcess,psaThread和bInheritHandles参数

  • 为了创建新进程,系统也必须创建一个进程内核对象和一个线程内核对象(用于进程的主线程);
  • 分配2个SECURITY_ATTRIBUTES结构,以便创建安全权限、对象句柄的继承;

4.2.3 fdwCreate参数

fdwCreate参数可用标志如下,还允许指定一个优先级类(priority class):

DEBUG_PROCESS 父进程希望调试子进程及子进程生成的所有进程,任何子进程发生事件,都要通知父进程
DEBUG_ONLY_PROCESS 父进程只调试子进程,最近子进程发生事件,才通知父进程
CREATE_SUSPENDED 创建新进程同时挂起主线程
DETACHED_PROCESS 阻止一个CUI的进程访问其父进程的控制台窗口,并将它的输出发送到一个新的控制台窗口
CREATE_NEW_CONSOLE 为新进程创建新的控制台窗口
CREATE_NO_WINDOW 不要为应用程序创建任何控制台窗口
CREATE_NEW_PROCESS_GROUP 创建一个新的进程组,修改用户按Ctrl+C或Ctrl+Break时获得通知的进程列表
CREATE_DEFAULT_ERROR_MODE 新进程不会继承父进程所用的错误模式(SetErrorMode函数)
CREATE_SEPARATE_WOW_VDM 16位系统独有,创建一个单独的虚拟DOS机(Virtual DOS Machine,VDM),各个程序在单独VDM中运行
CREATE_SHARED_WOW_VDM 16位系统独有,所有应用程序在共享VDM中运行
CREATE_UNICODE_ENVIRONMENT 标志告诉系统子进程环境块包含Unicode字符,进程环境块默认包含ANSI字符串
CREATE_FORCEDOS 强制运行一个嵌入在16位OS/2应用程序中的MS-DOS应用程序
CREATE_BREAKAWAY_FROM_JOB 允许作业中的进程生成一个和作业无关的进程
EXTENDED_STARTUPINFO_PRESENT 向系统表明传给psiStartInfo参数的是一个STARTUPINFOEX结构

4.2.4 pvEnvironment参数

pvEnvironment参数指向的内存块包含新进程要使用的环境字符串,使用GetEnvironmentStrings函数返回环境字符串数据块的地址,当不再需要这块内存时,调用FreeEnvironmentStrings函数来释放它;

4.2.5 pszCurDir参数

参数允许父进程设置子进程的当前驱动器目录

4.2.6 psiStartInfo参数

参数指向一个STARTUPINFO结构或STARTUPINFOEX结构,必须把这个结构不使用的成员清零,结构成员的初始化必须在调用CreateProcess之前完成;

STARTUPINFO结构和STARTUPINFOEX结构的成员:

typedef struct _STARTUPINFO{
        DWORD cb;                 //STARTUPINFO结构中的字节数
        PSTR IpReserved;          //保留,必须初始化为NULL
        PSTR IpDesktop;           //在哪个桌面上启动应用程序
        PSTR IpTitle;             //控制台窗口的窗口标题
        DWORD dwX;                //应用程序窗口在屏幕上的位置(x,y坐标)
        DWORD dwY;
        DWORD dwXSize;            //应用程序窗口的高度和宽度
        DWORD dwYSize;
        DWORD dwXCountChars;      //指定子进程的控制台窗口的高度和宽度(用字符数表示)
        DWORD dwYCountChars;
        DWORD dwFillAttribute;    //指定子进程的控制台窗口所用的文本和背景色
        DWORD dwFlags;       
        WORD wShowWindow;         //指定应用程序主窗口如何显示   
        WORD cbReserved2;         //保留,必须初始化为0
        PBYTE IpREserved2;        //保留,必须初始化为NULL
        HANDLE hStdInput;         //指定到控制台输入缓冲区的句柄和输出缓冲区的句柄
        HANDLE hStdOutput;
        HANDLE hStdError;
}STARTUPINFO, *LPSTARTUPINFO;

typedef struct _STARTUPINFOEX{
        STARTUPINFO StartupInfo;
        struct _PROC_THREAD_ATTRIBUTE_LIST *IpAttributeList;
}STARTUPINFOEX, *LPSTARTUPINFOEX;

dwFlags成员有一组标志,用于告诉CreateProcess函数:PSTARTUPINFO结构中的其他成员包含的信息是否有用,或者忽略一些成员;

4.2.7 ppiProcInfo参数

参数指向一个PROCESS_INFORMATION结构,Create函数返回之前会初始化结构成员:

typedef struct _PROCESS_INFORMATION{
    HANDLE hProcess;
    HANDLE hThread;
    DWORD dwProcessId;
    DWORD dwThreadId;
}PROCESS_INFORMATION:

GetCurrentProcessId    获得当前进程ID
GetCurrentThreadId     获得当前正在运行线程的ID
GetProcessId           获得与指定句柄对应的一个进程ID
GetThreadId            获得与指定句柄对应的一个线程ID
GetProcessIdOfThread   根据线程句柄,获得其所在进程的ID

应用程序要与它的创建者通信,最好不要使用ID,因为ID会被重用,此时ID标识的可能是另一个进程了,应该使用内核对象、窗口句柄来进行通信;

要使进程/线程ID不被重用,就是保证进程或线程对象不被销毁,即不关闭这些对象的句柄,直到应用程序不再使用ID时,再调用CloseHandle来释放内核对象;

4.3 终止进程

进程可通过以下4种方式终止:

4.3.1 主线程的入口点函数返回(强烈推荐的方式)

设计应用程序时,应保证主线程的入口点函数返回后,应用程序的进程才终止;

4.3.2 进程中的一个线程调用ExitProcess函数(避免这种方式)

VOID ExitProcess(UINT fuExitCode);
//ExitThread将使应用程序的主线程停止执行,进程只要还有其他线程在运行,进程就不会终止

4.3.3 进程中的一个线程调用TerminateProcess函数(避免这种方式)

//任何线程都能调用它来终止另一个进程或自己的进程
BOOL TerminateProcess(
     HANDLE hProcess,       //要终止的进程句柄
     UINT fuExitCode);      //进程终止时的退出代码,传入fuExitCode参数的值

4.4.4 进程中所有线程自然死亡(几乎不会发生)

BOOL GetExitCodeProcess(HANDLE hProcess,PDWORD pdwExitCode);

4.4 子进程

Windows提供了几种在不同进程之间传递数据的方式:动态数据交换(DDE),OLE,管道,邮件槽;

//创建新线程,让它执行工作,然后等候结果
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);
}

4.5 管理员以标准用户权限运行时

用户账户控制(UAC)

4.5.1 自动提升进程的权限

可执行文件中嵌入了特殊的资源(RT_MANIFEST),系统会检查清单文件的<trustInfo>段;

可以将清单保存到可执行文件所在目录,扩展名使用.manifest;

4.5.2 手动提升进程的权限

调用ShellExecuteEx函数来提升权限

4.5.3 何为当前权限上下文

GetProcessElevation函数能返回:提升类型和指出进程是否正在以管理员身份运行的布尔值;

4.5.4 枚举系统中正在运行的进程

“性能数据”数据库

4.5.5 Process Information示例程序

发布了3 篇原创文章 · 获赞 3 · 访问量 185

猜你喜欢

转载自blog.csdn.net/qq_35501364/article/details/104066757