一、实验一:
Windows进程管理
二、实验内容:
实验一包含三个小模块,一个一个分析
1. 清单1-1:
用devc++新建一个console application项目,运行指导书中的代码:
/* 实验一源程序 */
//清单1-1 一个简单的Windows控制台应用程序
// hello项目
# include <iostream>
int main()
{
std::cout << "Hello, Win32 Consol Application" << std::endl;
getchar();
}
显示内容:
2. 清单1-2:
用devc++新建一个console application项目,运行指导书中的代码:
//清单1-2 创建子进程
#include <windows.h>
#include <iostream>
#include <stdio.h>
// 创建传递过来的进程的克隆过程并赋于其ID值
void StartClone(int nCloneID)
{
// 提取用于当前可执行文件的文件名
TCHAR szFilename[MAX_PATH] ;
GetModuleFileName(NULL, szFilename, MAX_PATH) ;
// 格式化用于子进程的命令行并通知其EXE文件名和克隆ID
TCHAR szCmdLine[MAX_PATH];
sprintf(szCmdLine,"\"%s\" %d",szFilename,nCloneID);
// 用于子进程的STARTUPINFO结构
STARTUPINFO si;
ZeroMemory(&si , sizeof(si) ) ;
si.cb = sizeof(si) ; // 必须是本结构的大小
// 返回的用于子进程的进程信息
PROCESS_INFORMATION pi;
// 利用同样的可执行文件和命令行创建进程,并赋于其子进程的性质
BOOL bCreateOK=::CreateProcess(
szFilename, // 产生这个EXE的应用程序的名称
szCmdLine, // 告诉其行为像一个子进程的标志
NULL, // 缺省的进程安全性
NULL, // 缺省的线程安全性
FALSE, // 不继承句柄
CREATE_NEW_CONSOLE, // 使用新的控制台
NULL, // 新的环境
NULL, // 当前目录
&si, // 启动信息
&pi) ; // 返回的进程信息
// 对子进程释放引用
if (bCreateOK)
{
CloseHandle(pi.hProcess) ;
CloseHandle(pi.hThread) ;
}
}
int main(int argc, char* argv[] )
{
// 确定派生出几个进程,及派生进程在进程列表中的位置
int nClone=0;
//修改语句:int nClone;
//第一次修改:nClone=0;
if (argc > 1)
{
// 从第二个参数中提取克隆ID
:: sscanf(argv[1] , "%d" , &nClone) ; //用sscanf_s代替
}
//第二次修改:nClone=0;
// 显示进程位置
std :: cout << "Process ID:" << :: GetCurrentProcessId()
<< ", Clone ID:" << nClone
<< std :: endl;
// 检查是否有创建子进程的需要
const int c_nCloneMax=5;
if (nClone < c_nCloneMax)
{
// 发送新进程的命令行和克隆号
StartClone(++nClone) ;
}
// 等待响应键盘输入结束进程
getchar();
return 0;
}
显示结果:
代码分析:
这段代码实现了一个带有克隆功能的进程程序,可以创建多个子进程。
首先,我们来看一下这段代码中使用到的一些函数:
GetModuleFileName
:获取当前可执行文件的完整路径和文件名。它的参数包括一个模块句柄(通常为NULL表示当前进程)、一个输出缓冲区、以及缓冲区大小。
sprintf
:将格式化字符串输出到指定的字符数组中。它的参数包括一个输出缓冲区、格式化字符串以及若干个待输出的值。
ZeroMemory
:将指定内存区域置零。它的参数包括一个指针和要置零的字节数。
CreateProcess
:创建一个新的进程,并返回该进程的句柄和线程句柄。它的参数包括要执行的可执行文件名、命令行参数、安全性描述、环境变量、是否继承其父进程的句柄等信息。
CloseHandle
:关闭指定的对象句柄。在进程退出后需要调用该函数释放已经打开的句柄。接下来,我们来看一下代码的主要流程。首先,main函数会获取传递过来的参数(如果有),并解析出其中的克隆ID。然后,它会显示当前进程的ID和克隆ID,并检查是否需要创建子进程。如果克隆ID小于5,就会调用StartClone函数创建新的子进程,并将其克隆ID+1传递给StartClone函数。
StartClone函数负责创建子进程。它首先获取当前可执行文件的文件名,并格式化命令行参数,包括EXE文件名和克隆ID。然后,它使用CreateProcess函数创建一个新的进程,并指定要执行的EXE文件名和命令行参数等信息。最后,它关闭新进程的句柄和线程句柄。
总之,这段代码实现了一个简单的进程克隆程序,可以通过创建多个子进程来模拟并发执行。
需要注意的是,这里如果将进行注释中的第二次修改,子进程将会无限产生,为什么呢?
首先我们要了解main函数中参数argc,argv的意思:argc表示参数的个数,argv是存储所有参数,其中argv[0]为当前文件地址。当在用CreateProcess函数创建子进程时,子进程的参数argv[]是用szCmdLine传过来的,从szCdLine赋值代码sprintf(szCmdLine,"\"%s\" %d",szFilename,nCloneID);可知,空格分开的两个参数分别文件地址和nCloneID,即argv[1]的值为子进程ID,用语句:: sscanf(argv[1] , "%d" , &nClone) ;子进程的nClone初始化,从而实现累加,在nClone加到5后不在产生子进程。而第二次修改时在赋初值之后将nClone归零,子进程不断产生。
3. 清单1-3
用devc++新建一个console application项目,运行指导书中的代码:
//清单1-3 父子进程的简单通信及终止进程的示例程序
// procterm项目
# include <windows.h>
# include <iostream>
# include <stdio.h>
static LPCTSTR g_szMutexName = "w2kdg.ProcTerm.mutex.Suicide" ;
// 创建当前进程的克隆进程的简单方法
void StartClone()
{
// 提取当前可执行文件的文件名
TCHAR szFilename[MAX_PATH] ;
GetModuleFileName(NULL, szFilename, MAX_PATH) ;
// 格式化用于子进程的命令行,字符串“child”将作为形参传递给子进程的main函数
TCHAR szCmdLine[MAX_PATH] ;
//实验1-3步骤3:将下句中的字符串child改为别的字符串,重新编译执行,执行前请先保存已经完成的工作
sprintf(szCmdLine, "\"%s\" child" , szFilename) ;
// 子进程的启动信息结构
STARTUPINFO si;
ZeroMemory(&si,sizeof(si)) ;
si.cb = sizeof(si) ; // 应当是此结构的大小
// 返回的用于子进程的进程信息
PROCESS_INFORMATION pi;
// 用同样的可执行文件名和命令行创建进程,并指明它是一个子进程
BOOL bCreateOK=CreateProcess(
szFilename, // 产生的应用程序的名称 (本EXE文件)
szCmdLine, // 告诉我们这是一个子进程的标志
NULL, // 用于进程的缺省的安全性
NULL, // 用于线程的缺省安全性
FALSE, // 不继承句柄
CREATE_NEW_CONSOLE, //创建新窗口
NULL, // 新环境
NULL, // 当前目录
&si, // 启动信息结构
&pi ) ; // 返回的进程信息
// 释放指向子进程的引用
if (bCreateOK)
{
CloseHandle(pi.hProcess) ;
CloseHandle(pi.hThread) ;
}
}
void Parent()
{
// 创建“自杀”互斥程序体
HANDLE hMutexSuicide=CreateMutex(
NULL, // 缺省的安全性
TRUE, // 最初拥有的
g_szMutexName) ; // 互斥体名称
if (hMutexSuicide != NULL)
{
// 创建子进程
std :: cout << "Creating the child process." << std :: endl;
StartClone() ;
// 指令子进程“杀”掉自身
std :: cout << "Telling the child process to quit. "<< std :: endl;
//等待父进程的键盘响应
getchar() ;
//释放互斥体的所有权,这个信号会发送给子进程的WaitForSingleObject过程
ReleaseMutex(hMutexSuicide) ;
// 消除句柄
CloseHandle(hMutexSuicide) ;
}
}
void Child()
{
// 打开“自杀”互斥体
HANDLE hMutexSuicide = OpenMutex(
SYNCHRONIZE, // 打开用于同步
FALSE, // 不需要向下传递
g_szMutexName) ; // 名称
if (hMutexSuicide != NULL)
{
// 报告我们正在等待指令
std :: cout <<"Child waiting for suicide instructions. " << std :: endl;
//子进程进入阻塞状态,等待父进程通过互斥体发来的信号
WaitForSingleObject(hMutexSuicide, INFINITE) ;
//实验1-3步骤4:将上句改为WaitForSingleObject(hMutexSuicide, 0) ,重新编译执行
// 准备好终止,清除句柄
std :: cout << "Child quiting." << std :: endl;
CloseHandle(hMutexSuicide) ;
}
}
int main(int argc, char* argv[] )
{
// 决定其行为是父进程还是子进程
if (argc>1 && :: strcmp(argv[1] , "child" )== 0)
{
// std::cout<<argv[0]<<std::endl;
Child() ;
}
else
{
Parent() ;
}
return 0;
}
显示结果:
产生两个进程
当父进程出入时,杀死子进程。
这是一个使用Windows上的互斥体实现进程间通信和终止的C++程序。以下是主要函数和API的解释:
API 函数
GetModuleFileName()
:获取当前可执行文件的完整路径和文件名。sprintf()
:格式化字符串并将其写入缓冲区。ZeroMemory()
:用零填充内存块。CreateProcess()
:创建一个新的进程及其主线程。CloseHandle()
:关闭对象的打开句柄。CreateMutex()
:创建或打开一个命名或未命名的互斥体对象。OpenMutex()
:打开一个现有的命名或未命名的互斥体对象。WaitForSingleObject()
:等待指定的对象处于信号状态或超时间隔到期。ReleaseMutex()
:释放指定的互斥体对象的所有权。函数
StartClone()
:提取当前可执行文件的文件名,格式化命令行,并使用CreateProcess()
创建子进程。Parent()
:使用CreateMutex()
创建“自杀”互斥体,使用StartClone()
创建子进程,等待用户从键盘输入,使用ReleaseMutex()
释放互斥体的所有权,并使用CloseHandle()
关闭互斥体句柄和进程句柄。Child()
:使用OpenMutex()
打开“自杀”互斥体,使用WaitForSingleObject()
等待父进程的信号,打印一条消息指示它已准备好终止自身,并使用CloseHandle()
关闭互斥体句柄。main()
:根据传递给程序的命令行参数决定是执行Parent()
还是Child()
函数。在这个程序中,“自杀”互斥体用于向子进程发出终止自身的信号。父进程创建互斥体,创建子进程,等待用户从键盘输入,释放互斥体的所有权。子进程打开互斥体,等待父进程的信号,终止自身,并关闭互斥体句柄。
总的来说,这个程序演示了如何使用互斥体对象在Windows上创建和通信子进程和父进程之间的方法。
这里如果将WaitForSingleObject(hMutexSuicide, INFINITE) ;的INFINITE改成0,子进程将会被立即杀死,原因是子进程超时间隔到期。