一、引言
在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯。WIN32 API提供了许多函数使我们能够方便高效的进行进程间的通讯,通过这些函数我们可以控制不同进程间的数据交换.
内部进程间通讯(即:同机通讯)和数据交换有多种方式:消息、共享内存、匿名(命名) 管道、邮槽、Windows套接字等多种技术。“共享内存”(shared memory)可以定义为对一个以上的进程是可见的内存或存在于多个进程的虚拟地址空间。例如:如果两个进程使用相同的DLL,只把DLL的代码页装入内 存一次,其他所有映射这个DLL的进程只要共享这些代码页就可以了;利用消息机制实现IPC虽然有交换的数据量小、携带的信息少等缺点,但由于其实现方 便、应用灵活而广泛应用于无须大量、频繁数据交换的内部进程通讯系统之中。本文通过共享内存实现进程间的数据交换,利用windows消息机制实现进程间 的同步,两种机制结合使用,不仅解决了交换数据量小的问题,还解决了进程并发存取数据的问题。
二、同机进程间共享内存的实现
采用内存映射文件实现WIN32进程间的通讯:Windows中的内存映射文件的机制为我们高效地操作文件提供了一种途径,它允许我们在WIN32进程中保留一段内存区域,把目标文件映射到这段虚拟内存中。在程序实现中必须考虑各进程之间的同步问题。具体实现步骤如下:
1、在服务器端进程中调用内存映射API函数CreateFileMapping创建一个有名字标识的共享内存;
函数CreateFileMapping原型
HANDLE CreateFileMapping (
HANDLE hFile, // 映射文件的句柄,若设为0xFFFFFFFF则创建一个进程间共享的对象
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全属性DWORD flProtect, // 保护方式
DWORD dwMaximumSizeHigh, //对象的大小
DWORD dwMaximumSizeLow,
LPCTSTR lpName // 映射文件名,即共享内存的名称 );
与虚拟内存类似,保护方式参数可以是PAGE_READONLY或是PAGE_READWRITE。如果多进程都对同一共享内存进行写访问,则 必须保持相互间同步。映射文件还可以指定PAGE_WRITECOPY标志,可以保证其原始数据不会遭到破坏,同时允许其他进程在必要时自由的操作数据的 拷贝。
例如:创建一个名为“zzj”的长度为4096字节的有名映射文件:
HANDLE m_hMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),
NULL,PAGE_READWRITE,0,0x1000," zzj");
2、在创建文件映射对象后,服务器端进程调用MapViewOfFile函数映射到本进程的地址空间内;
例:映射缓存区视图
void* m_pBaseMapFile=MapViewOfFile(m_hMapFile,
FILE_MAP_READ|FILE_MAP_WRITE,
0,0,0);
3、客户端进程访问共享内存对象,需要通过内存对象名调用OpenFileMapping函数,以获得共享内存对象的句柄
HANDLE m_hMapFile =OpenFileMapping(FILE_MAP_WRITE,
FALSE," zzj");
4、如果客户端进程获得共享内存对象的句柄成功,则调用MapViewOfFile函数来映射对象视图。用户可以使用该对象视图来进行数据读写操作,以达到数据通讯的目的。
例:映射缓存区视图
void* m_pBaseMapFile=MapViewOfFile(m_hMapFile,
FILE_MAP_READ|FILE_MAP_WRITE,
0,0,0);
5、当用户进程结束使用共享内存后,调用UnmapViewOfFile函数以取消其地址空间内的视图:
if (m_pBaseMapFile)
{
UnmapViewOfFile(m_pBaseMapFile);
SharedMapView=NULL;
}
三、利用消息实现内部进程通讯
1、Windows消息机制简介
Windows是一种面向对象的体系结构,Windows环境和应用程序都是通过消息 来交互的。Windows应用程序开始执行后,Windows为该程序创建一个"消息队列(message queue)",用以存放邮寄给该程序可能创建的各种不同窗口的消息。消息队列中消息的结构(MSG)为:
typedef struct tagMSG{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
其中第一个成员变量是用以标识接收消息的窗口的窗口句柄;第二个参数便是消息标识号,如WM_PAINT;第三个和第四个参数的具体意义和 message值有关,均为消息参数。前四个参数是非常重要和经常用到的,至于后两个参数则分别表示邮寄消息的时间和光标位置(屏幕坐标)。
把消息传送到应用程序有两种方法:一种是由系统将消息"邮寄(post)"到应用程序 的"消息队列",这是"进队消息",Win32 API有对应的函数:PostMessage(),此函数不等待该消息处理完就返回;而另一种则是由系统在直接调用窗口函数时将消息"发送(send)" 给应用程序的窗口函数,属于"不进队消息",对应的函数是SendMessage(),该函数必须等待消息处理完后方可返回。
2、同机进程利用Windows自定义消息进行通信的方法:
◆ 在发送方程序和接收方程序中均定义Windows自定义消息:
例如:#define WM_SERVERDATACHANGE WM_USER+999
◆ 获取接收方的接收消息的窗口对象
(1)通过接收方程序的窗口标题获取接收方程序的窗口对象,调用函数为:
static CWnd* PASCAL FindWindow(
LPCTSTR lpszClassName, //窗口类名称,可为NULL
LPCTSTR lpszWindowName ); //窗口标题
例如:接收方程序的窗口标题为“WFClient”
CString str="WFClient";
CWnd *pWnd=CWnd::FindWindow(NULL,str);
(2)通过窗口句柄获取接收方程序的窗口对象,调用函数为:
static CWnd* PASCAL FromHandle( HWND hWnd );
◆ 获取接收方程序的窗口对象后,调用窗口类的SendMessage()或PostMessage()函数
发送消息
BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
例:if(pWnd)
pWnd->PostMessage(WM_ SERVERDATACHANGE ,0,0);
◆ 在接收方程序中响应自定义消息,须放在主框架类中,否则不响应
void CMainFrame::OnData2Chane(WPARAM wParam, LPARAM lParam)
{
AfxMessageBox("receive WM_USER+999");
}
四、实例程序
1、设计概述
◆ 本实例采用WINDOWS系统共享内存方式,在同一结点上运行的服务器端程序与客户端程序进行数据交换,共享内存的名称为“zzj”
◆ 共享内存分为不同的数据区,服务器端程序与客户端程序刷新各自的数据区,同时访问对方的数据区以获得实时的数据,数据区定义如下:
(1) 数据区1(客户端修改,服务器端读取):
数据区长度为 4*(n+1) bytes, n 为数据个数(每个数据对应一个点),其结构如下所示:
数据个数+1 (INT ,4BYTES)
数据1 (INT ,4BYTES)
数据2 (INT ,4BYTES)
、、、、、
数据N (INT ,4BYTES)
(2) 数据区2(服务端修改,客户端读取):
数据区长度为 4*(n+1) bytes, n 为数据个数(每个数据对应一个点),其结构如下所示:
数据个数 (INT ,4BYTES)
数据1 (INT ,4BYTES)
数据2 (INT ,4BYTES)
、、、、、
数据N (INT ,4BYTES)
(3) 窗口句柄区
窗口句柄区长度为8 bytes,服务器端和客户端使用对方的窗口句柄传递WINDOWS用户自定义消息,其结构如下图所示:
服务器端窗口句柄(4bytes)
客户端窗口句柄 (4bytes)
◆ 由于对共享内存数据访问存在并发性,服务器端程序与客户端程序使用对方的窗口句柄传递WINDOWS用户自定义消息来实现相互数据转送的驱动。一般情况 下,当服务器端程序的数据区2发生变化时应主动向客户端程序发消息;客户端更改数据区1后,也应主动向服务端发送消息,通知服务端读取。
◆ 用户自定义WINDOWS消息
(1)、服务端à客户端
#define WM_SERVERDATACHANGE WM_USER+999
当服务端发现数据区2发生变化时,发出此消息
(2)、客户端à服务端
#define WM_CLIENTDATACNANGE WM_USER+998
当客户端发现数据区1发生变化时,发出此消息
2、实现过程
(1) 利用VC++6.0 的AppWizard分别创建二个单文档SDI应用程序,项目名分别为“WFServer”、“WFClient”,视图类的基类采用CFormView。
(2) 为 了便于扩展与应用,笔者在两个进程中封装了对应的两个类CServerData和CClientData 你可以根据自己的共享内存区协议,在这两个类的基础上添加新的功能。CServerData类是服务器端的封装类,主要功能:创建共享内存区,建立映射缓 存区视图,初始化共享内存区的各数据区指针,读、写相应数据区的数据,类的定义如下: