0x01 使用文件映射实现共享内存。
用内存映射文件实现进程间的通讯:Windows中的内存映射文件的机制为我们高效地操作文件提供了一种途径,它允许我们在进程中保留一段内存区域,把硬盘或页文件上的目标文件映射到这段虚拟内存中。注意:在程序实现中必须考虑各进程之间的同步问题。
FileMapping用于将存在于磁盘的文件放进一个进程的虚拟地址空间,并在该进程的虚拟地址空间中产生一个区域用于“存放”该文件,这个空间就叫做File View(存放在进程的虚拟内存中),系统并同时产生一个File Mapping Object(存放于物理内存中)用于维持这种映射关系,这样当多个进程需要读写那个文件的数据时,它们的File View其实对应的都是同一个File Mapping Object,这样做可节省内存和保持数据的同步性,并达到数据共享的目的。
0x02 细节
1)物理文件句柄
任何可以获得的物理文件句柄,如果需要创建一个物理文件无关的内存映射也无妨,将它设置成为 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.
如果需要和物理文件关联,要确保你的物理文件创建的时候的访问模式和"保护设置"匹配,比如:物理文件只读,内存映射需要读写就会发生错误。推荐你的物理文件使用独占方式创建。
如果使用 INVALID_HANDLE_VALUE,也需要设置需要申请的内存空间的大小,无论物理文件句柄参数是否有效,这样 CreateFileMapping就可以创建一个和物理文件大小无关的内存空间,甚至超过实际文件大小,如果物理文件有效,而大小参数为0,则返回给你的是一个和物理文件大小一样的内存空间地址范围。返回给你的文件映射地址空间是可以通过复制,集成或者命名得到,初始内容为0。
2)保护设置
就是安全设置,不过一般设置NULL就可以了,使用默认的安全配置. 在win2k下如果需要进行限制,这是针对那些将内存文件映射共享给整个网络上面的应用进程使用是,可以考虑进行限制.
3)高位文件大小
32位地址空间,设置为0。
4) 共享内存名称
命名可以包含 "Global"或者 "Local" 前缀在全局或者会话名空间初级文件映射.其他部分可以包含任何除了()以外的字符,可以参考 Kernel Object Name Spaces.
5)调用CreateFileMapping的时候GetLastError的对应错误
ERROR_FILE_INVALID 如果企图创建一个零长度的文件映射,应有此报
ERROR_INVALID_HANDLE 如果发现你的命名内存空间和现有的内存映射,互斥量,信号量,临界区同名就麻烦了
ERROR_ALREADY_EXISTS 表示内存空间命名已经存在
使用函数CreateFileMapping创建一个想共享的文件数据句柄,然后使用MapViewOfFile来获取共享的内存地址,然后使用OpenFileMapping函数在另一个进程里打开共享文件的名称,这样就可以实现不同的进程共享数据。
0x03 API
1.内存映射API函数CreateFileMapping创建一个有名字标识的共享内存
WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateFileMappingW( _In_ HANDLE hFile, // 映射文件的句柄,若设为0xFFFFFFFF(即:INVALID_HANDLE_VALUE)则创建一个进程间共享的对象,(即创建了一个和物理文件大小无关的内存空间) _In_opt_ LPSECURITY_ATTRIBUTES lpFileMappingAttributes, //安全属性 _In_ DWORD flProtect, //保护方式 _In_ DWORD dwMaximumSizeHigh, //对象的大小 _In_ DWORD dwMaximumSizeLow, _In_opt_ LPCWSTR lpName //映射文件名,即共享内存的名称 );
与虚拟内存类似,保护方式参数可以是PAGE_READONLY或是PAGE_READWRITE。如果多进程都对同一共享内存进行写访问,则必须保持相互间同步。映射文件还可以指定PAGE_WRITECOPY标志,可以保证其原始数据不会遭到破坏,同时允许其他进程在必要时自由的操作数据的拷贝。
MappingHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,L"刘大大别是妖怪吧");
2.在创建文件映射对象后,服务器端进程调用MapViewOfFile函数映射到本进程的地址空间内
BaseAddress = MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
3、客户端进程访问共享内存对象,需要通过内存对象名调用OpenFileMapping函数,以获得共享内存对象的句柄
MappingHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,L"刘大大别是妖怪吧");
4、如果客户端进程获得共享内存对象的句柄成功,则调用MapViewOfFile函数来映射对象视图。用户可以使用该对象视图来进行数据读写操作,以达到数据通讯的目的。
例:映射缓存区视图
BaseAddress = MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
5、当用户进程结束使用共享内存后,调用UnmapViewOfFile函数以取消其地址空间内的视图:
if (BaseAddress) { UnmapViewOfFile(BaseAddress); SharedMapView=NULL; }
源代码:
1 // Server.cpp : 定义控制台应用程序的入口点。 2 // 3 4 #include "stdafx.h" 5 #include <windows.h> 6 7 int main() 8 { 9 10 HANDLE MappingHandle = NULL; 11 PVOID BaseAddress = NULL; 12 //创建一个物理文件无关的内存映射 13 //创建了一个和物理文件大小无关的内存空间 14 MappingHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,0x1000, L"刘大大别是妖怪吧"); 15 16 if (MappingHandle==NULL) 17 { 18 return 0; 19 } 20 21 BaseAddress = MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0); 22 23 24 __try 25 { 26 memcpy(BaseAddress, "HelloClient", strlen("HelloClient")+1); 27 28 printf("Input AnyKey To Continue\r\n"); 29 getchar(); 30 31 printf("%s\r\n", BaseAddress); 32 } 33 __except (EXCEPTION_EXECUTE_HANDLER) 34 { 35 printf("异常\r\n"); 36 goto Exit; 37 } 38 39 40 41 42 Exit: 43 if (MappingHandle!=NULL) 44 { 45 CloseHandle(MappingHandle); 46 MappingHandle = NULL; 47 } 48 49 printf("Input AnyKey To Exit\r\n"); 50 getchar(); 51 return 0; 52 }
Server.cpp
1 // Client.cpp : 定义控制台应用程序的入口点。 2 // 3 4 #include "stdafx.h" 5 #include <windows.h> 6 7 int main() 8 { 9 HANDLE MappingHandle = NULL; 10 PVOID BaseAddress = NULL; 11 12 MappingHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,L"刘大大别是妖怪吧"); 13 14 if (MappingHandle==NULL) 15 { 16 return 0; 17 } 18 BaseAddress = MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0); 19 __try 20 { 21 printf("%s\r\n", BaseAddress); 22 memcpy(BaseAddress, "HelloServer", strlen("HelloServer") + 1); 23 24 } 25 __except (EXCEPTION_EXECUTE_HANDLER) 26 { 27 printf("异常\r\n"); 28 goto Exit; 29 } 30 31 Exit: 32 33 if (MappingHandle!=NULL) 34 { 35 CloseHandle(MappingHandle); 36 MappingHandle = NULL; 37 } 38 printf("Input AnyKey To Exit\r\n"); 39 getchar(); 40 return 0; 41 }