Windows文件映射

几乎所有的应用程序都离不开对文件的操作,一般的步骤是打开文件,读写文件,关闭文件。但是,频繁的读写操作效率会很低,并且如果文件很大的情况,全部读入缓冲区也不现实,微软提供了一个叫映射文件的技术,可以完美解决上面的问题。我暂时的理解就是文件映射后得到一个指针,对这个指针进行任何操作(添加,修改数据)都是直接修改的文件。

用途1:使用内存映射文件加载和执行exe和dll,可以大大节省页面文件空间(暂未研究)
用途2:使用内存映射文件访问磁盘数据,不必IO操作,不必缓存
用途3:在同一台计算机上的多个进程来共享数据。windows下提供的其他进程间通信的方法基本都是基于内存映射文件实现的。

使用内存映射文件需要以下步骤:
1)创建或打开一个文件内核对象,用于告知磁盘上需要用作内存映射文件
2)创建文件映射内核对象,告诉系统文件的大小以及访问文件的方式
3)将文件的全部或部分映射到进程的地址空间中

映射完成后需要按照以下步骤清除
1)撤销文件映射内核对象的映像
2)关闭文件映射内核对象
3)关闭文件内核对象

创建或打开文件对象
HANDLE  CreateFileA(
    _In_ LPCSTR lpFileName,//文件名
    _In_ DWORD dwDesiredAccess,//如何访问  (GENERIC_READ|GENERIC_WRITE或组合)
    _In_ DWORD dwShareMode,//文件共享方式(FILE_SHARED_READ|FILE_SHARED_WRITE或组合或0)
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全属性,内核继承相关
    _In_ DWORD dwCreationDisposition,//
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    );

dwCreationDispositionLong,下述常数之一:
CREATE_NEW 创建文件;如文件存在则会出错
CREATE_ALWAYS 创建文件,会改写前一个文件
OPEN_EXISTING 文件必须已经存在。由设备提出要求
OPEN_ALWAYS 如文件不存在则创建它
TRUNCATE_EXISTING 将现有文件清空
参数见:https://baike.baidu.com/item/CreateFile/9621657?fr=aladdin

注:CreateFile失败会返回INVALID_HANDLE_VALUE

创建文件映射对象,失败返回NULL
LPVOID CreateFileMappingA(
    _In_     HANDLE hFile,//上面CreateFile的返回值
    _In_opt_ LPSECURITY_ATTRIBUTES lpFileMappingAttributes,//安全属性
    _In_     DWORD flProtect,//保护模式。PAGE_READONLY,PAGE_READWRITE,PAGE_WRITECOPY
    _In_     DWORD dwMaximumSizeHigh,//映射文件长度的高位
    _In_     DWORD dwMaximumSizeLow,//映射文件长度的低位,两个组合成映射文件的长度
    _In_opt_ LPCSTR lpName//内核名字,即命名对象
    );

将文件数据映射到进城地址空间
MapViewOfFile(
    _In_ HANDLE hFileMappingObject,//上面CreateFileMapping的返回值
    _In_ DWORD dwDesiredAccess,//权限,FILE_MAP_WRITE,FILE_MAP_READ,FILE_MAP_ALL_ACCESS(与FILE_MAP_WRITE相同),FILE_MAP_COPY
    _In_ DWORD dwFileOffsetHigh,//映射文件的起始位置高位
    _In_ DWORD dwFileOffsetLow,//映射文件的起始位置低位,组合起来则是起始位置
    _In_ SIZE_T dwNumberOfBytesToMap//此次需要映射的长度,设为0则全部映射
    );
注:偏移量需要是Windows分配粒度的整数倍,(目前一直都是64K)

撤销就比较简单了,就是UnMapViewOfFile与CloseHandle

两个进程通过内存文件映射共享内存的例子,简单拖了个界面,open则是进行初始化的一些创建操作,close则是清除文件映射对象,read获取值,write写入值

初始化部分代码:
 

        if (mapView)
	{
		UnmapViewOfFile(mapView);
		mapView = NULL;
	}
	 hFile = ::CreateFileA("D:/1.txt", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		MessageBox("CreateFile error");
		return ;
	}
	HANDLE hFileMapping = ::CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 1024,"FILEMAP");
	if (::GetLastError() == ERROR_ALREADY_EXISTS)
	{
		MessageBox("filemapping exists");
		//CloseHandle(hFileMapping);
	}
	
	if (!hFileMapping)
	{
		MessageBox("CreateFileMapping error");
		return ;
	}
	else
	{
		mapView = MapViewOfFile(hFileMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
		CloseHandle(hFileMapping);
		
	}

清除部分

UnmapViewOfFile(mapView);
mapView = NULL;
CloseHandle(hFile);

读取部分:

if (!mapView)
{
	MessageBox("mapview not open");
}
char *p = (char*)mapView;	
GetDlgItem(IDC_EDIT1)->SetWindowTextA(p);

由于MapViewOfFile的返回值是一个void*类型,所以可以自己进行转化,对得到的这个指针进行操作就像是在操作内存一般简单,不过实际上对它的所有操作都是在修改了文件

写入部分:

if (!mapView)
{
	MessageBox("mapview closed");
	return;
}

CString p;
GetDlgItemText(IDC_EDIT1,p);
memset(mapView, 0, 1024);
//SetEndOfFile必须在撤销视图的映像并关闭了文件映射对象之后才能调用,不然就会返回FALSE
//使用GetLastError返回ERROR_USER_MAPPED_FILE,表明不能在与文件映射对象相关联的对象上执行此操作
//此处只是简单的进行清0处理
//SetFilePointer(hFile, p.GetLength(),NULL, FILE_BEGIN);
//BOOL bRet = SetEndOfFile(hFile);
memcpy(mapView, p.GetBuffer(0), p.GetLength());
p.ReleaseBuffer();

上面清0的原因是如果一开始文件内容比较长,然后写入了一个短一点的字符串,再次读取后会读取到后面的未被修改的数据
例:源字符串 abc123,之后一个进程写入123,另一个进程再读时会得到123123

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值