进程 线程 DLL.docx
《进程 线程 DLL.docx》由会员分享,可在线阅读,更多相关《进程 线程 DLL.docx(72页珍藏版)》请在冰豆网上搜索。
进程线程DLL
第9章进程、线程和DLL操作技巧
本章介绍了进程的相关概念和对进程的各种操作,以及进程间通信的方式。
进程通常被定义为程序运行的实例,它一般包括两部分,即进程内核和进程地址空间。
进程是不活泼的,若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,该线程负责执行包含在进程的地址空间中的代码。
线程的操作要注意线程对资源的同步和互斥的情况。
最后阐述了动态链接库的创建及调用方式。
9.1 进程的操作技巧
在操作系统的教材中,都学习过程序和进程的概念:
程序是一段代码,它是一个静态的概念,进程是程序的一次执行,它是一个动态的概念,进程具有生命周期,在其生命周期期间,具有不同的状态:
新建、运行、阻塞、就绪和完成5个状态。
在多任务操作系统中,可以同时运行多个进程,每个进程都有自己的虚拟地址空间。
进程在自己的地址空间中修改数据不会影响其他的进程。
操作系统为了识别不同的进程,为每个进程分配一个进程标识。
我们如何去创建一个进程,如何在进程之间进行通信呢?
下面将会解决这些疑问。
9.1.1 进程的概念
进程是当前操作系统下一个被加载到内存的、正在运行的应用程序的实例。
每一个进程都是由内核对象和地址空间所组成的,内核对象可以让系统在其内存放有关进程的统计信息并使系统能够以此来管理进程,而地址空间则包括了所有程序模块的代码和数据,以及线程堆栈、堆分配空间等动态分配的空间。
进程仅仅是一个存在,是不能独自完成任何操作的,必须拥有至少一个在其环境下运行的线程,并由其负责执行在进程地址空间内的代码。
在进程启动的同时即同时启动了一个线程,该线程被称作主线程或执行线程,由此线程可以继续创建子线程。
如果主线程退出,那么进程也就没有存在的可能了,系统将自动撤销该进程并完成对其地址空间的释放。
加载到进程地址空间的每一个可执行文件或动态链接库文件的映像都会被分配一个与之相关联的全局唯一的实例句柄(Hinstance),该实例句柄实际是一个记录有进程加载位置的基本内存地址。
进程的实例句柄在程序入口函数WinMain()中通过第一个参数HINSTANCEhinstExe传递,其实际值即为进程所使用的基本地址空间的地址。
对于VC++链接程序所链接产生的程序,其默认的基本地址空间地址为0x00400000,如没有必要不要修改该值。
在程序中,可以通过GetModuleHandle()函数得到指定模块所使用的基本地址空间。
9.1.2 创建/终止进程
1.问题阐述
进程的创建通过CreateProcess()函数来实现,CreateProcess()通过创建一个新的进程及在其地址空间内运行的主线程来启动并运行一个新的程序。
具体地,在执行CreateProcess()函数时,首先由操作系统负责创建一个进程内核对象,初始化计数为1,并立即为新进程创建一块虚拟地址空间。
随后将可执行文件或其他任何必要的动态链接库文件的代码和数据装载到该地址空间中。
在创建主线程时,也是首先由系统负责创建一个线程内核对象,并初始化为1。
最后启动主线程并执行进程的入口函数WinMain(),完成对进程和执行线程的创建。
2.实现技巧
CreateProcess()函数的原型声明如下:
BOOLCreateProcess(
LPCTSTRlpApplicationName, //可执行模块名
LPTSTRlpCommandLine, //命令行字符串
LPSECURITY_ATTRIBUTESlpProcessAttributes, //进程的安全属性
LPSECURITY_ATTRIBUTESlpThreadAttributes, //线程的安全属性
BOOLbInheritHandles, //句柄继承标志
DWORDdwCreationFlags, //创建标志
LPVOIDlpEnvironment, //指向新的环境块的指针
LPCTSTRlpCurrentDirectory, //指向当前目录名的指针
LPSTARTUPINFOlpStartupInfo, //指向启动信息结构的指针
LPPROCESS_INFORMATIONlpProcessInformation //指向进程信息结构的指针
);
3.实例代码
在程序设计时,某一个具体的功能模块可以通过函数或线程等不同的形式来实现。
对于同一进程而言,这些函数、线程都是存在于同一个地址空间下的,而且在执行时,大多只对与其相关的一些数据进行处理。
如果算法存在某种错误,将有可能破坏与其同处一个地址空间的其他一些重要内容,这将造成比较严重的后果。
为保护地址空间中的内容可以考虑将那些需要对地址空间中的数据进行访问的操作部分放到另外一个进程的地址空间中运行,并且只允许其访问原进程地址空间中的相关数据。
具体地,可在进程中通过CreateProcess()函数去创建一个子进程,子进程在全部处理过程中只对父进程地址空间中的相关数据进行访问,从而可以保护父进程地址空间中与当前子进程执行任务无关的全部数据。
对于这种情况,子进程所体现出来的作用同函数和线程比较相似,可以看成是父进程在运行期间的一个过程。
为此,需要由父进程来掌握子进程的启动、执行和退出。
下面这段代码即展示了此过程:
CStringsCommandLine;
charcWindowsDirectory[MAX_PATH];
charcCommandLine[MAX_PATH];
DWORDdwExitCode;
PROCESS_INFORMATIONpi;
STARTUPINFOsi={sizeof(si)};
//得到Windows目录
GetWindowsDirectory(cWindowsDirectory,MAX_PATH);
//启动“记事本”程序的命令行
sCommandLine=CString(cWindowsDirectory)+"\\NotePad.exe";
:
:
strcpy(cCommandLine,sCommandLine);
//启动“记事本”作为子进程
BOOLret=CreateProcess(NULL,cCommandLine,NULL,NULL,FALSE,0,NULL,NULL,
&si,&pi);
if(ret){
//关闭子进程的主线程句柄
CloseHandle(pi.hThread);
//等待子进程的退出
WaitForSingleObject(pi.hProcess,INFINITE);
//获取子进程的退出码
GetExitCodeProcess(pi.hProcess,&dwExitCode);
//关闭子进程句柄
CloseHandle(pi.hProcess);
}
4.小结
此段代码首先通过CreateProcess()创建Windows自带的“记事本”程序为子进程,子进程启动后父进程通过WaitForSingleObject()函数等待其执行的结束,在子进程没有退出前父进程是一直处于阻塞状态的,这里子进程的作用同单线程中的函数类似。
一旦子进程退出,WaitForSingleObject()函数所等待的pi.hProcess对象将得到通知,父进程将得以继续,如有必要可以通过GetExitCodeProcess()来获取子进程的退出代码。
9.1.3 获取系统进程的技巧
1.问题阐述
进程的定义是为执行程序指令的线程而保留的一系列资源的集合。
进程是一个可执行的程序,由私有虚拟地址空间、代码、数据和其他操作系统资源(如进程创建的文件、管道、同步对象等)组成。
进程是一些所有权的集合,一个进程拥有内存、CPU运行时间等一系列资源,为线程的运行提供一个环境,每个进程都有它自己的地址空间和动态分配的内存、线程、文件和其他一些模块。
2.实现技巧
系统统快照的获取可以通过Win32API函数CreateToolhelp32Snapshot()来完成,通过该函数不仅可以获取进程的快照,同样可以获取堆、模块和线程的系统快照。
函数的声明如下:
HANDLEWINAPICreateToolhelp32Snapshot(
DWORDdwFlags, //指定要创建包含哪一类系统信息的快照函数
DWORDth32ProcessID //指定进程的ID号,当设定为0时表示指定当前进程
);
一旦系统得到系统快照句柄,就可以对当前的标识号进行枚举,进程号通过函数Process32First()和Procee32Next()得到,这两个函数可以用于获取系统快照中第一个和下一个系统的信息,这两个函数的声明如下:
BOOLWINAPIProcess32First(
HANDLEhSnapshot, //系统快照句柄
LPPROCESSENTRY32lppe //指向结构体PROCESSENTRY32的指针
);
BOOLWINAPIProcess32Next(
HANDLEhSnapshot, //系统快照句柄
LPPROCESSENTRY32lppe //指向结构体PROCESSENTRY32的指针
);
3.实例代码
#include
voidCTestView:
:
OnRButtonDown(UINTnFlags,CPointpoint)
{
CStringStrInfo="系统当前进程包括:
\n";
intnProcess=0;
HANDLEsnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(snapshot==NULL)return;
SHFILEINFOshSmall;
PROCESSENTRY32processinfo;
processinfo.dwSize=sizeof(processinfo);
BOOLstatus=Process32First(snapshot,&processinfo);
while(status)
{
ZeroMemory(&shSmall,sizeof(shSmall));
SHGetFileInfo(processinfo.szExeFile,0,&shSmall,sizeof(shSmall),
SHGFI_ICON|SHGFI_SMALLICON);
StrInfo+=processinfo.szExeFile;
StrInfo+="\n";
status=Process32Next(snapshot,&processinfo);
nProcess++;
}
MessageBox(StrInfo,"信息提示",MB_OK);
CView:
:
OnRButtonDown(nFlags,point);
}
4.小结
获取当前系统所有已启动的进程,通常分为两个过程:
首先获取系统进程快照,然后根据快照枚举进程。
在Windows操作系统下,系统已为所有保存在系统内存中的进程、线程及模块等当前状态的信息制作了一个系统快照,用户可以通过对系统快照的访问完成对进程当前状态的检测。
9.1.4 终止指定进程的技巧
1.问题阐述
终止进程也就是结束进程,让进程从内存中卸载。
进程的终止的原因一般有4种。
主线程的入口函数返回。
进程中的一个线程调用ExitProcess函数。
次进程中的所有线程结束。
其他进程中又有线程都结束。
2.实现技巧
函数Process32First()和函数Process32Next()能够枚举系统中的所有进程,函数SHGetFileInfo()能够获得进程的信息,一旦得到进程的标识号,就可以对进程进行终止。
由于被管理进程在当前进程之外,因此进程首先通过OpenProcess()函数来获取一个已经存在的进程对象的句柄,然后才可以通过该句柄对指定的进程进行管理和控制。
OpenProcess()函数的声明如下:
HANDLEWINAPIOpenProcess(
DWORDdwDesiredAccess, //访问标志
BOOLbInheritHandle, //处理进程标志
DWORDdwProcessId //进程标志号
);
3.实例代码
#include
voidCTestView:
:
OnRButtonDown(UINTnFlags,CPointpoint)
{
intnProcess=0;
HANDLEsnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(snapshot==NULL)
return;
SHFILEINFOshSmall;
PROCESSENTRY32processinfo;
processinfo.dwSize=sizeof(processinfo);
BOOLstatus=Process32First(snapshot,&processinfo);
while(status)
{
ZeroMemory(&shSmall,sizeof(shSmall));
SHGetFileInfo(processinfo.szExeFile,0,&shSmall,sizeof(shSmall),
SHGFI_ICON|SHGFI_SMALLICON);
CStringStrInfo="是否需要终止进程:
";
StrInfo+=processinfo.szExeFile;
if(AfxMessageBox(StrInfo,MB_YESNO)==IDYES)
{
DWORDdwProcessID=processinfo.th32ProcessID;
HANDLEhProcess=:
:
OpenProcess(PROCESS_TERMINATE,FALSE,
dwProcessID);
:
:
TerminateProcess(hProcess,0);
CloseHandle(hProcess);
}
status=Process32Next(snapshot,&processinfo);
nProcess++;
}
CView:
:
OnRButtonDown(nFlags,point);
}
4.小结
进程结束后,调用GetExitCodeProcess函数可以得到其退出代码,如果在调用这个函数时,目标进程还没有结束,此函数会返回STILL_ACTIVE,表示进程还在运行。
9.1.5 使用文件映射机制实现进程间通信的技巧
1.问题阐述
传统上都是用fread()、fwrite()之类的函数来存取文件,而文件映射把部分文件或全部文件映射在process的内存空间中,因此可以像存取内存一样存取文件。
2.实现技巧
下面我们介绍创建文件映射的方法。
在介绍CreateFileMapping()函数之前,必须先创建CreateFile()函数和OpenFile()函数打开映射到内存空间的文件,取得文件的句柄。
CreateFileMapping()的函数声明:
HANDLECreateFileMapping(
HANDLEhFile, //文件的句柄
LPSECURITY_ATTRIBUTESlpAttributes, //与安全有关的设置
DWORDflProtect, //用来决定与view有关的属性
DWORDdwMaximumSizeHigh, //设置映射文件的尺寸
DWORDdwMaximumSizeLow,
LPCTSTRlpName //文件映射对象的名称
);
MapViewOfFile()函数的声明:
LPVOIDMapViewOfFile(
HANDLEhFileMappingObject, //文件映射的句柄
DWORDdwDesiredAccess, //此view的属性
DWORDdwFileOffsetHigh, //view文件的起点
DWORDdwFileOffsetLow,
SIZE_TdwNumberOfBytesToMap //映射区的大小
);
3.实例代码
//发送数据
voidCTestDlg:
:
OnBnClickedBtnsendinfo()
{
UpdateData(TRUE);
//创建文件映像对象
HANDLEhMapping;
LPSTRStrData;
hMapping=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,PAGE_READWRITE,0,
0x100,"COMMUNICATION");
if(hMapping==NULL)
{
MessageBox("创建文件映像对象","信息提示",MB_OK);
return;
}
//将文件映射到一个进程的地址空间上
StrData=(LPSTR)MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,0);
if(StrData==NULL)
{
AfxMessageBox("MapViewOfFile()failed.");
MessageBox("文件映射失败","信息提示",MB_OK);
return;
}
//向映射内存写数据
sprintf(StrData,m_StrSendData);
//释放映像内存
UnmapViewOfFile(StrData);
}
//接收数据
voidCTestDlg:
:
OnBnClickedBtnreceiveinfo()
{
//创建文件映像对象
HANDLEhMapping;
LPSTRStrData;
hMapping=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,PAGE_READWRITE,0,
0x100,"COMMUNICATION");
if(hMapping==NULL)
{
MessageBox("创建文件映像对象","信息提示",MB_OK);
return;
}
//将文件映射到一个进程的地址空间上
StrData=(LPSTR)MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,0);
if(StrData==NULL)
{
AfxMessageBox("MapViewOfFile()failed.");
MessageBox("文件映射失败","信息提示",MB_OK);
return;
}
//获取映像内存的数据量
m_StrReceiveData.Format("%s",StrData);
//释放映像内存
UnmapViewOfFile(StrData);
UpdateData(FALSE);
}
4.小结
由于各个process之间是独立的,因此彼此之间无法存取对方的内存空间,这虽然是安全的保护机制,但是如果要两个process之间进行数据交换,那就又有问题了。
不过win3又另外提供了一系列的进程通信的机制,帮助process与另外一个process交换数据。
9.1.6 使用消息实现进程间通信
1.问题阐述
消息是Windows提供的一种驱动机制,在前面的章节中,已经多次使用消息解决问题了。
使用消息进行进程通信的过程,就是使用消息激活某种操作的过程。
对于进程间的通信,一般采用用户自定义的消息来完成进程间的通信,当然如果要实现的是Windows定义的消息功能,则完全可以使用已定义消息。
例如完全可以在一个进程中向另一个进程中的EDIT发送WM_COPY消息,那么,如何用消息来完成进程间的通信呢?
2.实现技巧
在进程间进行消息通信,那么进程之间首先应该约定唯一的确定的消息标识,这个消息标识必须是唯一的。
定义了消息标识后,消息就可以通过就PostMessage,SendMessage或者PostThreadMessage函数给接收方进程的窗口发送消息。
那么进程间通信还存在另外一个问题,就是消息发送给哪一个窗口,消息的发送方必须知道接收方的一个标识,比如窗口的句柄。
所以在通信前,两个通信的进程之间要进行协商,确定消息的接收方的窗口标识。
消息发送方可以通过FindWindow()/FindWindowEx()函数根据窗口的标题或者接收窗体的类名搜索窗口。
所以在进程通信之间,进程双方将约定好窗口类名或者窗口的标题。
前者只搜索顶层窗口,不搜索子窗口;而后者可以搜索子窗口,搜索的过程不区分大小写。
可以用FindWindow搜索指定的窗口,然后使用FindWindowEx来搜索它的子窗口。
MFC封装了FindWindow函数,没有对FindWindowEx函数进行封装。
FindWindow和FindwindowEx的原型如下:
HWND FindWindow(
LPCTSTRlpClassName, //窗口类名
LPCTSTRlpWindowName //窗口标题
)
HWNDFindwWindowEx(
HWNDhwndParent, //父窗口句柄
HWNDhwndChildAfter, //开始搜索的子窗口句柄
LPCTSTRlpszClass, //窗口类名
LPCTSTRlpszWindow //窗口标题
)
3.实例代码
本节编写了两个程序:
传输数据(Send.exe)和接收数据(Recv.exe)。
本例主要由Send.exe发送3个指令,Recv.exe接收这3个指令后,分别对这3个指令进行响应,根据指令改变窗口背景颜色。
发送端程序设计,用MFC的AppWizard(exe)创建新项目Send,设置“Projectname”为“Send”,单击【确定】按钮后进入创建应用程序类型,选择“DialogBased”类型并单击【Finish】按钮。
在对话框上增加3个按钮控件,在SendDlg.h中增加3个消息标识,对控件按钮的BN_CLICKED消息进行响应,其主要代码参考如下:
voidCSendDlg:
:
OnRedBtn()
{
CStringstrRecvWndName="Receiver";
CWnd*pWnd=CWnd:
:
FindWindow(NULL,strRecvWndName);
if(pWnd)
pWnd->PostMessage