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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章