首页 > 代码库 > windows - 进程 - CreateProcess函数详解

windows - 进程 - CreateProcess函数详解

CreateProcess函数原型如下:

[cpp] view plaincopy
  1. BOOL CreateProcess(  
  2.     PCTSTR pszApplicationName,  
  3.     PTSTR pszCommandLine,  
  4.     PSECURITY_ATTRIBUTES psaProcess,  
  5.     PSECURITY_ATTRIBUTES psaThread,  
  6.     BOOL bInheritHandles,  
  7.     DWORD fdwCreate,  
  8.     PVOID pvEnvironment,  
  9.     PCTSTR pszCurDir,  
  10.     PSTARTUPINFO psiStartInfo,  
  11.     PPROCESS_INFORMATION ppiProcInfo);  


一、参数 PCTSTR pszApplicationName 和 参数PTSTR pszCommandLine

该参数类型为PTSTR,在函数运行过程中,CreateProcess函数会修改其值,但是在CreateProcess返回前又会复原为原来传入时的值.CreateProcess试图修改该字符串时,就会发生违规访问(较早的Visual C++版本将该字符串放入读/写内存,因此调用CreateProcess不会导致违规访问的问题)。
解决这个问题的最好办法是在调用CreateProcess之前像下面这样将常量字符串拷贝到临时缓存中:
//所以如下调用可能会出错:
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess(NULL, TEXT("NOTEPAD"), NULL, NULL,FALSE, 0, NULL, NULL, &si, &pi);

//正确的调用应该是先将其传给一个变量,然后将该变量作为参数传入:
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR szCommandLine[] = TEXT("NOTEPAD");
CreateProcess(NULL, szCommandLine, NULL, NULL,FALSE, 0, NULL, NULL, &si, &pi);
如果pszApplicationName 值为NULL时,CreateProcess函数就会解析pszCommandLine参数,pszCommandLine参数需要包含一个进程创建的所有参数,如新进程的可执行文件名。如果程序没有后缀名,默认的后缀名是.exe。CreateProcess函数会按照如下五种顺序来搜寻对应的程序名:
主调进程.exe文件的所在目录。
主调进程的的当前目录。
windows 系统目录,即GetSystemDirectory返回的System32子文件夹。
windows目录。
PATH环境变量中列出的目录。
如果如果pszApplicationName 值不为NULL时,你必须在pszApplicationName传入可执行文件名(包含后缀名)。并且没有指定可执行文件的路径,reateProcess函数假设可执行文件就在当前目录,如果没有找到,则函数直接返回FALSE,不会按照上面那五种目录进行查找。实例如下:
<span style="font-family:KaiTi_GB2312;">// Make sure that the path is in a read/write section of memory.
TCHAR szPath[] = TEXT("WORDPAD README.TXT");
// Spawn the new process.
CreateProcess(TEXT("C:\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE"),szPath,...);</span>

二、参数psaProcess, psaThread, 和 bInheritHandles

创建一个新进程时一个进程和一个基线程。由于两个都是Kernel Object,都需要有两个对应的PSECURITY_ATTRIBUTES的结构体。psaProcess 和 psaThread 分别指向这两个结构体。你可以通过设置这两个结构体的值来设置对应的子进程和基线程的安全属性,如果这两个值都为NULL时,则系统将赋予进程和基线程默认的安全描述符。父进程在以后再创建子进程时,通过把bInhertHandles参数设置为TRUE,就可以让新的子进程继承父进程所有可继承的句柄(handle)。为bInhertHandles传递FA L S E ,那么子进程 将不能继承父进程目前使用的任何句柄。一个实例如下:
/************************************************************
Module name: Inherit.c
Notices:Copyright(c)2000 Jeffrey Richter
************************************************************/
#include <Windows.h>

int WINAPI WinMain (HINSTANCE hinstExe,HINSTANCE,
   PSTR pszCmdLine,int nCmdShow)
{

   //Prepare a STARTUPINFO structure 
   //for spawning processes.
   STARTUPINFO si = { sizeof(si) };
   SECURITY_ATTRIBUTES saProcess,saThread;
   PROCESS_INFORMATION piProcessB,piProcessC;
   TCHAR szPath[MAX_PATH];

   //Prepare to spawn Process B from Process A.
   //The handle identifying the new process
   //object should be inheritable.
   saProcess.nLength = sizeof(saProcess);
   saProcess.lpSecurityDescriptor = NULL;
   saProcess.bInheritHandle = TRUE;

   //The handle identifying the new thread
   //object should NOT be inheritable.
   saThread.nLength = sizeof(saThread);
   saThread.lpSecurityDescriptor = NULL;
   saThread.bInheritHandle = FALSE;

   //Spawn Process B.
   lstrcpy(szPath,TEXT("ProcessB"));
   
   CreateProcess(NULL,szPath,
      &saProcess,&saThread,
      FALSE,0,NULL,NULL,&si,
      &piProcessB);

   //The pi structure contains two handles
   //relative to Process A:
   //hProcess,which identifies Process B's process
   //object and is inheritable; and hThread,
   //which identifies Process B's primary thread
   //object and is NOT inheritable.

   //Prepare to spawn Process C from Process A.
   //Since NULL is passed for the psaProcess and
   //psaThread parameters,the handles to Process C's
   //process and primary thread objects default
   //to "noninheritable."

   //If Process A were to spawn another process,
   //this new process would NOT inherit handles 
   //to Process C's process and thread objects.

   //Because TRUE is passed for the bInheritHandles
   //parameter,Process C will inherit the handle that
   //identifies Process B's process object but will 
   //not inherit a handle to Process B's primary
   //thread object.

   lstrcpy(szPath,TEXT("ProcessC"));
   
   CreateProcess(NULL,szPath,NULL,NULL,
      TRUE,0,NULL,NULL,&si,&piProcessC);

   return(0);
}

三、参数 fdwCreate

fdwCreate参数用于标识标志,以便用于规定如何来创建新进程。如果将标志逐位用O R 操作符组合起来的话,就可以设定多个标志
DEBUG_ PROCESS 用于告诉系统,父进程想要调试子进程和子进程将来生成的任何进程。本标志还告诉系统,当任何子进程(被调试进程)中发生某些事件时,将情况通知父进程(这时是调试程序)
DEBUG_ONLY_THIS_PROCESS标志与DEBUG_PROCESS标志相类似,差别在于,调试程序只被告知紧靠父进程的子进程中发生的特定事件。如果子进程生成了别的进程,那么将不通知调试程序在这些别的进程中发生的事件。
CREATE_SUSPENDED
可导致新进程被创建,但是,它的主线程则被挂起。这使得父进程能够修改子进程的地址空间中的内存,改变子进程的主线程的优先级,或者在进程有机会执行任何代码之前将进程添加给一个作业。一旦父进程修改了子进程,父进程将允许子进程通过调用ResumeThread函数来执行代码(第7章将作详细介绍)。
DETACHED_PROCESS
用于阻止基于CUI的进程对它的父进程的控制台窗口的访问,并告诉系统将它的输出发送到新的控制台窗口。如果基于CUI的进程是由另一个基于CUI的进程创建的,那么按照默认设置,新进程将使用父进程的控制台窗口(当通过命令外壳程序来运行C编译器时,新控制台窗口并不创建,它的输出将被附加在现有控制台窗口的底部)。通过设定本标志,新进程将把它的输出发送到一个新控制台窗口。
CREATE_NEW_CONSOLE
标志负责告诉系统,为新进程创建一个新控制台窗口。如果同时设定CREATE_NEW_CONSOLE和DETACHED_PROCESS标志,就会产生一个错误。
CREATE_NO_WINDOW
用于告诉系统不要为应用程序创建任何控制台窗口。可以使用本标志运行一个没有用户界面的控制台应用程序
CREATE_NEW_PROCESS_GROUP
用于修改用户在按下Ctrl+C或Ctrl+Break键时得到通知的进程列表。如果在用户按下其中的一个组合键时,你拥有若干个正在运行的CUI进程,那么系统将通知进程组中的所有进程说,用户想要终止当前的操作。当创建一个新的CUI进程时,如果设定本标志,可以创建一个新进程组。如果该进程组中的一个进程处于活动状态时用户按下Ctrl+C或Ctrl_Break键,那么系统只通知用户需要这个进程组中的进程。
CREATE_DEFAULT_ERROR_MODE
用于告诉系统,新进程不应该继承父进程使用的错误模式(参见本章前面部分中介绍的SetErrorMode函数)。
CREATE_UNICODE_ENVIRONMENT标志用于告诉系统,子进程的环境块应该包含Unicode字符。按照默认设置,进程的环境块包含的是ANSI字符串。
CREATE_FORCEDOS
用于强制系统运行嵌入16位OS/2应用程序的MOS-DOS应用程序
CREATE_BREAKAWAY_FROM_JOB
用于使作业中的进程生成一个与作业相关联的新进程
fdwCreate参数也可以用来设定优先级类。不过用不着这样做,并且对于大多数应用程序来说不应该这样做,因为系统会为新进程赋予一个默认优先级。下面显示了各种可能的优先级类别。
优先级类别

优先级类别标志的标识符
空闲             IDLE_PRIORITY_CLASS 
低于正常     BELOW_NORMAL_PRIORITY_CLASS 
正常            NORMAL_PRIORITY_CLASS 
高于正常    ABOVE_NORMAL_PRIORITY_CLASS 
高                HIGH_PRIORITY_CLASS 
实时            REALTIME_PRIORITY_CLASS 

四、参数 pvEnvironment

pvEnvironment指向子进程将要用到的环境变量的内存地址块。如果没有的话,就可以直接置为NULL
涉及到2个函数
PVOID GetEnvironmentStrings();
该函数用于获得调用进程正在使用的环境字符串数据块的地址。可以使用该函数返回的地址,作为CreateProc ess 的
pvEnvironment参数。如果为pvEnvironment参数传递N U L L ,那么这正是CreateProcess 函数所做的操作。当不再需要该内存块时,应该调用FreeEnvironmentStrings函数将内存块释放: 
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);

五、参数 pszCurDir

pszCurDir参数可以让CreateProcess函数设置子进程的当前驱动器和目录。如果传入时是NULL,则子进程的当前驱动器和目录和父进程一样。如果传入时不为NULL,则必须是一个以‘\0‘结束的合法的当前驱动器和目录。

六、参数 psiStartInfo

psiStartInfo是一个指向 STARTUPINFO或 STARTUPINFOEX结构体的指针, 一般都不用怎么设置,一般只要这样初始化第一个值就可以了。如:STARTUPINFO si = { sizeof(si) };
如果未能将该结构的内容初始化为零,那么该结构的成员将包含调用线程的堆栈上的任何无用信息。将该无用信息传递给CreateProcess ,将意味着有时会创建新进程,有时则不能创建新进程,完全取决于该无用信息。有一点很重要,那就是将该结构的未用成员设置为零,这样,CreateProcess 就能连贯一致地运行。
技术分享


七、参数 ppiProcInfo

ppiProcInfo 指向一个 PROCESS_INFORMATION 结构体,CreateProcess函数会对它进行初始化。
<span style="font-family:KaiTi_GB2312;">typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
}  PROCESS_INFORMATION;</span>


正如上面提到的,父进程创建子进程时,会创建一个子进程对象(process kernel object)和一个基线程对象(primary thread kernel object)。hProcess 和 hThread 分别记录子进程对象的句柄和父进程对象的句柄。dwProcessId 和 dwThreadId 分别记录这个两个对象的ID号。特别需要注意的是:当CreateProcess函数创建一个个子进程对象(process kernel object)和一个基线程对象(primary thread kernel object),系统就将这两个内核对象的初始引用值置为1。子进程会再打开这两个对象,此时它们的引用值就增加为 2 。也就是说如果要释放这两内核对象,则必须在父子进程中都要执行对应的CloseHandle函数。
注意必须关闭子进程和它的主线程的句柄,以避免在应用程序运行时泄漏资源。当然,当进程终止运行时,系统会自动消除这些泄漏现象,但是,当进程不再需要访问子进程和它的线程时,编写得较好的软件能够显式关闭这些句柄(通过调用CloseHandle函数来关闭)。
当进程内核对象创建后,系统赋予该对象一个独一无二的标识号,系统中的其他任何进程内核对象都不能使用这个相同的ID号。线程内核对象的情况也一样。当一个线程内核对象创建时,该对象被赋予一个独一无二的、系统范围的ID号。进程ID和线程ID共享相同的号码池。这意味着进程和线程不可能拥有相同的ID。另外,对象决不会被赋予0作为其ID。
系统无法记住每个进程的父进程的ID,但是,由于ID是被立即重复使用的,因此,等到获得父进程的ID时,该ID可能标识了系统中一个完全不同的进程。父进程可能已经终止运行。如果应用程序想要与它的“创建者”进行通信,最好不要使用ID。应该定义一个持久性更好的机制,比如内核对象和窗口句柄等。
若要确保进程ID或线程ID不被重复使用,唯一的方法是保证进程或线程的内核对象不会被撤消。如果刚刚创建了一个新进程或线程,只要不关闭这些对象的句柄,就能够保证进程对象不被撤消。一旦应用程序结束使用该ID,那么调用CloseHandle就可以释放内核对象,要记住,这时使用或依赖进程ID,对来说将不再安全。如果使用的是子进程,将无法保证父进程或父线程的有效性,除非父进程复制了它自己的进程对象或线程对象的句柄,并让子进程继承这些句柄。实例如下:
PROCESS_INFORMATION pi;DWORD dwExitCode;// Spawn the child process.BOOL fSuccess = CreateProcess(..., &pi);if (fSuccess) {// Close the thread handle as soon as it is no longer needed!CloseHandle(pi.hThread);// Suspend our execution until the child has terminated.WaitForSingleObject(pi.hProcess, INFINITE);// The child process terminated; get its exit code.GetExitCodeProcess(pi.hProcess, &dwExitCode);// Close the process handle as soon as it is no longer needed.CloseHandle(pi.hProcess);}
作者:locojyw
email:locojyw@outlook.com
欢迎大家交流,有什么错误请指出
转载注明出处


windows - 进程 - CreateProcess函数详解