9.1.1 进程的概念
进程是当前操作系统下一个被加载到内存的、正在运行的应用程序的实例。每一个进程都是由内核对象和地址空间所组成的,内核对象可以让系统在其内存放有关进程的统计信息并使系统能够以此来管理进程,而地址空间则包括了所有程序模块的代码和数据,以及线程堆栈、堆分配空间等动态分配的空间。进程仅仅是一个存在,是不能独自完成任何操作的,必须拥有至少一个在其环境下运行的线程,并由其负责执行在进程地址空间内的代码。在进程启动的同时即同时启动了一个线程,该线程被称作主线程或执行线程,由此线程可以继续创建子线程。如果主线程退出,那么进程也就没有存在的可能了,系统将自动撤销该进程并完成对其地址空间的释放。
加载到进程地址空间的每一个可执行文件或动态链接库文件的映像都会被分配一个与之相关联的全局唯一的实例句柄(Hinstance),该实例句柄实际是一个记录有进程加载位置的基本内存地址。进程的实例句柄在程序入口函数WinMain()中通过第一个参数HINSTANCE hinstExe传递,其实际值即为进程所使用的基本地址空间的地址。对于VC++链接程序所链接产生的程序,其默认的基本地址空间地址为0x00400000,如没有必要不要修改该值。在程序中,可以通过GetModuleHandle()函数得到指定模块所使用的基本地址空间。
9.2.1 线程的概念
理解线程是非常关键的,因为每个进程至少需要一个线程。本节将更加详细地介绍线程的知识,尤其是要讲述进程与线程之间存在的差别,它们各自具有什么作用。还要介绍系统如何使用线程内核对象来管理线程,与进程内核对象一样,线程内核对象也拥有属性,我们将要观察许多用于查询和修改这些属性的函数。此外还要介绍可以在进程中创建和生成更多的线程时所用的函数。
上一节介绍的进程是由两个部分构成的,一个是进程内核对象,另一个是地址空间。同样,线程也是由两个部分组成的:
线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
线程堆栈,它用于维护线程在执行代码时需要的所有函数参数和局部变量。
上一节中讲过,进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个生命期都在该进程中。这意味着线程在它的进程地址空间中执行代码,并且在进程的地址空间中对数据进行操作。因此,如果在单进程环境中,有两个或多个线程正在运行,那么这两个线程将共享单个地址空间。这些线程能够执行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程。
9.1.3 获取系统进程的技巧
1.问题阐述
进程的定义是为执行程序指令的线程而保留的一系列资源的集合。进程是一个可执行的程序,由私有虚拟地址空间、代码、数据和其他操作系统资源(如进程创建的文件、管道、同步对象等)组成。进程是一些所有权的集合,一个进程拥有内存、CPU运行时间等一系列资源,为线程的运行提供一个环境,每个进程都有它自己的地址空间和动态分配的内存、线程、文件和其他一些模块。
2.实现技巧
系统统快照的获取可以通过Win32 API函数CreateToolhelp32Snapshot()来完成,通过该函数不仅可以获取进程的快照,同样可以获取堆、模块和线程的系统快照。函数的声明如下:

Code
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,//指定要创建包含哪一类系统信息的快照函数
DWORD th32ProcessID//指定进程的ID号,当设定为0时表示指定当前进程
);
一旦系统得到系统快照句柄,就可以对当前的标识号进行枚举,进程号通过函数Process32First()和Procee32Next()得到,这两个函数可以用于获取系统快照中第一个和下一个系统的信息,这两个函数的声明如下:

Code
BOOL WINAPI Process32First(
HANDLE hSnapshot,// 系统快照句柄
LPPROCESSENTRY32 lppe// 指向结构体PROCESSENTRY32的指针
);
BOOL WINAPI Process32Next(
HANDLE hSnapshot,// 系统快照句柄
LPPROCESSENTRY32 lppe// 指向结构体PROCESSENTRY32的指针
);
3.实例代码
#include <tlhelp32.h>
void CTestView::OnRButtonDown(UINT nFlags, CPoint point)
{
CString StrInfo="系统当前进程包括:\n";
int nProcess =0;
HANDLE snapshot=CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0) ;
if(snapshot == NULL)return ;
SHFILEINFO shSmallROCESSENTRY32 processinfo ;
processinfo.dwSize=sizeof(processinfo) ;
BOOL status=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.6 使用消息实现进程间通信
1.问题阐述
消息是Windows提供的一种驱动机制,在前面的章节中,已经多次使用消息解决问题了。使用消息进行进程通信的过程,就是使用消息激活某种操作的过程。对于进程间的通信,一般采用用户自定义的消息来完成进程间的通信,当然如果要实现的是Windows定义的消息功能,则完全可以使用已定义消息。例如完全可以在一个进程中向另一个进程中的EDIT发送WM_COPY消息,那么,如何用消息来完成进程间的通信呢?
2.实现技巧
在进程间进行消息通信,那么进程之间首先应该约定唯一的确定的消息标识,这个消息标识必须是唯一的。定义了消息标识后,消息就可以通过就PostMessage,SendMessage或者PostThreadMessage函数给接收方进程的窗口发送消息。那么进程间通信还存在另外一个问题,就是消息发送给哪一个窗口,消息的发送方必须知道接收方的一个标识,比如窗口的句柄。所以在通信前,两个通信的进程之间要进行协商,确定消息的接收方的窗口标识。消息发送方可以通过FindWindow()/FindWindowEx()函数根据窗口的标题或者接收窗体的类名搜索窗口。所以在进程通信之间,进程双方将约定好窗口类名或者窗口的标题。前者只搜索顶层窗口,不搜索子窗口;而后者可以搜索子窗口,搜索的过程不区分大小写。可以用FindWindow搜索指定的窗口,然后使用FindWindowEx来搜索它的子窗口。MFC封装了FindWindow函数,没有对FindWindowEx函数进行封装。FindWindow和FindwindowEx的原型如下:

Code
HWND FindWindow(
LPCTSTR lpClassName,//窗口类名
LPCTSTR lpWindowName//窗口标题
)
HWND FindwWindowEx(
HWND hwndParent,//父窗口句柄
HWND hwndChildAfter,//开始搜索的子窗口句柄
LPCTSTR lpszClass,//窗口类名
LPCTSTR lpszWindow //窗口标题
)
3.实例代码
本节编写了两个程序:传输数据(Send.exe)和接收数据(Recv.exe)。本例主要由Send.exe发送3个指令,Recv.exe接收这3个指令后,分别对这3个指令进行响应,根据指令改变窗口背景颜色。
发送端程序设计,用MFC的AppWizard(exe)创建新项目Send,设置“Project name”为“Send”,单击【确定】按钮后进入创建应用程序类型,选择“Dialog Based”类型并单击【Finish】按钮。在对话框上增加3个按钮控件,在SendDlg.h中增加3个消息标识,对控件按钮的BN_CLICKED消息进行响应,其主要代码参考如下:

Code
void CSendDlg::OnRedBtn()
{
CString strRecvWndName = "Receiver";
CWnd* pWnd = CWnd::FindWindow(NULL,strRecvWndName);
if(pWnd)pWnd->PostMessage(WM_RED_CONN,0,0);
}
void CSendDlg::OnGreenBtn()
{
CString strRecvWndName = "Receiver";
CWnd* pWnd = CWnd::FindWindow(NULL,strRecvWndName);
if(pWnd)pWnd->PostMessage(WM_GREEN_CONN,0,0);
}
void CSendDlg::OnBlueBtn()
{
CString strRecvWndName = "Receiver";
CWnd* pWnd = CWnd::FindWindow(NULL,strRecvWndName);
if(pWnd)pWnd->PostMessage(WM_BLUE_CONN,0,0);
}
接收端程序设计,用MFC的AppWizard(exe)创建新项目Receiver,设置“Project name”为“Receiver”,单击【确定】按钮后进入创建应用程序类型,选择“Dialog Based”类型并单击【Finish】按钮。
在Receiver.h中增加3个消息标识,增加3个自定义消息响应函数,代码参考如下:
/************************************************************************/
/* * /************************************************************************///
ReceiverDlg.cpp 的实现:
void CReceiverDlg::OnRed(WPARAM wParam,LPARAM lParam)
{
m_pbbrush=CreateSolidBrush(RGB(255,0,0));//设置皮肤颜色
Invalidate();
}
/************************************************************************/
/* /************************************************************************/
void CReceiverDlg::OnGreen(WPARAM wParam,LPARAM lParam)
{
m_pbbrush=CreateSolidBrush(RGB(0,255,0));//设置皮肤颜色
Invalidate();
}
/************************************************************************/
/* /************************************************************************/
void CReceiverDlg::OnBlue(WPARAM wParam,LPARAM lParam)
{
m_pbbrush=CreateSolidBrush(RGB(0,0,255));//设置皮肤颜色
Invalidate();
}
/************************************************************************/
/* /************************************************************************/
HBRUSH CReceiverDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
switch(nCtlColor)
{
case CTLCOLOR_LISTBOX:
case CTLCOLOR_STATIC:
case CTLCOLOR_DLG:
case CTLCOLOR_MSGBOX :
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(RGB(0,0,0));
return m_pbbrush;
break;
default:
return CReceiverDlg::OnCtlColor(pDC, pWnd, nCtlColor);
break;
}
return hbr;
}
上面代码中前3个函数为消息自定义的消息响应函数,它们接收到发送者的消息后,对背景画刷进行初始化。这个背景画刷由消息响应函数OnCtrlColor()返回,以达到更改背景的作用,在每个消息响应函数中调用Invalidate()函数,刷新接收方的背景,以使更改的背景及时显现。
4.小结
通过上面的实例可以看出利用消息进行进程间通信不失为一种便捷的方法,进程间的数据交换量不大却能完成一定的功能,要对上下层程序制定好规范、详尽的协议,便可编制出协调性很好的进程间的通信软件。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了