windows核心编程 第四章(2) 进程
6、系统版本
BOOL GetVersionEx(POSVERSIONINFOEX pVersionInformation);
在win7 + VS2010环境下,如果传POSVERSIONINFOEX 类型指针会报错,不能把这个类型转换为POSVERSIONINFOW类型,解决办法是传入POSVERSIONINFOEX后强转为POSVERSIONINFOW类型。
1 #include "stdafx.h"
2
3 #include "windows.h"
4
5 #include <iostream>
6
7 using namespace std; 8
9
10 int _tmain(int argc, _TCHAR* argv[]) 11 { 12
13 OSVERSIONINFO osVersion; 14
15 OSVERSIONINFOEX osVersion2; 16
17
18
19 osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); 20
21 osVersion2.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); 22
23
24 BOOL bRet = GetVersionEx(&osVersion); 25
26 BOOL bRet2 = GetVersionEx((LPOSVERSIONINFO)&osVersion2); //强转
27
28
29 return 0; 30
31 } 32
33
34
35
36
37 typedef struct{ 38
39 DWORD dwOSVersionInfoSize; //该结构体大小,传入sizeof(该类型)
40
41 DWORD dwMajorVersion;//主版本号
42
43 DWORD dwMinorVersion; //次版本号
44
45 DWORD dwBuildNumber; //构建版本号
46
47 DWORD dwPaltformId; //当前系统支持的套件(suite),值可以是VRE_PLATFORM_WIN32s(Win32s), VER_PLATFORM_WIN32_WINDOWS(win95/98), VER_PLATFORM_WIN32_NT(NT/2000/XP/2003/VISTA)
48
49 TCHAR szCSDVersion[128];//额外的文本,提供了与已安装的操作系统有关的更多信息
50
51 WORD wServicePackMajor;//SP主版本号
52
53 WORD wServicePackMinor; //SP次版本号
54
55 WORDD wSuiteMask; //标识当前系统上可用的suite(s)
56
57 BYTE wProductType; //指出安装的是以下操作系统产品中的哪个:VER_NT_WORKSTATION, VER_NT_SERVER, VER_NT_DOMAIN_CONTROLLER
58
59 BYTE wReserved; //保留,为0即可
60
61 }OSVERSIONINFOEX, *POSVERSIONINFOEX;
比较主机操作系统是否符合应用程序要求的版本:
1 BOOL VerifyVersionInfo( 2
3 POSVERSIONINFOEX pVersionInformation,//见上方
4
5 DWORD dwTypeMask, //比较那些成员(VER_MINORVERSION, VER_MAJORVERSION, VER_BUILDNUMBER, VER_PLATFORMID, VER_SERVICEPACKMINOR, VER_SERVICEPACKMAJOR, VER_SUITENAME, VER_PRODUCT_TYPE);
6
7 DWORDLONG dwlConditionMaks //怎么比较(VER_EQUAL, VER_GREATER, VER_GREATER_EQUAL, VER_LESS或VER_LESS_EQUAL。 对于VER_SUITNAME信息就不能执行这些测试,相反,必须用VER_AND(所有套件都必须安装)或VER_OR(至少安装了其中的一个套件产品))
8
9 );
dwConditionMask使用一套复杂的位组合对比较方式进行了描述,为了创建恰当的位组合,可以使用VER_SET_CONDITION宏:
1 VER_SET_CONDITION( 2
3 DWORDLONG dwlConditionMask, //初始为0
4
5 ULONG dwTypeBitMask, //同VerifyVersionInfo中的dwTypeMask,哪个(不是哪些)要比较的成员,调用多次这个宏,为每个需要比较的成员赋值条件掩码(第三个参数)
6
7 ULONG dwConditionMask//条件掩码,注意区分和dwlConditionMask
8
9 )
1 //eg.
2
3 OSVERSIONINFOEX osVersion3; 4
5 osVersion3.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); 6
7
8 osVersion3.dwMajorVersion = 6; 9
10 osVersion3.dwMinorVersion = 1; //win7的主次版本号
11
12 osVersion3.dwPlatformId = VER_PLATFORM_WIN32_NT; //要比较哪些成员就该哪些成员赋值;
13
14
15
16 DWORDLONG dwConditionMask = 0; 17
18 VER_SET_CONDITION(dwConditionMask, VER_MAJORVERSION, VER_EQUAL); 19
20 VER_SET_CONDITION(dwConditionMask, VER_MINORVERSION, VER_EQUAL); 21
22 VER_SET_CONDITION(dwConditionMask, VER_PLATFORMID, VER_EQUAL); 23
24
25
26 if (VerifyVersionInfo(&osVersion3, VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID, dwConditionMask) ) 27
28 { 29
30 cout<<"It is windows 7"<<endl; 31
32 } 33
34 else
35
36 { 37
38 cout<<"It is not windows 7"<<endl; 39
40 }
7、CreateProcess函数
1 BOOL CreateProcess( 2
3 PCTSTR pszApplicationName, //exe名字
4
5 PTSTR pszCommandLine,//命令行参数
6
7 PSECURITY_ATTRIBUTES psaProcess,//进程安全属性
8
9 PSECURITY_ATTRIBUTES psaThread, //进程的主线程安全属性
10
11 BOOL hInheritHandles, //是否允许该进程内核对象句柄被继承(最后一个参数中有该进程和主线程的内核对象句柄,如果不用直接CloseHandle)
12
13 DWORD fdwCreate,//标记
14
15 PVOID pvEnvironment, 16
17 PCTSTR pszCurDir,//进程当前目录
18 PSTARTUPINFO psiStartInfo, //进程启动信息
19
20 PPROCESS_INFORMATION ppiProcInfo//进程有关信息结构体,包含进程ID,进程句柄,线程ID,线程句柄
21
22 );
创建进程时,系统为新进程创建一个虚拟地址空间,并将可执行文件(和所有必要的DLL)的代码和数据加载到进程的地址空间中。
需要注意的是,当函数成功时返回TRUE,但是当创建的子进程尚未初始化好之前就返回TRUE,如果此子进程需要的DLL没有加载成功就会导致子进程创建不成功,而此时函数已经返回了TRUE,所以只是个需要注意的问题!
7.1 参数:
pszApplicationName:可执行文件名,通常为NULL,可执行文件名和参数都由pszCommandLine来传入。
pszCommandLine:要传给新进程的命令行字符串,类型为PTSTR,非CONST类型,意味着函数CreateProcess内部会修改这个参数,在CreateProcess函数返回之前它会将这个字符串还原为原来的形式。所以我们在给这个参数传参的时候不要直接传一个常量字符串,而要把常量字符串放在一个临时缓冲区中,当CreataProcess解析pszCommandLine字符串时它会检查字符串中的第一个标记(token),并假定它是我们运行的可执行文件的名字,如果可执行文件没有扩展名则默认是.exe扩展名。
最好使用VS编译器的/GF开关和一个临时缓冲区。
C/C++运行时库启动时会检查进程的命令行,将可执行文件名之后的第一个实参的地址传给(w)WinMain的pszCmdLine参数。
如果pszApplicationName为NULL的时候会执行上述操作,当pszApplicationName不为空的时候,pszApplicationName必须自定扩展名.exe,不可以省略,如果省略了会出错。
但同时pszApplicationName和pszCommandLine都不为空则不能同时都给这两个参数传可执行文件,如果pszApplicationName指定了可执行文件,pszCommandLine也指定了这可执行文件,则报如下的错误,不管pszCommandLine中有没有指定.exe扩展名。
1 void CCreateProcessDlg::OnBnClickedButton1() 2 { 3
4 // TODO: 在此添加控件通知处理程序代码;
5
6 TCHAR szApplication[] = _T("C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\IDE\\devenv.exe"); 7
8 TCHAR szCmdLine[] = _T("C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\IDE\\devenv \
9
10 D:\\Files\\VS2010-MyProjects\\windowsCore2\\windowsCore2.sln");
11
12
13
14 STARTUPINFO startInfo = {sizeof(startInfo)};//一定要初始化为0;
15
16 STARTUPINFOEX startInfoEx = {sizeof(startInfoEx)}; 17
18
19 PROCESS_INFORMATION proInfo; //进程信息结构体;
20
21
22 SECURITY_ATTRIBUTES saProcess; 23
24 saProcess.bInheritHandle = FALSE; //该进程内核对象不能被继承;
25
26 saProcess.lpSecurityDescriptor = NULL; 27
28 saProcess.nLength = sizeof(saProcess); 29
30
31 SECURITY_ATTRIBUTES saThread; 32
33 saThread.bInheritHandle = FALSE; 34
35 saThread.lpSecurityDescriptor = NULL; 36
37 saThread.nLength = sizeof(saThread); 38
39
40 BOOL bRet = CreateProcess( 41
42 NULL, /* szApplication ,*/
43
44 szCmdLine, 45
46 &saProcess, 47
48 &saThread, 49
50 FALSE, 51
52 CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS | EXTENDED_STARTUPINFO_PRESENT, //初始时进程的主线程挂起,等待主调进程调用ResumeThread;
53
54 NULL, 55
56 NULL, 57
58 /*&startInfo,*/ &startInfoEx.StartupInfo, //fdwCreate指定了EXTENDED_STARTUPINFO_PRESENT
59
60 &proInfo 61
62 ); 63
64
65 if (TRUE == bRet) 66
67 { 68
69 ResumeThread(proInfo.hThread); 70
71 CloseHandle(proInfo.hThread); 72
73 CloseHandle(proInfo.hProcess); 74
75 } 76
77 else
78
79 { 80
81 DWORD dwErr = GetLastError(); 82
83 CString strInfo; 84
85 strInfo.Format(_T("lasterror=%u"), dwErr); 86
87 AfxMessageBox(strInfo); 88
89 } 90
91 }
fdwCreate参数:
这个参数标识了影响新进程创建方式的标志。
DEBUG_PROCESS
DEBUG_ONLY_THIS_PROCESS
CREATE_SUSPENDED //创建子进程后子进程的主线程挂起,等待主调进程调用ResumeThread
DETACHED_PROCESS //当子进程是一个CUI程序时该表示表示子进程新创建一个控制台窗口,不使用父进程的控制台窗口
CREATE_NEW_CONSOLE //系统为新进程创建一个新的控制台窗口,不能和DETACHED_PROCESS同时用
CREATE_NO_WINDOW //不创建任何控制台窗口
CREATE_NEW_PROCESS_GROUP
CREATE_DEFAULT_ERROR_MODE //子进程不会继承父进程的错误模式(SetErrorMode)
CREATE_SEPARATE_WOW_VDM
CREATE_SHARED_WOW_VDM
CREATE_UNICODE_ENVIRONMENT //表示子进程的环境块包含UNICODE字符,进程的环境块默认包含的是ANSI字符串
CREATE_FORCEDOS //DOS年代的东西
CREATE_BREAKAWAY_FROM_JOB //标志允许一个作业中的进程生成一个和作业无关的进程EXTENDED_STARTUPINFO_PRISENT //表示CreateProcesss中给psiStartInfo参数传的是一个STARTUPINFOEX结构体(如果是STARTUPINFOEX结构体指针,则需要取其中的STARTUPINFO成员,否则不能创建子进程,GetLastError为87(参数错误)。
另外还有一些关于进程优先级的标志:
IDLE----IDLE_PRIORITY_CLASS
Below normal----BELOW_NORMAL_PRIORITY_CLASS
Normal----NORMAL_PRIORITY_CLASS
Above Normal----ABOVE_NORMAL_PRIORITY_CLASS
High----HIGH_PRIORITY_CLASS
RealTime----REALTIME_PRIORITY_CLASS
pvEnvironment参数
该参数指向一块内存,其中包含新进程要使用的环境字符串,大多数时候传入NULL即可,这将导致子进程继承父进程的一组环境字符串。
获得主调进程的环境字符串地址:
PVOID GetEnvironmentStrings(); //内部分配内存
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock); //释放内存
1 //eg.
2
3 PTSTR psEnv = (PTSTR)GetEnvironmentStrings(); 4
5 AfxMessageBox(CString(psEnv)); 6
7 FreeEnvironmentStrings(psEnv);
pszCurDir参数
进程当前驱动器和目录,如果为NULL则和父进程有同样的当前驱动器和目录,如果不为NULL,则必须至少指定一个驱动器。
psiStartInfo参数
该参数指向一个STARTUPINFO或一个STARTUPINFOEX结构,取决于fdwCreate中是否指定EXTENDED_STARTUPINFO_PRESENT标志,如果指定了该标记则为后者。
该参数结构体必须初始化其值,因为这些值可能会导致子进程创建失败,至少要做的是为第一个成员DWROD cb(结构体字节大小)赋值为sizeof(STARTUPINFO或STARTUPINFOEX),
其他成员都为0, 可如下:
1 STARTUPINFO startInfo = {sizeof(STARTUPINFO)}; 2
3
4
5 typedef struct _STARTUPINFO { 6
7 DWORD cb; //CUI, GUI大小
8
9 LPTSTR lpReserved; //CUI, GUI保留, NULL
10
11 LPTSTR lpDesktop; //CUI, GUI指定哪个桌面
12
13 LPTSTR lpTitle; //CUI,窗口标题
14
15 DWORD dwX; 16
17 DWORD dwY; //CUI,GUI,略
18
19 DWORD dwXSize; 20
21 DWORD dwYSize; //CUI, GUI
22
23 DWORD dwXCountChars; //CUI,控制台窗口的宽度和高度(字符数来表示)
24
25 DWORD dwYCountChars; //CUI,控制台窗口的宽度和高度(字符数来表示)
26
27 DWORD dwFillAttribute;//CUI, 控制台窗口所用的文本和背景色
28
29 DWORD dwFlags; //CUI, GUI,详见后面
30
31 WORD wShowWindow; //GUI,在某exe上右键属性:运行方式(常规,最大化,最小化)
32
33 WORD cbReserved2; //CUI, GUI, 保留,0
34
35 LPBYTE lpReserved2; //保留, NULL
36
37 HANDLE hStdInput; //CUI
38
39 HANDLE hStdOutput; //CUI
40
41 HANDLE hStdError; //CUI
42
43 } STARTUPINFO, *LPSTARTUPINFO;
关于STARTUPINFO的dwFlags成员:
该成员表示了使用该STARTUPINFO结构体中的哪些成员,即忽略哪些成员。
下面表示: 表示和对应的使用的那些成员
STARTF_USESIZE:dwSXize, dwYSize
STARTF_USESHOWWINDOW: wShowWindow
STARTF_USEPOSITION:dwX, dwY
STARTF_USECOUNTCHARS: dwXCountChars, dwYCountChars
STARTF_USEFILLLATTRIBUTES: dwFillAttribute
STARTF_USESTDHANDLES:hStdInput, hStdOutput, hStdError
STARTF_FORCEONFEEDBACK //鼠标变为忙碌,鼠标自己会变回来
STARTF_FORCEOFFFEEDBACK //不让或关闭鼠标变为忙碌
对某GUI程序右键时,属性可以看到运行方式,值即为WShowWindow的值。
1 typedef struct _STARTUPINFOEX { 2
3 STARTUPINFO StartupInfo; 4
5 PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; 6
7 } STARTUPINFOEX, *LPSTARTUPINFOEX;
此结构略,详见《windows核心编程》第五版,第96页。
ppiProcInfo参数
1 typedef struct_PROCESS_INFORMATION{ 2 HANDLE hProcess; 3
4 HANDLE hThread; 5
6 DWORD dwProcessId; 7
8 DWORD dwThreadId; 9
10 }PROCESS_INFORMATION;
其中成员含义如下。
① hProcess:返回新进程的句柄。
② hThread:返回主线程的句柄。
③ dwProcessId:返回一个全局进程标识符。该标识符用于标识一个进程。从进程被
创建到终止,该值始终有效。
④ dwThreadId:返回一个全局线程标识符。该标识符用于标识一个线程。从线程被创
建到终止,该值始终有效。
Windows任务管理器里PID为0的进程为【System Idel Process】, 该进程中的线程数量为逻辑CPU的数量。
获得当前进程ID
GetCurrentProcessId()
获得当前线程ID
GetCurrentThreadId()
根据进程内核对象句柄获得进程ID
GerProcessId(HANDLE hProcess);
根据线程内核对象句柄获得线程ID
GetThreadId(HANDLE hThread)
根据线程内核对象句柄获得该线程所在进程的进程ID
GetProcessIdOfThread(HANDLE hThread)
注意:父子进程的关系只有在创建瞬间才有,创建之后就不存在父子关系了,ToolHelp函数允许进程通过PROCESSENTRY32结构体来查询其父进程,其th32ParentProcessID成员记录的就是子进程的父进程ID,但是由于进程ID会被复用,因此结果可能不准确。
如果不用CloseHandle函数来关闭进程内核对象的句柄,则父进程的ID就不会被复用。
1 typedef struct tagPROCESSENTRY32 2 { 3
4 DWORD dwSize; 5
6 DWORD cntUsage; 7
8 DWORD th32ProcessID; 9
10 ULONG_PTR th32DefaultHeapID; 11
12 DWORD th32ModuleID; 13
14 DWORD cntThreads; 15
16 DWORD th32ParentProcessID; //其父进程进程ID(进程ID会被重用,因此可能不准确)
17
18 LONG pcPriClassBase; 19
20 DWORD dwFlags; 21
22 TCHAR szExeFile[MAX_PATH]; 23
24 } PROCESSENTRY32, *PPROCESSENTRY32;
8、终止进程
四种方式:
① 主线程的入口点函数返回(强烈推荐, strongly recommanded)
② 进程中的一个线程调用ExitProcess函数(要避免)
③ 另一个进程中的线程调用TerminateProcess函数(避免)
④ 进程中所有的线程都【自然死亡】(几乎从不会发生)
8.1 ExitProcess
VOID ExitProcess(UINT fuExitCode); //退出当前进程,参数为退出代码
当主线程的的入口点函数(WinMain, wWinMain, main, wmain)返回时,会返回到C/C++运行库启动代码,后者将正确清理进程使用的全部C/C++运行时资源。 释放了C运行时资源之后,C运行时启动代码将显示调用ExitProcess,并将入口点函数返回的值传给他,即退出代码。
C/C++运行库:不管进程中是否还有其他线程正在运行,只要应用程序的主线程从它的入口点函数返回,C/C++运行库就会调用ExitProcess来终止进程,但是,如果在入口点函数中调用的是ExitThread而不是调用ExitProcess或者入口点函数直接返回,应用程序住线程将停止执行,但只要进程中还有其他线程正在运行,进程就不会终止。
只要从主线程的入口点函数返回,C/C++运行时就能执行其清理工作,并正确析构所有的C++对象。
如果显示调用ExitProcess可能导致C++对象或者其他资源不能得到清理。
8.2 TerminateProcess
1 BOOL TerminateProcess( 2
3 HANDLE hProcess, //要关闭的进程的句柄
4
5 UINT fuExitCode //退出代码
6
7 );
该函数是异步的,当该函数返回的时候并不能保证要关闭的进程已经终止。
8.3
如果进程中的每个线程都调用了ExitThread函数来终止当前线程,那么进程的退出代码将会是最后一个线程的退出代码。
进程终止时要做的工作:
① 终止进程中遗留的任何线程
② 释放所有用户对象和GDI对象,关闭所有内核对象句柄(根据引用计数决定是否销毁)
③ 进程的退出代码从STILL_ACTIVE变为传给ExitProcess或TerminateProcess函数的代码
④ 进程内核对象的状态变为【已触发】
⑤ 进程内核对象的引用计数减1
关于子进程: 进程间通信的方式有剪贴板、内存文件映射、命名管道、匿名管道(仅父子进程间)、邮槽等。
//获得某个进程的退出代码
BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pExitCode);
1 TCHAR szParam = _T(“XXX.exe 参数1 参数2 … 参数N”); 2
3 PROCESS_INFORMATION proInfo; 4
5 BOOL bRet = CreateProcess(NULL, szParam,…..,&proInfo); 6
7 If(TRUE == bRet) 8 { 9
10 CloseHandle(proInfo.hThread); //关闭子进程的主线程内核对象句柄
11
12
13
14 WaitForSingleObject(proInfo.hProcess, INFINITE);//当proInfo.hProcess标识的进程终止时会设置内核对象状态为【已触发】
15
16
17
18 DWORD dwExitCode = 0; 19
20 GetExitCodeProcess(proInfo.hProcess, &dwExitCode); //获得子进程的退出代码, 如果调用GetExitCodeProcess时标识的进程还没有终止,则函数调用STILL_ACTIVE(值为0x103)标识符来填充dwExitCode的值。
21
22
23
24 CloseHandle(proInfo.hProcess); 25
26 }
如果要切断父子进程之间的所有联系,windows资源管理器必须调用CloseHandle来关闭新进程及其主线程的句柄。
CloseHandle(proInfo.hProcess);
CloseHandle(proInfo.hThread);