1.创建进程
实验目的
学会通过基本的Windows进程控制函数,由父进程创建子进程。
为什么要创建进程?
我们设计一个应用程序时,有时候需要另外一段代码做一些其他工作,方法如下:
- 函数调用或者子程序:
但是我们调用函数后只能等待函数返回。 - 在进程内创建一个新线程:
但在一个复杂应用系统中,我们有可能不慎改写了进程地址空间中的某些数据(例如某些引用数据),导致其它工作不能正确地进行。 - 创建新进程(子进程)
当我们需要某些工作同时进行,并且希望保护它们的运行地址空间时,一个很好地办法是创建一个新进程来执行需要同时进行的工作。
实验内容
假设现在有这样的一个工作,需要计算1——100的和,还需要做一个工作是读写文件。我们可以让父进程计算,创建一个子进程实现读写文件。
主要工作:
- 首先由父进程创建子进程;
- 让子进程创建一个文件并写入数据,子进程写文件过程中,父进程继续执行计算工作;
- 等子进程执行完以后,父进程读取文件内容输出,实现进程协同工作。
程序框架
父进程框架
int main()
{
//为创建进程做准备工作
//创建子进程
if(创建失败)
返回
else(创建成功)
//执行计算1——100的和
//等子进程执行完,读取子进程的文件内容,并输出。
}
子进程框架
int main()
{
//创建文件
if(失败)
返回
else(成功)
//向文件写入数据
//读取文件内容输出
}
需要用到的函数
CreateProcess
当调用CreateProcess时,系统所做的工作:
- 首先,系统为新进程创建一个进程内核对象,其初始使用计数为1;
(进程内核对象不代表进程本身,而是操作系统用来管理这个进程的一个数据结构) - 然后,系统为新进程创建一个虚拟地址空间,并将可执行文件(和所有必要的DLL)的代码和数据加载到进程的地址空间;
- 最后,系统为新进程创建一个主线程内核对象(使用计数为1);
(线程内核对象是操作系统用来管理线程的数据结构)
系统做完这些工作以后,新进程的主线程就开始执行运行时的启动代码,在启动代码中会调用应用程序的main函数,这样,新进程就从main函数开始执行。
如果调用CreateProcess函数后,系统成功创建了新进程并且创建了其主线程,则系统返回TRUE,否则返回FALSE。
参数
BOOL CreateProcess(
LPCTSTR lpApplicationName , //指定可执行程序名
LPTSTR lpCommandLine , //命令行字符串,可以为NULL
LPSECURITY_ATTRIBUTES lpProcessAttributes , //新进程对象的安全属性
LPSECURITY_ATTRIBUTES lpThreadAttributes , //新进程对应线程的安全属性
BOOL bInheritHandles ,//指定父进程的对象句柄是否能被子进程继承
DWORD dwCreationFlags , //指定创建进程的附加标记,即指定新创建进程的特性
LPVOID lpEnvironment ,//指定新进程使用的环境,NULL表示同父进程环境
LPCTSTR lpCurrentDirectory , //指定子进程当前路径,NULL表示与父进程路径相同
LPSTARTUPINFO lpStartupInfo , //指定新进程主窗口如何显示
LPPROCESS_INFORMATION lpProcessInformation //作为返回值使用,是一个指针
);
- LPCTSTR lpApplicationName
pApplicationName参数指定新进程要使用的可执行文件的名称。可以是完整路径和文件名,也可以是部分名称。在本次实验中,使用全路径和文件名。
(该参数可以为NULL,这时文件名必须是参数lpCommandLine 指向的字符串中第一个空格界定的标记。) - LPTSTR lpCommandLine
lpCommandLine参数用来指定传递给新进程的命令行字符串。在本次实验中,不需要这个参数,可以设为NULL。
(在很多时候,我们将可执行文件名和命令行参数都传给lpCommandLine参数,CreateProcess函数分析lpCommandLine参数时,会把该字符串中第一个空格分隔的标记作为可执行文件名,如果是可执行文件名是部分路径,则函数会在系统目录中按从下到上的顺序搜索可执行文件。) - LPSECURITY_ATTRIBUTES lpProcessAttributes
LPSECURITY_ATTRIBUTES lpThreadAttributes
lpProcessAttributes和lpThreadAttributes 都是指向指向LPSECURITY_ATTRIBUTES 结构体的指针。
前面介绍过,当调用CreateProcess函数创建新进程时,系统将为新进程创建一个进程内核对象和一个主线程内核对象。
lpProcessAttributes和lpThreadAttributes 参数就是分别用来设置新进程内核对象和主线程内核对象的安全属性。
在本次实验中为这两参数传递NULL,让系统为这两个对象赋予默认的安全描述符。 - BOOL bInheritHandles
bInheritHandles用来指定父进程随后创建的子进程是否能够继承父进程的对象句柄。如果该参数为TRUE,那么父进程的每个可继承打开句柄都能被子进程继承。
在本次实验中,把这个参数设置为FALSE,因为子进程不需要继承父进程的可继承句柄。 - DWORD dwCreationFlags
dwCreationFlags指定进程创建的附加标记,也可以用于控制新进程的优先级。如果只为了启动子进程,不需要设置创建标记,则直接设置为0。如果不需要为应用程序创建控制台窗口,则可以设置该参数为CREATE_NO_WINDOW。如果需要创建新控制台窗口,而不是继承父进程的控制台窗口,则设置为CREATE_NEW_CONSOLE,本次实验中设置为该标记。
(该参数可以取的创建标记很多,也可以用于设置新进程的优先级) - LPVOID lpEnvironment
lpEnvironment是一个指向环境块的指针,如果此参数是NULL,那么新进程使用调用进程的环境。通常都为此参数传递NULL。 - LPTSTR lpCurrentDirectory
lpCurrentDirectory参数是一个指向空终止的字符串,用来指定子进程当前的路径,这个字符串必须是一个完整的路径名,包括驱动器的标识符,如果此参数为NULL,那么新的子进程将与调用进程(父进程)拥有相同的驱动器和目录。 - LPSTARTUPINFO lpStartupInfo
lpStartupInfo是一个指向STARTUPINFO结构体的指针,用来指定新进程的主窗口将如何显示。
STARTUPINFO结构体成员比较多,并不需要为其所有成员都赋值。其中cb表示该结构体本身的大小,以字节为单位,通常都要为其cb成员赋值,否则函数调用会失败。在创建进程之前的准备工作就包括给该结构体变量赋值。
在本次实验中,不需要设置其它启动信息,所以直接给cb参数赋值就可以。
typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
……
……
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
STARTUPINFO sui;
ZeroMemory(&sui,sizeof(sui));
sui.cb=sizeof(STARTUPINFO);
CreateProcess(…,&si,…);
- LPPROCESS_INFORMATION lpProcessInformation
lpProcessInformation参数作为返回值使用,是一个指向PROCESS_INFORMATION结构体的指针,用来接收关于新进程的标志信息。
hProcess是新创建进程的句柄;
hThread是新创建进程的主线程句柄;
dwProcessId是全局进程标识符,用来标识一个进程;
dwThreadId是全局线程标识符,用来标识一个线程;
(当启动一个进程时,系统会为此进程分配一个标识符,同时这个进程中的线程也会被分配一个标识符,在一个进程运行时,该进程的标识符和线程的标识符是唯一的,停止后,这些标识符可能会被系统分配给其它进程和线程。)
在创建进程之前的另一项准备工作就是要定义PROCESS_INFORMATION 结构体变量,准备用于接收创建进程后返回的信息。
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
PROCESS_INFORMATION pi;
Sleep
因为1—100计算得太快,为了方便观察父进程和子进程系统工作的过程,可以在计算过程使用Sleep函数,强制让父进程的主线程休眠。
void Sleep( DWORD dwMilliseconds);//休眠一段时间,以ms为单位
Sleep(1000);//表示休眠1秒
WaitForSingleObject
等待子进程完成可以用WaitForSingleObject函数实现等待。
hHandle参数指定需要等待的对象句柄。
dwMilliseconds 参数指定需要等待的时间,以ms为单位。
0表示立即返回不等待。INFINITE表示等待直到对象句柄可用。
DWORD WaitForSingleObject(
HANDLE hHandle, //指定需要等待的对象句柄。
DWORD dwMilliseconds //指定需要等待的时间
);
文件操作
fopen
创建文件可以用fopen函数
FILE *fopen( //返回一个指向文件结构体的指针
const char *filename, //打开或创建的文件名
const char *mode //打开或创建方式,即设定读写权限
);
fopen函数既可以创建文件也可以打开已存在的文件
FILE *pFile=fopen("1.txt","w");
fwrite
文件写操作可以用fwrite函数
size_t fwrite(
const void *buffer, //指向被写入内容的文件指针
size_t size, //每次写入大小,字节为单位
size_t count, //写入次数
FILE *stream //一个指向文件结构体的指针,表示将去写操作的文件
);
fwrite("Xidian University",1,strlen("Xidian University"),pFile);
fread
读文件操作用fread函数
size_t fread(
const void *buffer, //指向被读取内容的文件指针
size_t size, //每次读取大小,字节为单位
size_t count, //读取次数
FILE *stream //一个指向文件结构体的指针,表示将要去读取操作的文件
);
fclose
在每次写入文件和读取文件操作后,关闭文件,使用fclose函数
int fclose( FILE *stream );
//关闭文件,参数是指向操作文件的文件结构体的指针
上代码
father.cpp
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
STARTUPINFO sui;
ZeroMemory(&sui,sizeof(sui));
sui.cb=sizeof(STARTUPINFO);
PROCESS_INFORMATION pi;
bool success=CreateProcess("child.exe",NULL,NULL,NULL,false,CREATE_NEW_CONSOLE,NULL,NULL,&sui,&pi);
if(!success)
{
cout<<"创建进程失败"<<endl;
return 0;
}
else
{
int sum=0;
for(int i=1;i<=100;i++)
{
sum+=i;
cout<<sum<<endl;
}
WaitForSingleObject(pi.hProcess,INFINITE);
cout<<"子进程结束"<<endl;
Sleep(2000);
cout<<"父进程读文件"<<endl;
FILE *file;
file=fopen("test.txt","r");
char c;
while(!feof(file))
{
c=fgetc(file);
cout<<c;
}
Sleep(2000);
fclose(file);
}
return 0;
}
child.cpp
#include<iostream>
#include<windows.h>
using namespace std;
int main()
{
cout<<"子进程执行"<<endl;
FILE *file;
cout<<"子进程写文件"<<endl;
file=fopen("test.txt","w+");
fprintf(file,"%s","hello world!");
fclose(file);
Sleep(2000);
return 0;
}