内存映射文件技术概述
内存映射文件,是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。
Win32系统允许多个进程(运行在同一计算机上)使用内存映射文件来共享数据,所以,如果一个进程往内存映射文件中写数据,另一个进程来读,就实现了它们之间的通信。实际上,其他共享和传送数据的技术,诸如使用SendMessage或者PostMessage,都在内部使用了内存映射文件。
实现
接下来我们通过C语言编写程序,实现两个进程交换数据(通信)。在本例中,只在同一台主机上实现不同进程间的通信,所以可以使用内存映射文件来实现。
程序基本流程:
1) 新建命名共享
首先利用CreateFile或者CreateFileForMapping获得一个用于映射的物理文件句柄, 然后利用该文件句柄结合CreateFileMapping得到一个命名的共享内存映射文件句柄。
2) 打开命名共享内存
如果需要共享已经存在的命名共享内存映射文件, 使用OpenFileMapping函数。
3) 获得地址空间指针
进行内存映射文件的读写和一般的文件读写不同, 是直接面对你申请的地址空间, 为此需要使用MapViewOfFile得到相关的地址LPVOID类型的指针。 如果需要进行数据写入, 可以通过类型转换直接对于内存地址进行赋值,比如memcpy( lpAddress, lpBuf, ....);如果是读取操作,将参数顺序调整一下就可以了。
4) 将内存复制到所映射的物理文件上面
FlushMapViewOfFile函数可以将内存里面的内容DUMP到物理磁盘上面。
5) 卸载内存映射文件地址指针
UnmapViewOffFile函数来卸内存映射文件。
6) 关闭内存映射文件
最后,调用CloseHandle来关闭内存映射文件。
实现及测试源代码
两个进程一个视为客户端,另一个视为服务端
cilentProcess:
#include "windows.h"
#include "math.h"
#include "stdio.h"
void main()
{
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,"ClientProcess");
if (hFileMapping == NULL)
{
CloseHandle(hFileMapping);
hFileMapping = NULL;
return ;
}
// 设定大小、偏移量等参数
SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
__int64 qwFileSize = 0x4000000;
__int64 qwFileOffset = 0;
__int64 T = 600 * sinf.dwAllocationGranularity;
DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity;
// 将文件数据映射到进程的地址空间
PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping,
FILE_MAP_ALL_ACCESS,
(DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock);
// 测试程序
// 在客户端输入一个数字,在服务端计算该数的平方,并将结果返回给客户端;
int dataInClientPro = 0;
printf("**********In ClintProcess**********\n");
while(1)
{
printf("Please input a num (0 for quit):\t\n");
scanf("0", &dataInClientPro);
if(dataInClientPro == 0)
{
return;
}
memcpy(pbFile,&dataInClientPro,sizeof(int));
memcpy(pbFile+sizeof(int),&dataInClientPro,sizeof(int));
FlushViewOfFile(pbFile,NULL);
Sleep(1000);
memcpy(&dataInClientPro,pbFile+sizeof(int),sizeof(int));
printf("Result form ServerProcess :\t0\n", dataInClientPro);
}
}
serverProcess:
#include "windows.h"
#include "math.h"
#include "stdio.h"
void main()
{
// 创建文件内核对象,其句柄保存于hFile
HANDLE hFile = CreateFile("Recv1.zip",
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
// 创建文件映射内核对象,句柄保存于hFileMapping
HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE,
0, 0x4000000, "ClientProcess");
if (hFileMapping != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(hFileMapping);
hFileMapping = NULL;
return ;
}
// 设定大小、偏移量等参数
SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
__int64 qwFileSize = 0x4000000;
__int64 qwFileOffset = 0;
__int64 T = 600 * sinf.dwAllocationGranularity;
DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity;
// 将文件数据映射到进程的地址空间
PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping,
FILE_MAP_ALL_ACCESS,
(DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock);
// 测试程序
// 从客户端读入一个数字,在服务端计算该数的平方,并将结果返回给客户端;
int dataInServerPro;
int flag = 0;
memset(pbFile, 0, sizeof(int));
FlushViewOfFile(pbFile,NULL);
while(1)
{
memcpy(&flag, pbFile, sizeof(int));
if(flag)
{
memcpy(&dataInServerPro, pbFile+sizeof(int), sizeof(int));
printf("**********In ServerProcess**********\nRead a num from ClintProcess :\t0\n",dataInServerPro);
dataInServerPro = dataInServerPro*dataInServerPro;
memcpy(pbFile+sizeof(int),&dataInServerPro,sizeof(int));
FlushViewOfFile(pbFile,NULL);
memset(pbFile, 0,sizeof(int));
FlushViewOfFile(pbFile, NULL);
}
Sleep(1);
}
// 将文件数据从进程的地址空间中释放
UnmapViewOfFile(pbFile);
// 释放文件映射内核对象
CloseHandle(hFileMapping);
// 释放文件内核对象
CloseHandle(hFile);
}