李sir_Blog

博客园 首页 联系 订阅 管理

转载http://hi.baidu.com/csw8923/blog/item/0a6fe71086ce270b213f2e8c.html

CreateProcess 函数
    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.参数 pszApplicationName
    传入的是一个非"常量字符串"地址。CreateProcess实际上会修改我们传给
    它的命令行字符串。在CreateProcess返回之前,它会将这个字符串还原为原本形式。
   
    命令行字符串包含文件映像的只读部分,会引起访问违规。
   
    在C/C++编译器把"NOTEPAD"字符串放在只读内存:
    STARTUPINFO si = { sizeof(si) };
   PROCESS_INFORMATION pi;
   CreateProcess(NULL, TEXT("NOTEPAD"), NULL, NULL,
   FALSE, 0, NULL, NULL, &si, &pi);

   CreateProcess 在试图修改字符串,会引起一个访问违规..所以,我们在调用 CreateProcess
   之前,把常量字符串复制到一个临时缓冲区,如下所示:
  
   STARTUPINFO si = { sizeof(si) };
   PROCESS_INFORMATION pi;
   TCHAR szCommandLine[] = TEXT("NOTEPAD");
   CreateProcess(NULL, szCommandLine, NULL, NULL,
   FALSE, 0, NULL, NULL, &si, &pi);
  
   另外,在Windows Vista 中调用 CreateProcess 函数ANSI版本,不会发生访问违规的。
   
2.使用 pszCommandLine 参数是用来指定一个完整的命令行,供 CreateProcess用于创建新
进程。
当CreateProcess解析pszCommandLine,字符串时,会检查字符串中第一个标记。
如果可执行文件的名称没有扩展名,就会默认是.exe扩展名。CreateProcess 会按照以下顺序搜索
可执行文件。
(1)主调进程.exe文件所在的目录
(2)主调进程的当前目录。
(3)Windows系统目录,即GetSystemDirectory返回的System32子文件件。
(4)Windows目录。
(5)PATH环境变量中列出的目录。
文件名如果包含一个完整路径.. 那么系统会利用完整路径来查找可执行文件,而不会搜索目录。
如果系统找到了可执行文件,就创建一个新进程,将可执行文件代码和数据映射到新进程地址空间。
将可执行文件名之后的第一个实参地址传给(w)WinMain的pszCmdLine参数。
     只要pszApplicationName参数为NULL(99%以上的情况是如此)就会发生上述情况。
     也可以不再 pszApplicationName 中传递NULL,而是传递一个字符串地址,并在字符串中包含
     想要运行的可执行文件名称。 这情况下,必须指定文件扩展名,系统不会自动假定文件名有.exe扩展名。
     CreateProcess文件位于当前目录,除非文件名前有一个路径。
   没有在当前目录中找到文件,CreateProcess不会再其他任何目录查找文件--调用会以失败告终。
   如果在 pszApplicationName 参数中指定文件名,CreateProcess也会将 szCommandLine 参数中
   的内容作为新进程的命令行传给它。 如下面这样调用 CreateProcess;
     // 请确保路径是在可在内存中读/写。
          TCHAR szPath[] = TEXT("WORDPAD README.TXT");

     // 创建一个新的进程.
          CreateProcess(TEXT("C:\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE"),szPath,...);

3. psaProcess, psaThread和blnheritHandles参数
   这些都是内核对象函数,所以其创建的父进程都有机会将安全属性关联到这两个对象上。
   可根据自己需要分别使用 psaProcess和psaThread参数来为进程对象和线程对象指定安全性。
   也可以为这两个参数传递NULL;系统会为这两个内核对象指定默认的安全描述符。
   并分配好初始化的两个SECURITY_ATTRIBUTES结构,以创建安全权限,并将它们分配给进程对象和线程对象。
  
   另外,blnheritHandles 参数可以为子进程继承父进程可访问的一些内核权限,如果将 blnheritHandles设置为TRUE
   那么,子进程就可以继承父进程所有可继承的句柄。
  
   实例代码:
  
   Inherit.cpp

/************************************************************
Module name: Inherit.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************/


#include <Windows.h>


int WINAPI _tWinMain (HINSTANCE hInstanceExe, HINSTANCE,
   PTSTR pszCmdLine, int nCmdShow) {

   // 准备一个产生相关进程的 STARTUPINFO 结构。
   STARTUPINFO si = { sizeof(si) };
   SECURITY_ATTRIBUTES saProcess, saThread;
   PROCESS_INFORMATION piProcessB, piProcessC;
   TCHAR szPath[MAX_PATH];

   // 准备产生一个可以让进城B继承的进程A .
  
   // 创建一个 新的进程
   // 对象是应该可以继承的。
   saProcess.nLength = sizeof(saProcess); // 进程
   saProcess.lpSecurityDescriptor = NULL;
   saProcess.bInheritHandle = TRUE; // 可继承

   // 句柄确定后创建一个 新的线程
   // 对象是不能继承的.
   saThread.nLength = sizeof(saThread);   // 线程
   saThread.lpSecurityDescriptor = NULL;
   saThread.bInheritHandle = FALSE; // 不可继承

   // 创建一个进程 B
   _tcscpy_s(szPath, _countof(szPath), TEXT("ProcessB"));
   CreateProcess(NULL, szPath, &saProcess, &saThread, // 执行 CreateProcess 函数创建一个进程B
      FALSE, 0, NULL, NULL, &si, &piProcessB);

   // 创建一个进程 C
   _tcscpy_s(szPath, _countof(szPath), TEXT("ProcessC"));
   CreateProcess(NULL, szPath, NULL, NULL, // 执行 CreateProcess 函数创建一个进程C
      TRUE, 0, NULL, NULL, &si, &piProcessC);

   return(0);
}

4.2.3 fdwCreate 参数

fdwCreate 参数 标识了影响新进程创建方式的标志(flag),多个标志可以使用按位或起来,以便同时指定多个标志组合.

DEBUG_PROCESS 父进程希望调试子进程以及子进程将来生成的所有进程.

DEBUG_ONLY_THIS_PROCESS 标志类似于DEBUG_PROCESS,只有在关系最近的子进程中发生特定事件时,父进程才会得到通知.

CREATE_SUSPENDED 标志让系统在创建新进程的同时挂起其主线程.
父进程就可以修改子进程地址空间中的内存,更改子进程的主线程的优先级,或在进程执行任何代码之前,将此进程添加到一个作业中.
调用ResumeThread函数来允许子进程执行代码.

DETACHED_PROCESS 阻止一个基于CUI的进程访问其父进程的控制台窗口,并告诉系统将它的输出发送到一个新的控制台窗口,通过指定这个标志,\新进程如果需要将输出发送到一个新的控制台窗口,就必须调用AllocConsole函数来创建它自己的控制台.

CREATE_NEW_CONSOLE标志指示系统为新进程创建一个新的控制台窗口.

CREATE_NEW_CONSOLE 和 DETACHED_PROCESS标志,会导致一个错误.

CREATE_NO_WINDOW 标志指示系统不要为应用程序创建任何控制台窗口.使用这个标志来执行没有用户界面的控制台应用程序.

CREATE_NEW_PROCESS_GROUP修改用户按Ctrl+C或Ctrl+Break时或得通知的进程列表.告诉它们用户打算中断当前操作.
组中的一个进程处于活动状态时,一旦用户按下组合键Ctrl+C或Ctrl+Break,系统只是向这个组的进程发出通知.

CREATE_DEFAULT_ERROR_MODE向系统表明新进程不会继承父进程所用的错误模式.

DREATE_SEPATE_WOW_VDM 虚拟DOS机.

CREATE_SHARED_WOW_VDM 所有16位Windows应用程序都在单独一个VDM中运行的,指定CREATE_SEPARATE_WOW_VDM标志.
不过,也可以覆盖这个默认行为.

DefaultSeparateVDM的值设为yes.如果设置CREATE_SHARED_WOW_VDM标志,16位Windows应用程序就会在系统的共享VDM中运行.
为了检查在64位操作系统下运行的32位进程,我们可以调用IsWow64Process函数.一个参数是我们要检测的进程的句柄,
第二参数则是指向一个布尔值的指针:一个32位进程在64位操作系统下运行,这个值就会被设为TRUE;否则会设为FALSE.

CREATE_UNICODE_ENVIRONMENT 系统子进程的环境块应包含Unicode字符.默认包含是ANSI字符串.

CREATE_FORCEDOS强制系统运行一个嵌入在16位OS/2应用程序中的MS-DOS应用程序.

CREATE_BREAKAWAY_FROM_JOB允许一个作用中的进程生成一个和作业无关的进程.

EXTENED_STARTUPINFO_PRESENT向操作系统表明传给psiStartInfo参数的一个STARTUPINFOEX结构.

fdwCreate参数还允许我们指定一个优先级类-系统会为新进程分配一个默认的优先级类.

4.2.4 PvEnrironment 参数

   该参数指向的是一块内存,包含新进程要使用环境字符串。
参数一般可以传入默认值NULL 它能使子进程继承父进程使用的一组环境字符串。
另外我们还可以使用GetEnvironmentString函数:
PVOID GetEnvironmentStrings();
获取主调进程正使用的环境字符串数据块地址,
然后再将函数返回到CreateProcess中的pvEnvironment参数其作用与传入NULL是一样的。
当不再使用GetEnvironmentStrings时可使用FreeEnvironmentStrings函数来释放它。
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);

4.2.5 PszpszCurDir参数

参数允许父进程和子进程再相同的工作目录
     NULL - 允许
不为NULL - 则自己指定一条路径(须用0终止符,且带驱动器号)

4.2.6 psiStartInfo

参数是指向一个STARTUPINFO 结构或 STARTUPINFOEX 结构
使用该参数结构时不使用的成员应初始为0
cb成员是设为结构的大小
如果,不能把结构内容清零可能会出现,不能创建进程的情况
这一般是因为主调线程的栈上可能包括垃圾数据。

STARTUPINFO 结构或 STARTUPINFOEX 结构声明:

typedef struct _STARTUPINFO {
   DWORD cb;      
   // 包含 STARTUPINFO 结构中的字节数。充当版本控制,应该初始化为sizeof(STARTUPINFO) 或 sizeof(STARTUPINFOEX).
   PSTR lpReserved; // 保留。必须初始化为NULL
   PSTR lpDesktop;
   // 标示一个名称,表明再哪个桌面上启动应用程序 值填写为NULL为当前默认桌面。
   PSTR lpTitle; // 控制台窗口标题,设置NULL用文件名为默认标题。
   DWORD dwX;
   DWORD dwY;
   //控制程序的屏幕位置,子过程用 CW_USEDEFAULT 作为 CreateWindow函数x参数来创建
   //其第一个重叠窗口的时候,才会使用这些坐标。
   DWORD dwXSize;
   DWORD dwYSize;
   // 应用程序窗口的宽度和高度。只有再子进程将CW_USEDEFAULT作为CreateWindow函数
   // 的nWidth参数来创建其第一个重叠窗口的时候。这些成员指定是控制台窗口宽度和高度。
   DWORD dwXCountChars;
   DWORD dwYCountChars;
   // 子进程的控制台窗口的宽度和高度(字符数标示)。
   DWORD dwFillAttribute; // 子进程控制台窗口所用的文本和背景色
   DWORD dwFlags;    // 参见下面解释
   WORD wShowWindow; // 应用程序如何显示主窗口
   //第一个 ShowWindow 调用 wShowWindow 的值,并忽略ShowWindow的nCmdShow参数。
   //在后续的ShowWindow函数的前提下,才会使用wShowWindow的值。
   //dwFlags指定了STARTF_USESHOWWINDOW标志,否则 wShowWindow会被忽略。
   WORD cbReserved2; // 保留。初始化话为0.
   PBYTE lpReserved2; //保留。初始化话为 NULL
   HANDLE hStdInput;
   HANDLE hStdOutput;
   HANDLE hStdError;
   //指定控制台输入缓冲的句柄和输出缓冲区的句柄。
   // 默认情况下hStdInput标示益而高键盘缓冲区,hStdPutput和hStdError标示一个控制台窗口的缓冲区,重定向子进程的输入/输出。
} STARTUPINFO, *LPSTARTUPINFO;

typedef struct _STARTUPINFOEX {
    STARTUPINFO StartupInfo;
    struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttributeList;
} STARTUPINFOEX, *LPSTARTUPINFOEX;


关于:dwFlages标志

标志                                                   含义

STARTF_USESIZE                         使用 dwXSize 和 dwYSize 成员.
STARTF_USESHOWWINDOW     使用 wShowWindow 成员.
STARTF_USEPOSITION              使用 dwX and dwY 成员.
STARTF_USECOUNTCHARS     使用 dwXCountChars and dwYCountChars 成员.
STARTF_USEFILLATTRIBUTE     使用 dwFillAttribute 成员.
STARTF_USESTDHANDLES     使用 hStdInput, hStdOutput, and hStdError 成员.
STARTF_RUNFULLSCREEN      使x86计算机上运行的一个控制台应用程序以全屏模式启动.

两外两个标志,STARTF_FORCEONFEEDBACK 和 STARTF_FORCEOFFFEEDBACK它们可以
在启动一个新进程时控制鼠标指针。
即,我们启动一个程序时,CreateProcess临时将系统鼠标指针改为特殊形状。
另外CreateProcess也有许多其他的控制方式:

STARTF_FORCEOFFFEEDBACK 标志 -- 不会把指针改为上述形状
STARTF_FORCEONFEEDBACK   标志 -- 会令CreateProcess监视新进程的初始化过程,并根据结构更改光标形状。
两秒后,如果新进程没有执行任何GUI调用,CreateProcess就会将光标重置为普通箭头形状。

可以将wShowwindow函数标识符传给(w)Winmain.函数最后参数nCmdshow的值。
通常 可向nCmdshow值传入如下标示:
      SW_SHOWNORMAL -- 常规显示窗口
      SW_SHOWMINNOACTIVE -- 最小化显示窗口
      SW_SHOWDEFAULT -- 最大化显示窗口
对CreateProcess 函数属性进行扩展时,可使用STARTUPINFOEX结构所提供的除 STARTUPINFO
字段外的lpAttributeList字段。

typedef struct _STARTUPINFOEXA {
    STARTUPINFOA StartupInfo;
    struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
typedef struct _STARTUPINFOEXW {
    STARTUPINFOW StartupInfo;
    struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttributeList;
} STARTUPINFOEXW, *LPSTARTUPINFOEXW;

改属性字段包含有两个属性键值:

PROC_THREAD_ATTRIBUTE_HANDLE_LIST
这个属性键可告诉CreateProcess子进程究竟应该继承哪些内核对象句柄。
子进程只能继承一组选定句柄,而不是继承的句柄。

PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
这个属性键值是一个句柄,它可以指定进程继承的属性,包括科继承
句柄,处理器关联系、优先级、配额、用户令牌,以及关联的作业。

不会改变调试器进程和被调试进程的关系。
   由于上面属性并不是透明的,所以要调用以下两次函数,才能创建一个空白的属性列表。
  
   BOOL InitializeProcThreadAttributeList(
   PPROC_THREAD_ATTRIBUTE_LIST pAttributeList,
   DWORD dwAttributeCount,
   DWORD dwFlags,
   PSIZE_T pSize);
  
   注意,其中dwFlays参数是被保留的,一般初始化为0.
   第一次调用函数目的是知道Windows用来保存属性内存块的大小;
  
   SIZE_T cbAttributeListSize = 0;
   BOOL bReturn = InitializeProcThreadAttributeList(
   NULL, 1, 0, &cbAttributeListSize);
   // bReturn is FALSE but GetLastError() returns ERROR_INSUFFICIENT_BUFFER
  
   Psize指向SIZE_T变量将按收到内存块的大小值,这内存块
   是根据dwAttributeCout所指定的属性的数目来分配。
  
   pAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)
   HeapAlloc(GetProcessHeap(), 0, cbAttributeListSize);

   bReturn = InitializeProcThreadAttributeList(
   pAttributeList, 1, 0, &cbAttributeListSize);
  
   在属性列表分配内存之后,再调用InitializeProcThreadAttributeList来初始化
   它的内容(这些内容"不透明"的);

   BOOL UpdateProcThreadAttribute(
   PPROC_THREAD_ATTRIBUTE_LIST pAttributeList,
   DWORD dwFlags,
   DWORD_PTR Attribute,
   PVOID pValue,
   SIZE_T cbSize,
   PVOID pPreviousValue,
   PSIZE_T pReturnSize);
  
   pAttributeList 参数分配并初始化的Attribute列表,
   函数将在其中添加一个新的键/值对:Attribute参数,
   其中键一部分要么按

   PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 标记
   pValue -- 须指向一个变量,包含新的父进程句柄
   cbSize -- 应用sizeof(HANDLE)来作为它的值。
  
   PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 标记
   pValue -- 须指向一个数组起始位,数组包含运行子进程的内核对象句柄。
   cbSize -- 应用sizeof(HANDLE)来作为它的数。
            (0)        (NULL)           (NULL)
   另外有 dwFlags, pPreviousValue, 和 pReturnSize 保留必须参数。
   同时传入
   PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 关联的新的父进程中。
   PROC_THREAD_ATTRIBUTE_HANDLE_LIST 关联的句柄必须是有效的
  
   这些句柄将从指定新进程继承,而不是从调用CreateProcess函数的当前进程继承。
   清除不透明的属性列表,释放其已分配好相关内存。

   VOID GetStartupInfo(LPSTARTUPINFO pStartupInfo);
   
   4.2.7 ppiProclnfo参数

   指向的是一个PROCESS.INFORMATION结构.
   GreateProcess函数会在返回前,初始化这个结构成员.
   typedef struct _PROCESS_INFORMATION {
   HANDLE hProcess;
   HANDLE hThread;
   DWORD dwProcessId;
   DWORD dwThreadId;
   } PROCESS_INFORMATION;

   一个进程分配到创建一个线程内核对象,对象就会被分配一个独一无二的ID号.
   分配到的ID号不会是0,Windows任何管理器会将进程ID为0与"systemIdleprocess"分配给它,即系统空闲进程,
   systemIdleprocess中线程数量始终等于计算机CPU数量.
  
   且它始终代表未被使用的CPU使用率.
   CreateProcess中线程数量始终等于计算机CPU数量,且它始终代表未被使用的CPU使用率.
   CreateProcess返回前,它会将这些ID填充到PROCESS_INFORMATION结构中的
   dwProcessId和dwThreadId成员中.
   ID使我们很容易识别系统中的进程和线程.
   如何获取或跟踪进程和线程ID,
   GetCurrentProcessId - 得到当前ID
   GetCurrentThreadId - 得到正运行线程ID
   GetProcessId - 获取指定句柄对应进程ID
   GetTheadId - 获取指定句柄相对应线程ID
   GetProcessIdofThrend - 跟据线程句柄获取其所在进程ID.
  
   另外,我们可利用ToolHelp函数来获取子进程的父进程.
   进程通过PROCEssENTRY32 结构查询父进程.
   结构内部一个th32ParentProcessID成员些结构内部的th32ParemtprocessID成员能返回父进程ID但是,请记住父子进
   程的关系只存在刚建立的时刻,但进程建立后它们就没关系了.因此,应用程序需要与它的"创建者"通信,最好不用ID,
   因为系统ID会被重用(即你所找到ID进程未必是真正的有血缘关系进程)
   所以,要定义一个如内核对象或窗口句柄来建立通信机制会更好.
   保证一个进程或线程ID不被重用方法是保证进程和线程对像不被销毁.
   对于子进程,除非父进程复制自己的进程或线程对象句柄,并允许子进程继承这些这些句柄,
   否则它无法确保父进程的进程ID或线程线程ID的有效性.

posted on 2010-06-03 13:50  李sir  阅读(1977)  评论(0)    收藏  举报