【windows核心编程】 第四章(2) 进程

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);

 

 

 

posted on 2013-05-26 13:01  崔好好  阅读(556)  评论(0编辑  收藏  举报

导航