转载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的有效性.
浙公网安备 33010602011771号