Windows进程通信——内存映射

来源:互联网 发布:nginx服务器绑定域名 编辑:程序博客网 时间:2024/06/05 01:50

1. 概述

1.1 介绍

文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。

Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
应用程序有三种方法来使多个进程共享一个文件映射对象。
(1)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。
(2)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。
(3)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。

1.2 使用场景

内存映射文件的作用主要体现在下面3点:

(1)系统使用内存映射文件,以便加载和执行. exe和DLL文件。这可以大大节省页文件空间和应用程序启动运行所需的时间

(2)可以使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I/O操作,并且可以不必对文件内容进行缓存

(3)可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。Windows确实提供了其他一些方法,以便在进程之间进行数据通信,但是这些方法都是使用内存映射文件来实现的,这使得内存映射文件成为单个计算机上的多个进程互相进行通信的最有效的方法。

1.3 使用到的函数解析

对于内存文件映射,首先需要CreateFile()打开的一个文件句柄,函数原型为

HANDLE WINAPI CreateFile(  _In_     LPCTSTR               lpFileName,  _In_     DWORD                 dwDesiredAccess,  _In_     DWORD                 dwShareMode,  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,  _In_     DWORD                 dwCreationDisposition,  _In_     DWORD                 dwFlagsAndAttributes,  _In_opt_ HANDLE                hTemplateFile);
参数dwDesiredAccess
0

不能读取或写入文件的内容。当只想获得文件的属性时,请设定0
GENERIC_READ
可以从文件中读取数据 
GENERIC_WRITE
可以将数据写入文件 
GENERIC_READ |GENERIC_WRITE
可以从文件中读取数据,也可以将数据写入文件 
参数dwShareMode
0

打开文件的任何尝试均将失败
FILE_SHARE_READ
使用GENERIC_WRITE打开文件的其他尝试将会失败
FILE_SHARE_WRITE
使用GENERIC_READ打开文件的其他尝试将会失败
FILE_SHARE_DELETE
在文件或设备上启用后续的打开操作以请求删除访问

创建文件映射对象函数CreateFileMapping(),函数原型为

HANDLE WINAPI CreateFileMapping(  _In_     HANDLE                hFile,  _In_opt_ LPSECURITY_ATTRIBUTES lpAttributes,  _In_     DWORD                 flProtect,  _In_     DWORD                 dwMaximumSizeHigh,  _In_     DWORD                 dwMaximumSizeLow,  _In_opt_ LPCTSTR               lpName);
参数 hFile
用于标识你想要映射到进程地址空间中的文件句柄。该句柄由前面调用的CreateFile函数返回
参数 psa
参数是指向文件映射内核对象的SECURITY_ATTRIBUTES结构的指针,通常传递的值是NULL(它提供默认的安全特性,返回的句柄是不能继承的)
参数 fdwProtect
参数使你能够设定这些保护属性。大多数情况下,可以设定下表列出的3个保护属性之一
PAGE_READONLY
当文件映射对象被映射时,可以读取文件的数据。必须已经将GENERIC_READ传递给CreateFile函数
PAGE_READWRITE
当文件映射对象被映射时,可以读取和写入文件的数据。必须已经将GENERIC_READ | GENERIC_WRITE传递给Creat eFile
PAGE_WRITECOPY
当文件映射对象被映射时,可以读取和写入文件的数据。如果写入数据,会导致页面的私有拷贝得以创建。必须已经将GENERIC_READ或GENERIC_WRITE传递给CreateFile
除了上面的页面保护属性外,还有4个节保护属性
节的第一个保护属性是SEC_NOCACHE,它告诉系统,没有将文件的任何内存映射页面放入高速缓存。因此,当将数据写入该文件时,系统将更加经常地更新磁盘上的文件数据。供设备驱动程序开发人员使用的,应用程序通常不使用。
节的第二个保护属性是SEC_IMAGE,它告诉系统,你映射的文件是个可移植的可执行(PE)文件映像。当系统将该文件映射到你的进程的地址空间中时,系统要查看文件的内容,以确定将哪些保护属性赋予文件映像的各个页面。例如, PE文件的代码节( . text)通常用PAGE_ EXECUTE_READ属性进行映射, 而PE 文件的数据节( .data) 则通常用PAGE_READW RITE属性进行映射。如果设定的属性是S E C _ I M A G E,则告诉系统进行文件映像的映射,并设置相应的页面保护属性。
最后两个保护属性是SEC_RESERVE和SEC_COMMIT,它们是两个互斥属性。只有当创建由系统的页文件支持的文件映射对象时,这两个标志才有意义。SEC_COMMIT标志能使CreateFileMapping从系统的页文件中提交存储器。如果两个标志都不设定,其结果也一样。
参数 dwMaximumSizeHigh和dwMaximumSizeLow
这两个参数将告诉系统该文件的最大字节数
参数 pszName
它是个以0结尾的字符串,用于给该文件映射对象赋予一个名字(也是标识符)。该名字用于与其他进程共享文件映射对象

将文件映射到进程的地址空间中使用MapViewOfFile()函数,它返回指向地址空间的首地址,函数原型为

LPVOID WINAPI MapViewOfFile(  _In_ HANDLE hFileMappingObject,  _In_ DWORD  dwDesiredAccess,  _In_ DWORD  dwFileOffsetHigh,  _In_ DWORD  dwFileOffsetLow,  _In_ SIZE_T dwNumberOfBytesToMap);
参数: hFileMappingObject
用于标识文件映射对象的句柄,该句柄是前面调用CreateFileMapping或OpenFileMapping函数返回的
参数:dwDesiredAccess
用于标识如何访问该数据。可以设定下表所列的4个值中的一个
FILE_MAP_WRITE
可以读取和写入文件数据。CreateFileMapping函数必须通过传递PAGE_READWRITE标志来调用
FILE_MAP_READ
可以读取文件数据。CreateFileMapping函数可以通过传递下列任何一个保护属性来调用:PAGE_READONLY、PAGE_ READWRITE或PAGE_WRITECOPY
FILE_MAP_ALL_ACCESS与FILE_MAP_WRITE相同
FILE_MAP_COPY
可以读取和写入文件数据。如果写入文件数据,可以创建一个页面的私有拷贝。在Windows 2000中,CreateileMapping函数可以用PAGE_READONLY、PAGE_READWRITE或PAGE_WRITECOPY等保护属性中的任何一个来调用。在Windows 98中,CreateFileMapping必须用PAGE_WRITECOPY来调用
(一个文件映射到你的进程的地址空间中时,你不必一次性地映射整个文件。相反,可以只将文件的一小部分映射到地址空间。被映射到进程的地址空间的这部分文件称为一个视图。)
参数:dwFileOfsetHigh和dwFileOfsetLow
指定哪个字节应该作为视图中的第一个字节来映射
参数:dwNumberOfBytesToMap
有多少字节要映射到地址空间。如果设定的值是0,那么系统将设法把从文件中的指定位移开始到整个文件的结尾的视图映射到地址空间

在另外一个程序中需要打开内存映射文件,需要使用OpenFileMapping(),函数原型为

HANDLE WINAPI OpenFileMapping(  _In_ DWORD   dwDesiredAccess,  _In_ BOOL    bInheritHandle,  _In_ LPCTSTR lpName);
参数解释的参考前面内容

当需要关闭内存映射文件的之后需要调用UnmapViewOfFile(),它停止当前程序的一个内存映射

BOOL WINAPI UnmapViewOfFile(  _In_ LPCVOID lpBaseAddress);

参数是MapViewOfFile()或OpenFileMapping()返回的首地址

注意:如果没有调用这个函数,那么在进程终止运行前,保留的区域就不会被释放。每当调用MapViewOfFile时,系统总是在你的进程地址空间中保留一个新区域,而以前保留的所有区域将不被释放。

为了提高速度,系统将文件的数据页面进行高速缓存,并且在对文件的映射视图进行操作时不立即更新文件的磁盘映像。如果需要确保你的更新被写入磁盘,可以强制系统将修改过的数据的一部分或全部重新写入磁盘映像中,方法是调用FlushViewOfFile函数:

BOOL WINAPI FlushViewOfFile(  _In_ LPCVOID lpBaseAddress,  _In_ SIZE_T  dwNumberOfBytesToFlush);
参数pvAddress
是包含在内存映射文件中的视图的一个字节的地址。该函数将你在这里传递的地址圆整为一个页面边界值
参数dwNumberOfBytesToFlush
用于指明你想要刷新的字节数。系统将把这个数字向上圆整,使得字节总数是页面的整数。如果你调用FlushViewOfFile函数并且不修改任何数据,那么该函数只是返回,而不将任何信息写入磁盘

2. 编码实现

2.1 发送端


#include "stdafx.h"#include <string>#include <iostream>#include <stdlib.h>#include <stdio.h>#include <windows.h>using std::cout;using std::endl;HANDLE m_hFile;//文件句柄HANDLE m_hMapping;//内存映射句柄void* m_pBase = nullptr;//内存映射的地址bool CreateMemoryMapping();//创建内存映射bool ClearMapping();//关闭内存映射int _tmain(int argc, _TCHAR* argv[]){system("color f0");if (!CreateMemoryMapping()) return -1;system("pause");ClearMapping();return 0;}//************************************************************************// 函数名称:    CreateMemoryMapping// 访问权限:    public // 创建日期:2017/06/06// 创 建 人:// 函数说明:创建内存映射// 返 回 值:   bool//************************************************************************bool CreateMemoryMapping(){//初始化文件句柄m_hFile = ::CreateFile(_T("E:\\C++_SQL_program\\ProcessesThreads\\MemoryMappedFiles\\MemoryMapSender\\MemoryMapSender\\test.txt"),GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL, NULL);//检查有效性if (INVALID_HANDLE_VALUE == m_hFile){cout << "initial file Handel failed," << endl;CloseHandle(m_hFile);return false;}//创建文件映射对象m_hMapping = CreateFileMapping(m_hFile, NULL, PAGE_READWRITE, 0, 0x1000, _T("my_app"));//检查有效性if (INVALID_HANDLE_VALUE == m_hMapping){cout << "initial file mapping Handel failed," << endl;CloseHandle(m_hMapping);CloseHandle(m_hFile);return false;}//将文件映射到进程的地址空间中if (!(m_pBase = MapViewOfFile(m_hMapping, FILE_MAP_READ|FILE_MAP_WRITE, NULL, NULL, NULL))){cout << "将文件映射到进程的地址空间中 failed," << endl;CloseHandle(m_hMapping);CloseHandle(m_hFile);return false;}return true;}//************************************************************************// 函数名称:    ClearMapping// 访问权限:    public // 创建日期:2017/06/06// 创 建 人:// 函数说明:关闭内存文件映射// 返 回 值:   bool//************************************************************************bool ClearMapping(){if (!UnmapViewOfFile(m_pBase)){cout << "unmapping failed" << endl;}CloseHandle(m_hMapping);CloseHandle(m_hFile);m_pBase = nullptr;return true;}

2.2 接收端


#include "stdafx.h"#include <string>#include <iostream>#include <stdlib.h>#include <stdio.h>#include <windows.h>using std::cout;using std::endl;HANDLE m_hMapping;//内存映射句柄LPTSTR m_pBase = nullptr;//内存映射字符串首地址bool my_OpenFileMapping();//打开内存文件映射void my_FileMappingWriteData();//写内容void my_FileMappingReadData();//读取内容bool ClearMapping();//关闭内存映射int _tmain(int argc, _TCHAR* argv[]){system("color f0");if (!my_OpenFileMapping()) return -1;my_FileMappingWriteData();Sleep(500);my_FileMappingReadData();system("pause");ClearMapping();return 0;}//************************************************************************// 函数名称:    my_OpenFileMapping// 访问权限:    public // 创建日期:2017/06/06// 创 建 人:// 函数说明:打开内存文件映射// 返 回 值:   void//************************************************************************bool my_OpenFileMapping(){m_hMapping = OpenFileMapping(FILE_MAP_READ|FILE_MAP_WRITE, false, _T("my_app"));if (INVALID_HANDLE_VALUE == m_hMapping){cout << "initial open file mapping Handel failed," << endl;CloseHandle(m_hMapping);return false;}return true;}//************************************************************************// 函数名称:    my_FileMappingWriteData// 访问权限:    public // 创建日期:2017/06/06// 创 建 人:// 函数说明:写内容// 返 回 值:   void//************************************************************************void my_FileMappingWriteData(){m_pBase = (LPTSTR)MapViewOfFile(m_hMapping, FILE_MAP_WRITE, 0, 0, 0);if (!m_pBase){cout << "映射文件读取 failed," << endl;CloseHandle(m_hMapping);return;}LPTSTR str = _T("hello sender124131");lstrcpy(m_pBase, str);if (!FlushViewOfFile(m_pBase, 0)){cout << "save 2 file failed" <<endl;}/*std::string addstr = ", hello sender";str = addstr.c_str();*/}//************************************************************************// 函数名称:    my_FileMappingReadData// 访问权限:    public // 创建日期:2017/06/06// 创 建 人:// 函数说明:读取内容// 返 回 值:   void//************************************************************************void my_FileMappingReadData(){m_pBase = (LPTSTR)MapViewOfFile(m_hMapping, FILE_MAP_READ, 0, 0, 0);if (!m_pBase){cout << "映射文件读取 failed," << endl;CloseHandle(m_hMapping);return;}LPTSTR str = m_pBase;char *ansiRemoteHost = new char[wcslen(str) * 2 + 1];memset(ansiRemoteHost, 0, wcslen(str) * 2 + 1);WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, str, wcslen(str), ansiRemoteHost, wcslen(str), NULL, NULL);std::string sddddd = std::string(ansiRemoteHost);cout << "内容为:" << sddddd <<endl;}//************************************************************************// 函数名称:    ClearMapping// 访问权限:    public // 创建日期:2017/06/06// 创 建 人:// 函数说明:关闭内存文件映射// 返 回 值:   bool//************************************************************************bool ClearMapping(){if (!UnmapViewOfFile(m_pBase)){cout << "unmapping failed" << endl;}CloseHandle(m_hMapping);m_pBase = nullptr;return true;}