进程由两部分组成:
- 内核对象。内核对象也是系统用来存放进程统计信息的地方。
- 地址空间,包含可执行模块或DLL模块的代码和数据。还包含动态分配的空间。如线程堆栈和堆分配空间
4.1.1 进程的实例句柄
加载到进程地址空间的每个可执行文件或DLL文件均被赋予一个独一无二的示例句柄。可执行文件的示例句柄作为WinMain的第一个参数hinstExe来传递。加载资源的函数都需要该句柄的值。
HICON LoadIcon( HINSTANCE hinst, PCTSTR pszIcon);
第一个参数指明哪个文件(可执行文件或D L L 文件)包含你想加载的资源。
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);
当一个线程调用该函数时,会为新创建的进程创建一个内核对象,初始使用计数是1,内核对象可理解为一种较小的数据结构。然后为新进程创建一个虚拟地址空间,并将可执行文件或DLL文件的代码和数据加载到该进程的地址空间中。
然后系统为新进程创建一个线程内核对象(使其计数为1),主线程便开始运行,最终调用Wi n M a i n 、w Wi n M a i n 、m a i n 或w m a i n 函数。如成功则返回TRUE。
4.2.1 pszApplicationName和pszCommandLine
pszCommandLine类型是PTSTR,所以不能是只读变量,系统在函数返回之前会还原pszCommandLine中的数据
一般情况下,pszApplicationName都设置为NULL,而pszCommandLine包含 可执行文件名 + 命令行。例如:
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR szCommandLine[] = TEXT("NOTEPAD");
CreateProcess(NULL,szCommandLine,NULL,NULL,
FALSE,0,NULL,NULL,&si,&pi);
如果szCommandLine的第一个标记(文件名)没有扩展名,则认为扩展名是.exe,如果没有制定路径,则按照下面的顺序在目录中搜索:
1) 包含调用进程的. e x e 文件的目录。
2) 调用进程的当前目录。
3) Wi n d o w s 的系统目录。
4) Wi n d o w s 目录。
5) PAT H 环境变量中列出的目录。
特殊情况下,pszApplicationName不设置为NULL时,而设置为可执行文件的路径时,必须设置文件的扩展名,系统不会为它增加扩展名,也只会在当前目录中查找文件,除非它是个绝对路径,在当前路径中找不到就执行失败了。(这种方式几乎不用。)
4.2.2 psaProcess 、psaThread和binheritHandles
因为是父进程创建的子进程,所以父进程可以操作子进程的进程对象句柄和线程对象句柄。(也就相当于子进程的进程句柄和线程句柄就在父进程当中,父进程再次创建的其他子进程也可以继承第一次创建的子进程的进程和线程句柄。)
举例来说明这3个参数的用法:
ProcessA创建ProcessB,然后ProcessA再创建ProcessC。
A调用CreateProcess创建B(此时A可以操作B进程和线程对象),psaProcess.binheritHandles设置为TRUE,psaThread.binheritHandles设置为FALSE,代表B的进程对象在A中是可继承的,线程对象在A中是不可以继承的。
A调用CreateProcess创建C(此时A可以操作C进程和线程对象), CreateProcess的参数binheritHandles设置为TRUE,代表C继承了A中的可继承句柄(包含B中可继承句柄即B的进程句柄)。
4.2.3 fdwCreate
规定如何创建新进程,支持几个特定的参数。
4.2.6 psiStartInfo
设置子进程的启动信息,一般都使用默认,但有一点要注意,使用默认值时,必须初始化为0,就像这样:
STARTUPINFO si = { sizeof(si) };
CreateProcess(...,&si,...);
如果不初始化为0,有时就不会创建进程。
它的成员可以用来设置一些启动的信息,例如程序启动后的显示方式,程序启动后窗口的坐标等等。
子进程可以调用函数来获得由父进程创建的该结构的拷贝,来做出响应。
4.2.7 ppiProcInfo
typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
CreateProcess返回之前会打开进程对象和线程对象,并把进程句柄和线程句柄保存在hProcess和hThread中,此时这两个对象的使用计数都会加1。(是告诉系统:我要使用这两个内核对象,你不要删除它。)
这意味着在系统能够释放进程对象前,该进程必须终止运行(将使用计数递减为1),并且父进程必须调用CloseHandle(再将使用计数递减1,使之变为0)。