进程与多线程
作者:Ackarlix
一、进程的创建
一般将创建进程的称为父进程,被创建的进程称为子进程。系统在创建新的进程时会为新进程指定一个STARTUPINFO类型的变量,这个结构包含了父进程传递给子进程的一些显示信息。对界面应用程序来说,这些信息将影响进程中主线程的窗口显示;对控制台应用程序来说,将影响这个控制台程序的窗口。
STARTUPINFO
STARTUPINFO结构定义如下:
typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
参数解析:
DWORD cb; //包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,它
//可用作版本控制手段。应用程序须将cb初始为sizeof(STARTUPINF/O)
PSTR lpReserved; //保留。必须初始化为NULL
PSTR lpDesktop; //用于标识启动应用程序所在的桌面的名字。如果该桌面存在,新进程便与 //指定的桌面相关联。如果桌面不存在,便创建一个带有默认属性的桌面
//并使用为新进程指定的名字。如果lpDesktop是NULL
//(这是最常见的情况),那么该进程将与当前桌面相关联
PSTR lpTitle; //用于设定控制台窗口的名称。如lpTitle是NULL,则可执行文件的名字将
//用作窗口名
DWORD dwX; //用于设定应用程序窗口在屏幕上应该放置的位置的x 和y 坐标(以像素为
//单位)。
DWORD dwY; //只有当子进程用CW_USEDEFAULT作为CreateWindow的x参数创建
//它的第一个重叠窗口时,才使用这两个坐标。若是创建控制台窗口的应用
//程序,这些成员用于指明控制台窗口的左上角
DWORD dwXSize; //用于设定应用程序窗口的宽度和长度(以像素为单位)只有dwYsize
DWORD dwYSize; //当子进程将CW_USEDEFAULT用作Create Window的nWidth参数来
//建它的第一个重叠窗口时,才使用这些值。若是创建控制台窗口的应用程
//序,这些成员将用于指明控制台窗口的宽度
DWORD dwXCountChars; //用于设定子应用程序的控制台窗口的宽度和高度(以字符为单位)
DWORD dwYCountChars;
DWORD dwFillAttribute; //用于设定子应用程序的控制台窗口使用的文本和背景颜色
DWORD dwFlags; //请参见下一段和表4-7的说明
表4-7 dwFlags 使用标志及含义
标志 含义
STARTF_USESIZE // 使用dwXSize和dwYSize成员
STARTF_USESHOWWINDOW //使用wShowWindow成员
STARTF_USEPOSITION //使用dwX和dwY成员
STARTF_USECOUNTCHARS //使用dwXCountChars和dwYCountChars 成员
STARTF_USEFILLATTRIBUTE //使用dwFillAttribute成员
STARTF_USESTDHANDLES //使用hStdInput、hStdOutput和hStdError成员
STARTF_RUN_FULLSCREEN //强制x86计算机上运行的控制台应用程序以全屏方式启动运行
WORD wShowWindow; //用于设定如果子应用程序初次调用的ShowWindow将
//SW_SHOWDEFAULT作为nCmdShow参数传递,该应用程序的第一个
//重叠窗口应该如何出现。本成员可以是通常用于ShowWindow函数的任
//何一个SW_*标识符
WORD cbReserved2; //保留。必须被初始化为0
PBYTE lpReserved2; //保留。必须被初始化为NULL
HANDLE hStdInput; //用于设定供控制台输入和输出用的缓存的句柄。按照默认设置,
//hStdInput用标识键盘缓存,hStdOutput和hStdError用于标识控制台窗
//口的缓存
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
一个进程可以用GetStartupInfo函数来取得父进程创建自己所用的STARTUPINFO结构。定义一个STARTUPINFO结构对象以后,总要在使用对象之前将对象的cb成员初始化为STARTUPINFO结构大小。
如:
STARTUPINFO si = {sizeof(si)};
::GetStartupInfo(&si);
CreateProcess函数
CreateProcess函数创建一个新的进程和该进程的主线程。新的进程在父进程的安全上下文中运行指定的可执行文件。
原型:
BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
参数解析:
lpApplicationName:指向一个NULL结尾的、用来指定可执行模块的字符串。 这个字符串可以使可执行模块的绝对路径,也可以是相对路径,在后一种情况下,函数使用当前驱动器和目录建立可执行模块的路径。 这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpCommandLine 参数的最前面并由空格符与后面的字符分开。 这个被指定的模块可以是一个Win32应用程序。如果适当的子系统在当前计算机上可用的话,它也可以是其他类型的模块(如MS-DOS 或 OS/2)。
lpCommandLine:指向一个NULL结尾的、用来指定要运行的命令行。 这个参数可以为空,那么函数将使用参数指定的字符串当作要运行的程序的命令行。 如果lpApplicationName和lpCommandLine参数都不为空,那么lpApplicationName参数指定将要被运行的模块,lpCommandLine参数指定将被运行的模块的命令行。新运行的进程可以使用GetCommandLine函数获得整个命令行。C语言程序可以使用argc和argv参数。 如果lpApplicationName参数为空,那么这个字符串中的第一个被空格分隔的要素指定可执行模块名。如果文件名不包含扩展名,那么.exe将被假定为默认的扩展名。如果文件名以一个点(.)结尾且没有扩展名,或文件名中包含路径,.exe将不会被加到后面。如果文件名中不包含路径,Windows将按照如下顺序寻找这个可执行文件:
1. 当前应用程序的目录。
2. 父进程的目录。
3. Windows 95:Windows系统目录,可以使用GetSystemDirectory函数获得。 Windows NT:32位Windows系统目录。可以使用GetSystemDirectory函数获得,目录名是SYSTEM32。
4. 在Windows NT中:16位Windows系统目录。不可以使用Win32函数获得这个目录,但是它会被搜索,目录名是SYSTEM。
5. Windows目录。可以使用GetWindowsDirectory函数获得这个目录。
6. 列在PATH环境变量中的目录。
如果被创建的进程是一个以MS-DOS或16位Windows为基础的应用程序,lpCommandLine参数应该是一个以可执行文件的文件名作为第一个要素的绝对路径,因为这样做可以使32位Windows程序工作的很好,这样设置lpCommandLine参数是最强壮的。
lpProcessAttributes:指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承。如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。
在Windows NT中:SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员指定了新进程的安全描述符,如果参数为空,新进程使用默认的安全描述符。
在Windows95中:SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员被忽略。
lpThreadAttributes:指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承。如果lpThreadAttributes参数为空(NULL),那么句柄不能被继承。
在Windows NT中,SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员指定了主线程的安全描述符,如果参数为空,主线程使用默认的安全描述符。
在Windows95中:SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员被忽略。
bInheritHandles:指示新进程是否从调用进程处继承了句柄。如果参数的值为真,调用进程中的每一个可继承的打开句柄都将被子进程继承。被继承的句柄与原进程拥有完全相同的值和访问权限。
dwCreationFlags:指定附加的、用来控制优先类和进程的创建的标志。以下的创建标志可以以除下面列出的方式外的任何方式组合后指定。
值:CREATE_DEFAULT_ERROR_MODE
含义:新的进程不继承调用进程的错误模式。CreateProcess函数赋予新进程当前的默认错误模式作为替代。应用程序可以调用SetErrorMode函数设置当前的默认错误模式。
这个标志对于那些运行在没有硬件错误环境下的多线程外壳程序是十分有用的。
对于CreateProcess函数,默认的行为是为新进程继承调用者的错误模式。设置这个标志以改变默认的处理方式。
值:CREATE_NEW_CONSOLE
含义:新的进程将使用一个新的控制台,而不是继承父进程的控制台。这个标志不能与DETACHED_PROCESS标志一起使用。
值:CREATE_NEW_PROCESS_GROUP
含义:新进程将使一个进程树的根进程。进程树种的全部进程都是根进程的子进程。新进程树的用户标识符与这个进程的标识符是相同的,由lpProcessInformation参数返回。进程树经常使用GenerateConsoleCtrlEvent函数允许发送CTRL+C或CTRL+BREAK信号到一组控制台进程。
值:CREATE_SEPARATE_WOW_VDM
含义:(只适用于Windows NT)这个标志只有当运行一个16位的Windows应用程序时才是有效的。如果被设置,新进程将会在一个私有的虚拟DOS机(VDM)中运行。另外,默认情况下所有的16位Windows应用程序都会在同一个共享的VDM中以线程的方式运行。单独运行一个16位程序的优点是一个应用程序的崩溃只会结束这一个VDM的运行;其他那些在不同VDM中运行的程序会继续正常的运行。同样的,在不同VDM中运行的16位Windows应用程序拥有不同的输入队列,这意味着如果一个程序暂时失去响应,在独立的VDM中的应用程序能够继续获得输入。
值:CREATE_SHARED_WOW_VDM
含义:(只适用于Windows NT)这个标志只有当运行一个16位的Windows应用程序时才是有效的。如果WIN.INI中的Windows段的DefaultSeparateVDM选项被设置为真,这个标识使得CreateProcess函数越过这个选项并在共享的虚拟DOS机中运行新进程。
值:CREATE_SUSPENDED
含义:新进程的主线程会以暂停的状态被创建,直到调用ResumeThread函数被调用时才运行。
值:CREATE_UNICODE_ENVIRONMENT
含义:如果被设置,由lpEnvironment参数指定的环境块使用Unicode字符,如果为空,环境块使用ANSI字符。
值:DEBUG_PROCESS
含义:如果这个标志被设置,调用进程将被当作一个调试程序,并且新进程会被当作被调试的进程。系统把被调试程序发生的所有调试事件通知给调试器。
如果你使用这个标志创建进程,只有调用进程(调用CreateProcess函数的进程)可以调用WaitForDebugEvent函数。
值:DEBUG_ONLY_THIS_PROCESS
含义:如果此标志没有被设置且调用进程正在被调试,新进程将成为调试调用进程的调试器的另一个调试对象。如果调用进程没有被调试,有关调试的行为就不会产生。
值:DETACHED_PROCESS
含义:对于控制台进程,新进程没有访问父进程控制台的权限。新进程可以通过AllocConsole函数自己创建一个新的控制台。这个标志不可以与CREATE_NEW_CONSOLE标志一起使用。
dwCreationFlags参数还用来控制新进程的优先类,优先类用来决定此进程的线程调度的优先级。如果下面的优先级类标志都没有被指定,那么默认的优先类是NORMAL_PRIORITY_CLASS,除非被创建的进程是IDLE_PRIORITY_CLASS。在这种情况下子进程的默认优先类是IDLE_PRIORITY_CLASS。
可以下面的标志中的一个:
优先级:HIGH_PRIORITY_CLASS
含义:指示这个进程将执行时间临界的任务,所以它必须被立即运行以保证正确。这个优先级的程序优先于正常优先级或空闲优先级的程序。一个例子是Windows任务列表,为了保证当用户调用时可以立刻响应,放弃了对系统负荷的考虑。确保在使用高优先级时应该足够谨慎,因为一个高优先级的CPU关联应用程序可以占用几乎全部的CPU可用时间。
优先级:IDLE_PRIORITY_CLASS
含义:指示这个进程的线程只有在系统空闲时才会运行并且可以被任何高优先级的任务打断。例如屏幕保护程序。空闲优先级会被子进程继承。
优先级:NORMAL_PRIORITY_CLASS
含义:指示这个进程没有特殊的任务调度要求。
优先级:REALTIME_PRIORITY_CLASS
含义:指示这个进程拥有可用的最高优先级。一个拥有实时优先级的进程的线程可以打断所有其他进程线程的执行,包括正在执行重要任务的系统进程。例如,一个执行时间稍长一点的实时进程可能导致磁盘缓存不足或鼠标反映迟钝。
lpEnvironment:指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。 一个环境块存在于一个由以NULL结尾的字符串组成的块中,这个块也是以NULL结尾的。每个字符串都是name=value的形式。因为相等标志被当作分隔符,所以它不能被环境变量当作变量名。 与其使用应用程序提供的环境块,不如直接把这个参数设为空,系统驱动器上的当前目录信息不会被自动传递给新创建的进程。对于这个情况的探讨和如何处理,请参见注释一节。 环境块可以包含Unicode或ANSI字符。如果lpEnvironment指向的环境块包含Unicode字符,那么dwCreationFlags字段的CREATE_UNICODE_ENVIRONMENT标志将被设置。如果块包含ANSI字符,该标志将被清空。 请注意一个ANSI环境块是由两个零字节结束的:一个是字符串的结尾,另一个用来结束这个快。一个Unicode环境块石油四个零字节结束的:两个代表字符串结束,另两个用来结束块。
lpCurrentDirectory:指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为空,新进程将使用与调用进程相同的驱动器和目录。这个选项是一个需要启动启动应用程序并指定它们的驱动器和工作目录的外壳程序的主要条件。
lpStartupInfo:指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。
lpProcessInformation:指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。
另外CreateProcess函数除了创建一个进程,还创建一个线程对象。这个线程将连同一个已初始化了的堆栈一起被创建,堆栈的大小由可执行文件的文件头中的描述决定。线程由文件头处开始执行。
新进程和新线程的句柄被以全局访问权限创建。对于这两个句柄中的任一个,如果没有安全描述符,那么这个句柄就可以在任何需要句柄类型作为参数的函数中被使用。当提供安全描述符时,在接下来的时候当句柄被使用时,总是会先进行访问权限的检查,如果访问权限检查拒绝访问,请求的进程将不能使用这个句柄访问这个进程。
这个进程会被分配给一个32位的进程标识符。直到进程中止这个标识符都是有效的。它可以被用来标识这个进程,或在OpenProcess函数中被指定以打开这个进程的句柄。进程中被初始化了的线程一样会被分配一个32位的线程标识符。这个标识符直到县城中止都是有效的且可以用来在系统中唯一标识这个线程。这些标识符在PROCESS_INFORMATION结构体中返回。
当在lpApplicationName或lpCommandLine参数中指定应用程序名时,应用程序名中是否包含扩展名都不会影响运行,只有一种情况例外:一个以.com为扩展名的MS-DOS程序或Windows程序必须包含.com扩展名。
调用进程可以通过WaitForInputIdle函数来等待新进程完成它的初始化并等待用户输入。这对于父进程和子进程之间的同步是极其有用的,因为CreateProcess函数不会等待新进程完成它的初始化工作。举例来说,在试图与新进程关联的窗口之前,进程应该先调用WaitForInputIdle。
首选的结束一个进程的方式是调用ExitProcess函数,因为这个函数通知这个进程的所有动态链接库(DLLs)程序已进入结束状态。其他的结束进程的方法不会通知关联的动态链接库。注意当一个进程调用ExitProcess时,这个进程的其他县城没有机会运行其他任何代码(包括关联动态链接库的终止代码)。
ExitProcess, ExitThread, CreateThread, CreateRemoteThread,当一个进程启动时(调用了CreateProcess的结果)是在进程中序列化进行的。在一段地址空间中,同一时间内这些事件中只有一个可以发生。这意味着下面的限制将保留:
*在进程启动和DLL初始化阶段,新的线程可以被创建,但是直到进程的DLL初始化完成前它们都不能开始运行。
*在DLL初始化或卸下例程中进程中只能有一个线程。
*直到所有的线程都完成DLL初始化或卸下后,ExitProcess函数才返回。
在进程中的所有线程都终止且进程所有的句柄和它们的线程被通过调用CloseHandle函数终止前,进程会留在系统中。进程和主线程的句柄都必须通过调用CloseHandle函数关闭。如果不再需要这些句柄,最好在创建进程后立刻关闭它们。
当进程中最后一个线程终止时,下列的事件发生:
*所有由进程打开的对象都会关闭。
*进程的终止状态(由GetExitCodeProcess函数返回)从它的初始值STILL_ACTIVE变为最后一个结束的线程的结束状态。
*主线程的线程对象被设置为标志状态,供其他等待这个对象的线程使用。
*进程对象被设置为标志状态,供其他等待这个对象的线程使用。
假设当前在C盘上的目录是/MSVC/MFC且有一个环境变量叫做C:,它的值是C:/MSVC/MFC,就像前面lpEnvironment中提到过的那样,这样的系统驱动器上的目录信息在CreateProcess函数的lpEnvironment参数不为空时不会被自动传递到新进程里。一个应用程序必须手动地把当前目录信息传递到新的进程中。为了这样做,应用程序必须直接创建环境字符串,并把它们按字母顺序排列(因为Windows NT和Windows 95使用一种简略的环境变量),并把它们放进lpEnvironment中指定的环境块中。类似的,他们要找到环境块的开头,又要重复一次前面提到的环境块的排序。
一种获得驱动器X的当前目录变量的方法是调用GetFullPathName("x:",..)。这避免了一个应用程序必须去扫描环境块。如果返回的绝对路径是X:/,就不需要把这个值当作一个环境数据去传递了,因为根目录是驱动器X上的新进程的默认当前目录。
由CreateProcess函数返回的句柄对于进程对象具有PROCESS_ALL_ACCESS的访问权限。
由lpcurrentDirectory参数指定的当前目录室子进程对象的当前目录。lpCommandLine参数指定的第二个项目是父进程的当前目录。
对于Windows NT,当一个进程在指定了CREATE_NEW_PROCESS_GROUP的情况下被创建时,一个对于SetConsoleCtrlHandler(NULL,True)的调用被用在新的进程上,这意味着对新进程来说CTRL+C是无效的。这使得上层的外科程序可以自己处理CTRL+C信息并有选择的把这些信号传递给子进程。CTRL+BREAK依旧有效,并可被用来中断进程/进程树的执行。
附录:
导入库:kernel32.lib
头文件:Winbase.h
CreateProcess创建的子进程的命令行参数
用CreateProcess创建的子进程所获得的命令行参数有以下几种情况:
1.子进程中,WinMain函数的第三个参数lpCmdLine表示的命令行参数中除去应用程序路径、文件名以及与参数相隔的空格等字符串后的内容。比如
父进程:
CreateProcess(NULL, “c://test.exe -p“, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
子进程中,lpCmdLine参数为-p,注意没有双引号
2.如果通过lpCmdLine参数查看命令行参数,那么父进程创建子进程的时候,需要在子程序和参数中加上空格号。比如:
父进程:
CreateProcess(“c://test.exe”,“ -p“, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
子进程中,lpCmdLine参数为-p,没有空格了。
3.如果想获得子进程的完整命令行参数,调用GetCommandLine函数。通常通过GetCommandLine函数获得的命令行参数,是父进程调用时的完整参数。
CreateProcess(NULL, “c://test.exe -p“, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
子进程中,GetCommandLine返回的参数为c://test.exe -p,注意没有双引号
以下情况都是调用GetCommandLine函数获得命令行参数
4.CreateProcess第一个参数是NULL,第二个参数指定子应用程序和命令行参数,那么子进程的命令行参数是第二个参数,不带双引号。比如:
父进程:
CreateProcess(NULL, “c://test.exe -p“, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
子进程中,GetCommandLine获得的命令行参数为c://test.exe -p
5.CreateProcess第一个参数指定子应用程序路径和文件名,第二个参数为NULL,那么子进程的命令行参数是第一个参数,带双引号。比如:
父进程:
CreateProcess(“c://test.exe“, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
子进程中,GetCommandLine获得的命令行参数为”c://test.exe“
6.CreateProcess第一个参数指定子应用程序路径和文件名,第二个参数为传递给子进程的参数,那么子进程的命令行参数是第二个参数,不带双引号。比如:
父进程:
CreateProcess(“c://test.exe“, ”-p“, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
子进程中,GetCommandLine获得的命令行参数为-p。
此处注意同第二点比较,如果用lpCmdLine查看命令行参数,没什么都没有。但是将”-p”改成” -p”,用可以看到参数是-p,是不是很混乱?
怎样启动另一个可执行文件
1 CreateProcess
#include <windows.h>
void main( void)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line).
"D://Program Files//FlashGet//flashget.exe", // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi ) // Pointer to PROCESS_INFORMATION structure.
)
{
//ErrorExit( "CreateProcess failed." );
}
// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
2 WinExec
UINT WinExec(
LPCSTR lpCmdLine,
UINT uCmdShow
);
3 ShellExcute
ShellExecute(NULL,"open","http://yange.yao68.net",NULL,NULL,SW_SHOW);
Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=1431967
CreateProcess API函数
利用CreateProcess API函数来创建相应的进程,该函数的原型如下:
CreateProcess(
LPCWSTR lpszImageName, //指向可执行的模块的指针
LPCWSTR lpszCmdLine, //指向可执行命令行字符串的指针
LPSECURITY_ATTRIBUTES lpsaProcess, //CE 不支持
LPSECURITY_ATTRIBUTES lpsaThread, //CE 不支持
BOOL fInheritHandles, //CE 不支持
DWORD fdwCreate, //创建标志
LPVOID lpvEnvironment, //CE 不支持
LPWSTR lpszCurDir, //CE 不支持
LPSTARTUPINFOW lpsiStartInfo, //CE 不支持
LPPROCESS_INFORMATION lppiProcInfo //指向进程信息结构体的指针
);
该函数最重要的两个参数是lpszImageName和lpszCmdLine,下面的几个应用也主要是通过设置这两个参数来达到目的
1、打开指定的URL
TCHAR szAppName[_MAX_PATH] = TEXT("iexplore.exe");//IE浏览器程序
TCHAR szCmdLine[_MAX_PATH] = TEXT("http://www.cnblogs.com");
CreateProcess(szAppName,szCmdLine, NULL, NULL,FALSE, 0, NULL, NULL, NULL, NULL);
2、打开指定的文件夹
TCHAR szAppName[_MAX_PATH] = TEXT("fexplore.exe");//资源管理器程序
TCHAR szCmdLine[_MAX_PATH] = TEXT(//Windows//);
CreateProcess(szAppName,szCmdLine, NULL, NULL,FALSE, 0, NULL, NULL, NULL, NULL);
3、打开帮助文件
PROCESS_INFORMATION pi;
TCHAR szAppName[_MAX_PATH] = TEXT("peghelp.exe");//系统帮助程序,PC上是才c:/WINDOWS目下的winhlp32.exe
TCHAR szCmdLine[_MAX_PATH] = TEXT("myhelp.html");//自己制作的帮助文档,甚至是系
//统原有的帮助文件,如wince.htm、bluetooth.htm等等。myhelp.html应该放在/Windows目录下
CreateProcess(szAppName,szCmdLine, NULL, NULL,FALSE, 0, NULL, NULL, NULL, &pi);
通过以上的方式,可以在自己的应用程序里打开相应的帮助文件。
从以上的3种应用可以看出,只要知道系统自带的应用程序名,就可以通过设定szAppName和szCmdLine相应的值,来调用相应的程序,来达到自己想要的目的。只要认真挖掘,一定可以发现更多的应用。
CreateProcess API函数的妙用
我们可以利用CreateProcess API函数来创建相应的进程,该函数的原型如下:
CreateProcess(
LPCWSTR lpszImageName, //指向可执行的模块的指针
LPCWSTR lpszCmdLine, //指向可执行命令行字符串的指针
LPSECURITY_ATTRIBUTES lpsaProcess, //CE 不支持
LPSECURITY_ATTRIBUTES lpsaThread, //CE 不支持
BOOL fInheritHandles, //CE 不支持
DWORD fdwCreate, //创建标志
LPVOID lpvEnvironment, //CE 不支持
LPWSTR lpszCurDir, //CE 不支持
LPSTARTUPINFOW lpsiStartInfo, //CE 不支持
LPPROCESS_INFORMATION lppiProcInfo //指向进程信息结构体的指针
);
该函数最重要的两个参数是lpszImageName和lpszCmdLine,下面的几个应用也主要是通过设置这两个参数来达到目的。(以下的程序在Pocket PC 2003 SE上通过测试)
1、打开指定的URL
TCHAR szAppName[_MAX_PATH] = TEXT("iexplore.exe");//IE浏览器程序
TCHAR szCmdLine[_MAX_PATH] = TEXT("http://www.cnblogs.com");
CreateProcess(szAppName,szCmdLine, NULL, NULL,FALSE, 0, NULL, NULL, NULL, NULL);
2、打开指定的文件夹
TCHAR szAppName[_MAX_PATH] = TEXT("fexplore.exe");//资源管理器程序
TCHAR szCmdLine[_MAX_PATH] = TEXT(//Windows//);
CreateProcess(szAppName,szCmdLine, NULL, NULL,FALSE, 0, NULL, NULL, NULL, NULL);
3、打开帮助文件
PROCESS_INFORMATION pi;
TCHAR szAppName[_MAX_PATH] = TEXT("peghelp.exe");//系统帮助程序,PC上是在c:/WINDOWS目下的winhlp32.exe
TCHAR szCmdLine[_MAX_PATH] = TEXT("myhelp.html");//自己制作的帮助文档,甚至是系
//统原有的帮助文件,如wince.htm、bluetooth.htm等等。myhelp.html应该放在/Windows目录下
CreateProcess(szAppName,szCmdLine, NULL, NULL,FALSE, 0, NULL, NULL, NULL, &pi);
通过以上的方式,可以在自己的应用程序里打开相应的帮助文件。
从以上的3种应用可以看出,只要知道系统自带的应用程序名,就可以通过设定szAppName和szCmdLine相应的值,来调用相应的程序,来达到自己想要的目的。只要认真挖掘,一定可以发现更多的应用。
Windows 2000系统编程——进程的创建
1 引言
新一代操作系统——Windows 2000是一个具有完整功能的环境,与其他操作系统相比完成同样的任务Windows 2000更出色。编程人员要编写系统层次应用程序和用户应用程序掌握Windows 2000中的进程管理至关重要。下面对Windows 2000进程的创建作详细论述。以便使读者从中掌握Windows 2000的特性,编写更有效和更有用的程序。
2 使用WinExec命令
⑴ 函数原型:
UINT Win Exec(LPCSTR lpCmdLine, UINT uCmdShow);
⑵ 参数:
lpCmdLine:指向一个空结束的字符串,串中包含将要执行的应用程序的命令行(文件名加上可选参数)。
uCmdShow:定义Windows应用程序的窗口如何显示,并为CreateProcess函数提供STARTUPINFO参数的wShowWindow成员的值。
⑶ 返回值:
若函数调用成功,则返回值大于31。若函数调用失败,则返回值为下列之一:
① 0:系统内存或资源已耗尽。
② ERROR_BAD_FORMAT:EXE文件无效(非Win32.EXE或.EXE影像错误)。
③ ERROR_FILE_NOT_FOUND:指定的文件未找到。
④ ERROR_PATH_NOT_FOUND:指定的路径未找到。
虽然Microsoft认为WinExec已过时,但是在许多时候,简单的WinExec函数仍是运行新程序的最好方式。简单地传送作为第一个参数的命令行,还需要决定如何显示程序(该程序也许会忽视它)的第二个参数。通常,将其设置为SW_SHOW,也可尝试SW_MINIMIZED或SW_MAXIMIZED。WinExec不允许用CreateProcess获得的所有选项,而它的确简单。
3 使用ShellExecute命令
⑴ 函数原型:
HINSTANCE ShellExecute(HWND hwnd, LPCTSTR lpOperation, LPCTSTR lpFile, LPCTSTR lpParameters, LPCTSTR lpDirectory, INT nShowCmd);
⑵ 参数:
hwnd:指向父窗口的窗口句柄。此窗口接收应用程序产生的任何信息框。
lpOperation:一个空结束的字符串地址,此字符串指定要执行的操作。下面的操作字符串是有效的:
"open" |
此函数打开由参数lpFile指定的文件,此文件可以是一个可执行文件或文档文件,也可是一个文件夹。 |
"print" |
此函数打印由参数lpFile指定的文件,此文件应是一个文档文件,假如此文件是一个可执行文件,则打开此文件。 |
"explore" |
此函数搜索由参数lpFile指定的文件夹,此文件应是一个文档文件, |
此参数可以为空。这种情况下,函数用于打开由参数lpFile指定的文件。
lpFile:一个空结束的字符串地址,此字符串指定要打开或打印的文件或者是要打开或搜索的文件夹。
lpParameters:假如参数lpFile指定一个可执行文件,lpParameters则是一个空结束的字符串地址,此字符串指定要传递给应用程序的参数。假如lpFile指定一个文档文件,lpParameters应为空。
lpDirectory:一个空结束的字符串地址,此字符串指定默认目录。
nShowCmd:假如lpFile指定一个可执行文件,nShowCmd表明应用程序打开时如何显示。假如lpFile指定一个文档文件,nShowCmd应为空。
⑶ 返回值:
若函数调用成功,则返回值大于32,否则为一个小于等于32的错误值。
说明:可以用此函数打开或搜索一个外壳文件夹。打开文件夹可用下面任何一种形式:
ShellExecute(handle, NULL, path_to_folder, NULL, NULL, SW_SHOWNORMAL);
或
ShellExecute(handle, "open", path_to_folder, NULL, NULL, SW_SHOWNORMAL);
搜索文件夹,可用如下形式
ShellExecute(handle, "explore", path_to_folder, NULL, NULL, SW_SHOWNORMAL);
ShellExecute命令虽已过时但易于得到。该命令向命令解释程序提出打开、浏览或打印文档或文件夹的请求,虽然可以用ShellExecute运行程序,但通常只发送文档名,而命令解释程序则决定要运行那个程序。另外在打开目录文件夹时,ShellExecute命令非常有用。
⑷ 程序示例
下面通过一个例子来说名WinExec和ShellExecute的使用。下面程序有控制台程序示例,其使用两种不同的方法,打开文本文件。下面程序使用WinExec,并明确指定使用记事本程序。然后,使用ShellExecute,打开文本文件。
程序清单
#include <windows.h>
#include <iostream.h>
void main(int argc,char *argv[])
{
cout<<”Opening with WinExec/n”;
if (WinExec(“notepad readme.txt”,SH_SHOW)<32)
MessagBox(NULL,”Can’t WinExec”,NULL,MB_OK);
cout<<”Press Enter/n”;
MessagBox(NULL,”Press OK to continue”,”Progrm Launched”,MB_OK);
cout<<”Opening with ShellExecute/n”;
if (ShellExecute (NULL,”open”,
”readme.txt”,NULL,NULL,SW_SHOW)<(HANDLE) 32)
MessagBox(NULL,”Can’t ShellExecute/n”,NULL,MB_OK);
}
4 使用CreateProcess命令
⑴ 函数原型:
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
⑵ 参数:
lpApplicationName:指向一个以空结尾的串,他指定了要执行的模块
lpCommandLine:指向一个以空结尾的串,该串定义了要执行的命令行。
lpProcessAttributes:指向一个SECURITY_ATTRIBUTES结构,该结构决定了返回的句柄是否可被子进程继承。
lpThreadAttributes:指向一个SECURITY_ATTRIBUTES结构,该结构决定了返回的句柄是否可被子进程继承。
bInheritHandles,:表明新进程是否从调用进程继承句柄。
dwCreationFlags:定义控制优先类和进程创建的附加标志。
lpEnvironment:指向一个新进程的环境块。
lpCurrentDirectory:指向一个以空结尾的串,该串定义了子进程的当前驱动器和当前目录。
lpStartupInfo:指向一个STARTUPINFO结构,该结构定义了新进程的主窗口将如何显示。
lpProcessInformation:指向PROCESS_INFORMATION结构,该结构接受关于新进程的表示信息。
⑶ 返回值:
若函数调用成功,则返回值不为0;若函数调用失败,返回值为0。
ShellExecute和WinExec命令用于简单的作业。如果要完全控制一个新进程,就必须调用CreateProcess。
在上述参数中,参数lpStartupInfo是STARTUPINFO结构。可以用来设置控台的标题,新窗口的的初始大小和位置,及重定向标准输入和输出。新程序通常可以忽略多数这些数据项,如果选择那样做的话。可以规定该结构体中的标志,已表明要设置的数据段。有时,不想设置任何信息,也必须传递一个有效的指针给空结构(确定设置大小到cb,及设置dwFlags成员为0)。参数lpProcessInformation返回进程和线程句柄,还包括进程和线程ID。这些句柄拥有在参数lpProcessAttributes和lpThreadAttributes中规定的访问。
要注意,针对CreateProcess的一些参数对控制台应用程序是特定的,而其它参数则对各种应用程序有用。大多数情况下,并不一定要填入STARTUPINFO结构,但无论如何必须提供它。其返回值是布尔型的,而真正感兴趣的返回值发生于作为参数传送的结构中(PROCESS_INFORMATION)。CreateProcess返回该结构中的进程ID及其句柄,以及初始线程ID及其句柄。可以将ID发送到其它进程,或使用句柄来控制新进程。
⑷ 相关命令
给定进程句柄,就可以用相关命令来控制进程。下面我们讨论进程结束的确定,进程结束的确定有以下几种方法:
① 调用GetExitCodeProcess
命令GetExitCodeProcess既能返回STILL_ACTIVE,也能返回进程退出值(如果进程结束时)返回值需要一个指针,其指向命令填充的变量。
② 调用WaitForSingleObject
WaitForSingleObject的目的是要确定句柄是否处于发送信号的状态。当进程结束时,进程句柄发出信号。当调用WaitForSingleObject时,就规定进程句柄和超时值,如果超时为0,则该命令就立刻返回,且能够确定进程的状态。如果超时是常数INFINITE,则命令就不返回,直到目标进程退出为止。当然,还可以规定超时值,其导致该命令等待要结束的进程一段时间。如果进程在超时届满前结束,该命令就返回,并指出句柄在发射信号状态。否则,就返回一个负值。不管句柄在何种状态,WaitForSingleObject将成功返回,没有错误发生。要确定进程的状态,就必须比较返回值为WAIT_OBJECT_0(已发信号的)和WAIT_TIMEOUT(未发信号的)。真正的错误返回值为WAIT_FAILED。另外可能的返回值是WAIT_ABANDONED,是不会看到何时处理进程。要等待一个进程,就必须带有SYNCHRONIZE特权的打开局柄。
这里要注意,进程ID与进程句柄不同。不能简单地在进程之间传送句柄,这意味着除非有句柄,否则不能从外部进程直接操纵一个进程。不过OpenProcess命令将允许任何程序(有足够的安全特权)将进程标示符(可以用来于其它进程通信)变换为进程句柄。通过调用GetCurrentProcessId,还可以了解当前进程标示符。如果要想与其他无关的进程共享,以使能够打开进程句柄,这是非常有用的。但调用OpenProcess时,可以请求对进程的访问。对每种进程的访问,也许有或也许没有访问要打开进程的安全性,于是试图请求是仅仅需要的。例如,如果要了解进程的返回代码,就需要PROCESS_QUERY_INFORMATION的访问。要终止进程,就必须有PROCESS_TERMINATE的访问。
⑸ 程序示例
下面通过一个例子来说名CreateProcess和相关命令的使用。下面程序是两个简单的控制台应用程序,第一个程序(MASTER)运行第二个程序(SLAVE),并进入睡眠。SLAVE程序从命令行读取源程序的进程ID(PID),并等待MASTER程序终止。这些程序说明了以下几个重要技术:
l 使用CreateProcess
l 使用OpenProcess
l 使用WaitForSingleObject
程序清单 MASTER程序
#include <windows.h>
#include <iostream.h>
#include <stdio.h>
#include <string.h>
void main(int argc,char *argv[])
{
char cmd[128];
if (argc!=1)
strcpy(cmd,argv[1]);
else
strcpy(cmd,”slave.exe”);
int pid=GetCurrentProcessId();
sprintf(cmd+strlen(cmd),” %d”,pid);
cout<<”Master: Starting:”<<cmd<<”/n”);
cout.flush();
STARTUPINFO info;
memset(&info,0,sizeof(info));
info.cb=sizeof(info);
PROCESS_INFORMATION pinfo;
If(!CreateProcess(NULL,cmd,NULL,NULL,FALSE<NORMAL_PRIORITY_CLASS,NULL,NULL,&info,&pinfo))
{
c out<<”Master:Slave process did not start/n”;
c out<<” Master:Try naming slave process on the command line/n”;
}
cout<<”Master:Sleeping/n”;
cout.flush();
Sleep(15000);
Cout<<”Master:Exiting/n”;
exit(0);
}
程序清单 SLAVE程序
#include <window.h>
#include <iostream.h>
#include <stdio.h>
void main(int argc,char *argv[])
{
if (argc!=2)
{
cout<<”Slave:Please rrun MASTER.EXE instead./n”;
exit(1);
}
int pid=atoi(argv[1]);
HANDLE process=OpenProcess(PROCESS_QUERY_INFORMATION|SYNCHRONIZE,FALSE,pid);
if (!process) cot<<”Slave:Error opening process/n”;
cout<<”Slave :Waiting for master to finish/n”;
cout.flush();
if (WaitForSingleObject(process,INFINITE)==STAUTE_WAIT_0)
cout<<”Slave:Master completed/n”;
else
cout<<”Slave:Unexpected error/n”;
exit(0);
}