vc 内存映射文件---《WINDOWS核心编程》17章学习笔记
windows下如果要对一个磁盘文件指定部分进行修改有如下【三种方法】:
【方法一】打开文件,把file指针移动到指定位置,写入数据;
【方法二】打开文件,读文件指定部分内容到内存buf,在buf中查找,修改,最后回写入文件;
【方法三】内存映射文件,此方法同时考虑到了以上2种方式,即直接操作文件+在内存中操作文件
其原理和虚拟内存相同,不同的是以磁盘文件代替系统的页文件。
也就是把磁盘文件看作一块内存,并得到一个buf的指针指向文件头,对此buf的操作等同操作文件。
内存映射文件的【三个好处】:
【好处一】加载EXE和DLL时,节省内存,减少加载时间;
不学不知道,原来windows加载EXE和DLL都是通过内存映射文件方式来进行的
【好处二】操作文件时,可不必进行IO操作,不必对文件内容进行缓冲操作.
【好处三】可达到进程间通信的目的;实际上所有进程间通信全依赖内存映射文件,不过内存映射文件无法单独完成
因为进程通信要有互相通知的过程,即发消息,所以表现出来形式各异。
【好处一】的实例说明
由于windows加载EXE和DLL都是通过内存映射文件方式来进行的,
得出结论同一EXE文件的不同实例之间是可以通信的
vc6.0建对话框程序<AppInst>,在文件AppInst.cpp上面加入下列代码
/**********************************************
#pragma data_seg("Shared") //都是编译选项
volatile LONG g_lApplicationInstances = 0;//大概意思是更改本exe文件结构,添加1个节(术语)
#pragma data_seg() //此节的内容是共享的,本例共享的变量为g_lApplicationInstances
#pragma comment(linker, "/Section:Shared,RWS")
***********************************************/
这样设置后,同1个EXE不同实例间g_lApplicationInstances是共享的,可以自己实践
缺点:不同EXE就没有办法了,所以禁止多次运行还是用互斥稳当,这个实例只是验证windows加载EXE的方式
【好处二】的实例说明,象操作内存buf一样操作文件,操作大文件时效果明显
/**********************************************
#include <windows.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
// 步骤1 打开文件FILE_FLAG_WRITE_THROUGH
HANDLE hFile = CreateFile(
"demo.txt",
GENERIC_WRITE | GENERIC_READ,// 如果要映射文件:此处必设置为只读(GENERIC_READ)或读写
0, // 此设为打开文件的任何尝试均将失败
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, //|FILE_FLAG_WRITE_THROUGH,【解1】
NULL);
if (hFile != INVALID_HANDLE_VALUE)// 文件打开失败返回句柄为-1
// 这步必须测试,详细见步骤2
{
printf("文件打开成功~!/n");
}
// 步骤2 建立内存映射文件
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("文件大小为:%d/n", dwFileSize);
HANDLE hFileMap = CreateFileMapping(
hFile, // 如果这值为INVALID_HANDLE_VALUE,是合法的,上步一定测试啊
NULL, // 默认安全性
PAGE_READWRITE, // 可读写
0, // 2个32位数示1个64位数,最大文件字节数,
// 高字节,文件大小小于4G时,高字节永远为0
0,//dwFileSize, // 此为低字节,也就是最主要的参数,如果为0,取文件真实大小
NULL);
if (hFileMap != NULL)
{
printf("内存映射文件创建成功~!/n");
}
// 步骤3:将文件数据映射到进程的地址空间
PVOID pvFile = MapViewOfFile( //pvFile就是得到的指针,用它来直接操作文件
hFileMap,
FILE_MAP_WRITE, // 可写
0, // 文件指针头位置 高字节
0, // 文件指针头位置 低字节 必为分配粒度的整倍数,windows的粒度为64K
0); // 要映射的文件尾,如果为0,则从指针头到真实文件尾
if (pvFile != NULL)
{
printf("文件数据映射到进程的地址成功~!/n");
}
// 步骤4: 像操作内存一样操作文件,演示功能把整个文件倒序
char *p = (char*)pvFile;
printf("%s/n", p);
for (unsigned int i = 0; i <= dwFileSize / 2; i++)
{
int nTmp = p[i];
p[i] = p[dwFileSize - 1 - i];
p[dwFileSize - 1 - i] = nTmp;
}
printf("%s/n", p);
// 步骤5: 相关的释放工作
UnmapViewOfFile(pvFile); // 释放内存映射文件的头指针
CloseHandle(hFileMap); // 内存映射文件句柄
CloseHandle(hFile); // 关闭文件
getchar();
return 0;
}
***********************************************/
【解1】如此操作文件文件会立即改变吗?可能不会,
为了提高速度,系统将文件的数据页面进行高速缓存,并且在对文件的映射视图进行操作时不立即更新文件的磁盘映像。如果需要确保你的更新被写入磁盘,可以强制系统将修改过的数据的一部分或全部重新写入磁盘映像中,方法是调用F lushViewOfFile函数或在CreateFile加上此选项FILE_FLAG_WRITE_THROUGH
【好处三】的实例说明 不同进程间通信,省去了对磁盘文件依赖,直接建立内存映射文件
步骤1: vc6.0建对话框程序,添加3个Button; 1个Edit:m_edit; 1个listbox: m_list
在对话框单元声明公共变量
HANDLE m_hFileMap;
CString strMsg;
步骤2: OnInitDialog() 添加
SetDlgItemText(IDC_EDIT1, "操作数据成功~!");
m_hFileMap = NULL;
步骤3:按钮1-建立内存映射文件,并写入内容
/***********************************************
HANDLE hFileMap= CreateFileMapping(INVALID_HANDLE_VALUE, NULL,//必须是INVALID_HANDLE_VALUE
PAGE_READWRITE, 0, 4 * 1024, TEXT("MMFSharedData"));
if (hFileMap == NULL)
{
m_list.AddString("创建虚拟内存映射文件失败~!");
return;
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
strMsg.Format("%d: 指定的文件已存在,创建失败~!", hFileMap);
m_list.AddString(strMsg);
CloseHandle(hFileMap);
return;
}
strMsg.Format("%d: 创建成功", hFileMap);
m_list.AddString(strMsg);
m_hFileMap = hFileMap;
PVOID pView = MapViewOfFile(m_hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0,0,0);
if (pView == NULL)
{
m_list.AddString("创建视图失败~!");
return;
}
m_edit.GetWindowText((LPTSTR)pView, 4 * 1024);
m_list.AddString((LPTSTR)pView);
UnmapViewOfFile(pView);
***********************************************/
步骤4:按钮2-打开存在的内存映射文件,并读出内容 【此功能与按钮1可在不同进程】
/***********************************************
HANDLE hFileMapT = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,
false, TEXT("MMFSharedData"));
if (hFileMapT == NULL)
{
m_list.AddString("打开文件失败~!");
return;
}
strMsg.Format("%d: 打开成功", hFileMapT);
m_list.AddString(strMsg);
PVOID pView = MapViewOfFile(hFileMapT,
FILE_MAP_READ | FILE_MAP_WRITE, 0,0,0);
if (pView != NULL)
{
m_list.AddString((char*)pView);
UnmapViewOfFile(pView);
if (!CloseHandle(hFileMapT))
{
m_list.AddString("文件关闭失败~!");
}
}
***********************************************/
步骤5 最后的释放
if (m_hFileMap == 0) // 映射文件已经关闭
{
return;
}
CloseHandle(m_hFileMap); // 关闭成功
m_hFileMap = NULL;