创建进程函数详解

  1. BOOL WINAPI CreateProcess(
  2. __in_opt LPCTSTR lpApplicationName,
  3. __inout_opt LPTSTR lpCommandLine,
  4. __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
  5. __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
  6. __in BOOL bInheritHandles,
  7. __in DWORD dwCreationFlags,
  8. __in_opt LPVOID lpEnvironment,
  9. __in_opt LPCTSTR lpCurrentDirectory,
  10. __in LPSTARTUPINFO lpStartupInfo,
  11. __out LPPROCESS_INFORMATION lpProcessInformation
  12. );

创建一个新的进程以及进程的主线程。新的进程运行在调用该函数的进程的安全环境中。

如果调用进程模拟了另外一个用户,那么新进程使用调用进程的令牌,而不是这个进程的模拟令牌。如果想让新建进程运行在模拟令牌表示的安全环境中,使用CreateProcessAsUser和CreateProcessWithLongW函数。

  1. lpApplicationName:

被执行的模块的名称。这个模块可以是一个windows应用程序。也可以是其他类型的模块(例如MS-DOS或者OS/2)。当然,如果要运行这些类型的模块,操作系统必须有支持这些模块运行的子系统。
这个字符串,可以指定可执行文件的全路径名和文件名,也可以指定一个相对路径文件名。如果指定的是相对路径和文件名,那么函数使用当前的磁盘号和当前目录,来将这个相对路径补充成全路径。这个函数不使用查找路径。这个参数必须包括文件的扩展名,没有默认的文件扩展名!

lpApplicationName参数可以使NULL在这种情况下,lpCommandLine参数的第一个字符,必须是一个空白字符。如果可执行文件的文件名中,包含有空格,那么使用引号引起字符串,来表示文件名的结束和参数的开始;否则,文件名就不明确。例如,考虑这个文件名字符串:”c:\program files\sub dir\program name”。这个字符串可以被解释成好多种字符串。

c:\program.exe files\sub dir\program name
c:\program files\sub.exe dir\program name
c:\program files\sub dir\program.exe name
c:\program ifles\sub dir\program name.exe

如果可执行模块为程序,那么lpApplicationName应该是NULL,lpCommandLine应该指向可执行模块和其参数。

为了运行一个批处理文件,你必须启动命令行解释器;将lpApplicationName设置成cmd.exe,将lpCommandLine参数设置成如下形式:/c加上批处理文件的文件名。

lpCommandLine:
被执行的命令行参数。这个字符串的最大长度可以达到32768个字符,包括null结尾符。如果lpApplicationName是NULL,那么lpCommandLine参数中的可执行文件的名字被限定在MAX_PATH个字符之内。

这个函数的UNICODE版本,CreateProcessW,会修改这个字符串的内容。因此,这个参数不能指向只读内存(例如一个字符串常量)。如果这个参数是一个常量字符串,那么这个函数将引发一个违规访问。

lpCommandLine参数可以是NULL。此时,函数使用lpApplicationName指向的字符串,作为命令行字符串。

如果lpApplicationName和lpCommandLine都是no-NULL,那么lpApplicationName就是可执行文件的文件名,而lpCommandLine指向的就是命令行参数。新进程可以使用GetCommandLine函数,来获取完整的命令行。控制台进程使用argc和argv参数,来分析命令行。此时argv[0]代表可执行文件的名称,作为命令行的第一个参数。

如果lpApplicationName是NULL,那么第一个空白符前面的字符串,被看做是可执行文件的文件名。如果使用的文件名或者路径名中包含了空格,那么需要使用一个引号,将文件名字符串包围起来。如果文件名没有扩展名,那么系统会添加exe作为其文件的扩展名。如果文件名的后缀扩展名为.com,那么就必须在文件名后,添加.com作为其后缀。如果文件名不包含路径,那么系统会按按照下面的顺序查找文件:

  1. 进程可执行文件所在目录
  2. 父进程的当前目录
  3. GetSystemDirectory函数获取的系统目录。
  4. 16位windows系统目录。没有函数可以获得这个系统目录,但这个目录确实会被搜索。这个系统目录是System。
  5. windows目录。也就是GetWindowsDirectory函数获得的目录。
  6. 在PATH环境变量中列出的目录。注意,这个函数并不搜索App Paths注册表键定义的路径。如果想搜索这个目录下的目录,使用ShellExecute函数。
  7. lpProcessAttributes

一个指向SECURITY_ATTRIBUTES结构的指针,这个结构中,最重要的数据结构是一个安全描述符,他决定了新产生的进程对象,是否能被其他子进程继承,这个进程对象,可以被那些用户访问。如果这个参数是NULL,那么产生的进程的进程对象不能被继承。

SECURITY_ATTRIBUTES结构体的成员变量lpSecurityDescriptor,指向一个安全描述符,这个安全描述符将做为新进程的安全描述符。如果lpProcessAttributes=NULL,或者lpSecurityDescriptor=NULL,那么新进程将使用默认的安全描述符。在默认的安全描述符中,ACLs来自于创建新进程的原始令牌。

  1. lpThreadAttributes

一个指向SECURITY_ATTRIBUTES结构体的指针。如果lpThreadAttributes=NULL,那么新线程的句柄不能够被继承。
SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员变量指向一个安全描述符,这个描述符将作为进程主线程的安全描述符。如果lpThreadAttributes=NULL,那么线程的描述符使用缺省描述符,其描述符中的ACLs来自于进程令牌。

  1. dwCreationFlags

这个参数还可以控制进程的优先级,这个优先级决定了进程中的线程的优先级。如果没有优先级标志给出,那么新进程的优先级为NORMAL_PRIORITY_CLASS,如果父进程的优先级是IDLE_PRIORITY_CLASS或者BELOW_NORMAL_PRIORITY_CLASS,那么被创建的子进程的优先级则继承父进程的优先级。

  1. lpEnvironment

一个指向环境变量内存块的指针。如果这个参数是NULL,那么新进程使用父进程的环境变量。环境变量块保存的是一个NULL结尾的字符串,每个环境变量在该字符串中的形式为:name=value\0
因为等号是分隔符,所以,在环境变量的名称中,千万不要出现等号。
环境变量块可以包含UNICODE或者ASCII字符。如果lpEnvironment指针指向的内存,包含UNICODE字符,那么请确保dwCreationFlags标志包含CREATE_UNICODE_ENVIRONMENT标志。如果这个lpEnvironment=NULL,那么子进程将继承父进程的环境变量块,同时你必须保证,dwCreationFlages包含CREATE_UNICODE_ENVIRONMENT标志。
这个函数的的ACSII版本,也就是CreateProcessA,在环境变量块的大小超过32767时,会失败。
注意,注意,这个环境变量块以2个零结尾。

  1. lpCurrentDirectory

进程的当前目录。这个字符产还可以定义一个UNC路径。如果lpCurrentDirectory=NULL,那么子进程的当前目录继承父进程的当前目录。

  1. lpStartupInfo

一个指向STARTUPINFO或者STARTUPINFOEX结构的指针。如果要设置扩展属性,那么dwCreateFlags标志中,应该包含EXTENDED_STARTUPINFO_PRESENT标志。

  1. typedef struct _STARTUPINFO {
  2. DWORD cb;
  3. LPTSTR lpReserved;
  4. LPTSTR lpDesktop;
  5. LPTSTR lpTitle;
  6. DWORD dwX;
  7. DWORD dwY;
  8. DWORD dwXSize;
  9. DWORD dwYSize;
  10. DWORD dwXCountChars;
  11. DWORD dwYCountChars;
  12. DWORD dwFillAttribute;
  13. DWORD dwFlags;
  14. WORD wShowWindow;
  15. WORD cbReserved2;
  16. LPBYTE lpReserved2;
  17. HANDLE hStdInput;
  18. HANDLE hStdOutput;
  19. HANDLE hStdError;
  20. } STARTUPINFO, *LPSTARTUPINFO;
  21. typedef struct _STARTUPINFOEX {
  22. STARTUPINFO StartupInfo;
  23. PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
  24. } STARTUPINFOEX, *LPSTARTUPINFOEX;
  25. BOOL WINAPI InitializeProcThreadAttributeList(
  26. __out_opt LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  27. __in DWORD dwAttributeCount,
  28. __reserved DWORD dwFlags,
  29. __inout PSIZE_T lpSize
  30. );

如果lpAttributeList不是NULL,那么lpSize参数输入lpAttributeList内存块的字节大小。如果lpAttributeList=NULL,那么这个参数接收buffer所需大小。

  1. BOOL WINAPI UpdateProcThreadAttribute(
  2. __inout LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  3. __in DWORD dwFlags,
  4. __in DWORD_PTR Attribute,
  5. __in PVOID lpValue,
  6. __in SIZE_T cbSize,
  7. __out_opt PVOID lpPreviousValue,
  8. __out_opt PSIZE_T lpReturnSize);
  9. VOID WINAPI DeleteProcThreadAttributeList(
  10. __inout LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList);
  1. lpProcessInformation:

一个指向PROCESS_INFORMATION结构的指针。

  1. typedef struct _PROCESS_INFORMATION {
  2. HANDLE hProcess;//进程句柄
  3. HANDLE hThread;//主线程句柄
  4. DWORD dwProcessId;//进程ID
  5. DWORD dwThreadId;//主线程ID
  6. } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

创建一个线程代码

  1. #include<Windows.h>
  2. #include<tchar.h>
  3. int _tmain()
  4. {
  5. STARTUPINFO info;
  6. ZeroMemory(&info,sizeof(info));
  7. info.cb=sizeof(info);
  8. PROCESS_INFORMATION si;
  9. info.dwFlags=STARTF_USESHOWWINDOW;
  10. info.wShowWindow=1;
  11. _tprintf(L"CreateProces\n",GetLastError());
  12. TCHAR CommandLine[200]=L"\"D:/hello/Test.exe\" abc 123";
  13. if(!CreateProcess(NULL,CommandLine,
  14. NULL,NULL,FALSE,CREATE_NEW_CONSOLE,NULL,NULL,&info,&si))
  15. {
  16. _tprintf(L"CreateProcess failure!=%d\n",GetLastError());
  17. _gettchar();
  18. return 0;
  19. }
  20. _tprintf(L"Waiting for child_process Exit\n");
  21. WaitForSingleObject(si.hProcess,INFINITE);
  22. //这个函数会阻塞程序运行,只有当si.hProcess进程推出后,
  23. //这个函数才返回。
  24. _tprintf(L"child_process Exited!\n");
  25. CloseHandle(si.hProcess);//知道这个函数被调用,代表这个子进程的内核对象,才可能被销毁。
  26. _gettchar();
  27. return 0;
  28. }

进程内核对象与进程

进程内核对象的生命周期,一定比进程生命周期长,进程没有退出,代表进程的进程内核对象一定不会被销毁!!!!当进程退出以后,也就是说,进程消失以后,进程内核对象才可能被销毁。
什么时候销毁这个进程内核对象呢?
当进程内核对象的引用计数为零时,销毁进程内核对象。当进程退出的时候,进程内核对象的状态会发生变化(进程内核对象会减1),直到进程内核对象的引用计数为0是退出。
我们把进程退出后,代表该进程的内核对象的状态教激发态!如果进程在运行,那么代表进程的内核对象处于非激发态。当进程处于非激发态时候,如果有类似WaitForSingleObject函数等待这个进程内核对象的话,这个WaitForSingleObject函数会被阻塞,直到进程退出,进程内核对象处于激发态。

参考文章: http://www.178linux.com/93744

结束一个进程

进程结束的方式:

  1. 进程的主线程返回(最好的方式)
    什么是进程的主线程?主线程就是Main,WinMain函数代表的线程。也是进程中的第一个线程。

无论这个进程还有多少其他线程,只要主线程一退出,那么这个进程就结束了!其他线程,自然就被系统杀 死,也就是其他线程自然终止!!!!

  1. 进程中的一个线程调用ExitProcess函数(不推荐)
    这个函数只能结束本进程,也就是说,这个函数只能把自己所在的进程干掉,其他进程他干不掉!这个函数,我们说这是一个同步函数,或者说这是一个阻塞函数。ExitProcess干完活以后,才返回。
  2. 其他进程中的某个线程调用TerminateProcess函数(不推荐)
    只要你有足够的权限,你就可以使用这个TerminateProcess函数,结束系统中的任意进程。但这个TerminateProcess函数是一个异步函数。
  3. 进程中的所有线程执行完毕(这个很少出现!!)

注意:TerminateProcess函数是一个异步函数。

当一个进程结束的时候,下面的动作一定被执行:

  1. 所有线程结束
  2. 所有用户GDI对象被释放,所有内核对象被关闭。
    例如画笔,背景刷,句柄,设备句柄,申请的内存等。
  3. 退出码被设置。(退出码就是main或者WinMain的返回值,或者ExitProcess,TerminateProcess函数给出的返回值)
    BOOL GetExitCodeProcess(
    HANDLE hProcess,
    PDWORD *pdwExitCode);
    如果进程依然在运行,那么这个函数返回STILL_ACTIVE。
  4. 进程内核对象的状态变为有信号状态。
    WaitForSingleObject(
    in HANDLE hHandle,
    in DWORD dwMillionseconds);
  5. 进程内核对象的计数减一。

子进程和父进程之间的父子关系:
父进程在创建完子进程后,立即关闭子进程的进程句柄
CloseHandle(ChildProcess_Handle);
CloseHandle(ChildThread_Handle);

  1. #include<Windows.h>
  2. #include<tchar.h>
  3. class A
  4. {
  5. public:
  6. A()
  7. {
  8. _tprintf(L"Constructor!\n");
  9. }
  10. ~A()
  11. {
  12. _tprintf(L"Destructor!\n");
  13. }
  14. };
  15. DWORD WINAPI ThreadProc(
  16. __in LPVOID lpParameter
  17. )
  18. {
  19. //_tprintf(L"Thread sleeping!\n");
  20. //Sleep(INFINITE);//让这个线程无限沉睡下去。
  21. _tprintf(L"the Thread will exit in two seconds\n");
  22. Sleep(2000);
  23. //ExitProcess(0);//退出本进程!
  24. return 0;
  25. }
  26. int _tmain()
  27. {
  28. /*
  29. DWORD dwThreadID=0;
  30. DWORD dwExitCode;
  31. CreateThread(NULL,0,ThreadProc,NULL,0,&dwThreadID);//创建一个线程
  32. _tprintf(L"the Sleeping Thread ID is %d\n",dwThreadID);
  33. _tprintf(L"exit 4828 process\n");
  34. _gettchar();
  35. HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,3068);
  36. TerminateProcess(hProcess,444);
  37. //这个函数是一个异步函数,所以,当这个函数返回的时候,进程可能还没有结束
  38. //所以,此时后的的返回值是:STILL_ACTIVE=259。所以,我们要等进程真正被
  39. //退出以后,再来获取退出码。
  40. WaitForSingleObject(hProcess,INFINITE);
  41. BOOL re=GetExitCodeProcess(hProcess,&dwExitCode);
  42. _tprintf(L"Exit Code is %d\n",dwExitCode);
  43. CloseHandle(hProcess);
  44. */
  45. {
  46. A a;
  47. ExitProcess(0);
  48. }
  49. _gettchar();
  50. return 0;
  51. }

程序中使用ExitProcess退出时容易出现错误

  1. //main程序
  2. #include<Windows.h>
  3. #include<tchar.h>
  4. int _tmain()
  5. {
  6. _tprintf(L"\n");
  7. TCHAR ws_Directory[MAX_PATH];
  8. GetCurrentDirectory(MAX_PATH,ws_Directory); //获取文件目录
  9. _tprintf(L"%s/\n",ws_Directory); //屏幕输出文件目录
  10. TCHAR str_Command[MAX_PATH]={0};
  11. STARTUPINFO start_info;
  12. PROCESS_INFORMATION info={0};
  13. //ZeroMemory(start_info, sizeof(start_info));
  14. start_info.cb = sizeof(start_info);
  15. while(CSTR_EQUAL != CompareStringOrdinal(str_Command,4,L"quit",4,TRUE))
  16. {
  17. _tprintf(L"%s/",ws_Directory);
  18. _getts_s(str_Command);
  19. CreateProcess(NULL,str_Command,NULL,NULL,FALSE,0,NULL,NULL,&start_info,&info);
  20. CloseHandle(info.hProcess);
  21. CloseHandle(info.hThread);
  22. }
  23. _tprintf(L"press any key to quit\n");
  24. _gettchar();
  25. return 0;
  26. }
  1. //子程序文件
  2. #include<Windows.h>
  3. #include<tchar.h>
  4. class A
  5. {
  6. public:
  7. A()
  8. {
  9. _tprintf(L"Constructor!\n");
  10. }
  11. ~A()
  12. {
  13. _tprintf(L"Destructor!\n");
  14. }
  15. };
  16. int _tmain()
  17. {
  18. _tprintf(L"this is Advanced_17 process\n");
  19. {
  20. A a;
  21. ExitProcess(0);//程序直接退出的话,C++销毁函数将不被调用!所以,这个函数不推荐使用。
  22. //goto LL;
  23. }
  24. _tprintf(L"press any key to exit Advanced_17 process\n");
  25. _gettchar();
  26. LL:
  27. return 0;
  28. }

image.png
如上图,只调用 了构造函数,没有调用析构函数,并没有销毁堆内存,容易造成内存泄露。
推荐使用goto
image.png
image.png