进程间创建共享内存实例解析
对共享内存的学习整理:
创建共享内存分以下几个步骤:
1.定义共享内存的结构体;
2.利用CreateFileMapping函数创建共享内存;
3.定义指向共享内存结构体的指针pShareMem,利用MapViewOfFile函数将刚刚创建的内存映射到定义指针pShareMem。
读写共享内存分以下几个步骤:
1.定义共享内存的结构体;
2.用OpenFileMapping函数打开上面创建的共享内存区,该函数返回共享内存的地址;
3.将共享内存映射为文件指针;
4.定义指向共享内存结构体的指针,将共享内存的内容拷贝到结构体指针里。
#include <afxwin.h> #include <stdio.h> #include <stdlib.h> #include <cstring> #include <iostream> using namespace std; HANDLE hMap; HANDLE hAddress; int iErrCode; //第一步要定义所要共享的内容的结构体 typedef struct _TShareMem { char Data[256]; }TShareMem; void CreateMap() { CString strBuf; CString strShare = "共享内存"; char szBuf[256]; TShareMem* pShareMem; //创建共享内存,这个函数的最后一个参数即是创建的共享内存的名称 (*该函数的作用是创建一个文件映射内核对象,以告知系统文件映射对象需要多大的物理存储器。创建内存映射文件对象对系统资源几乎没有什么影响,也不会影响进程的虚拟地址空间。除了需要用来表示该对象的内部资源之外通常并不用为其分配虚拟内存,但是如果内存映射文件对象是作共享内存之用的话,就要在创建对象时由系统为内存映射文件的使用在系统页文件中保留足够的空间。函数第一个参数hFile为标识要映射到进程的地址空间的文件的句柄。虽然由于内存映射文件的物理存储器是来自于磁盘上的文件,而非系统的页文件,使创建内存映射文件就像保留一个地址空间区域并将物理存储器提交给该区域一样。第二个参数为指向文件映射内核对象的 SECURITY_ATTRIBUTES结构的指针,由此来决定子进程能否继承得到返回的句柄。通常为其传递NULL值,以默认的安全属性来禁止返回句柄的被继承。接下来的参数用于文件被映射后设定文件映像的保护属性。其可能的取值为PAGE_READONLY、 PAGE_READWRITE和PAGE_WRITECOPY。虽然在创建文件映射对象时,系统并不为其保留地址空间区域,也不将文件的存储器映射到该区域。但是,在系统将存储器映射到进程的地址空间中去时,系统必须确切知道应赋予物理存储器页面的保护属性。在设置保护属性时,必须与用 CreateFile()函数打开文件时所指定的访问标识相匹配,否则将导致CreateFileMapping()的执行失败。因此这里设置 PAGE_READWRITE属性。除了上述三个页面保护属性外,还有4个区(Section)保护属性也可以一起组合使用: 区保护属性 说明 SEC_COMMIT 为区中的所有页面在内存中或磁盘页面文件中分配物理存储器 SEC_IMAGE 告知系统,映射的文件是一个可移植的EXE文件映像 SEC_NOCACHE 告知系统,未将文件的任何内存映射文件放入高速缓存,多供硬件设备驱动程序开发人员使用 SEC_RESERVE 对一个区的所有页面进行保留而不分配物理存储器 后面的两个参数指定了要创建的文件映射对象的最大字节数的高32位值和低32位值,实际也就设定了文件的最大字节数(最大可以处理16EB的文件)。这两个参数可以满足确保文件映射对象能够得到足够的物理存储器这一基本条件。在参数设置的大小小于文件实际大小时,系统将从文件映射指定的字节数。这里将其设置为0,将使所创建的文件映射对象将为文件的当前大小,以上两种情况均无法改变文件的大小。如果设置的参数大于文件的实际大小,系统将会在CreateFileMapping()函数返回前扩展该文件。需要指出的是,文件映射对象的大小是静态的,一旦创建完毕后将无法更改。如果设置的文件映射对象尺寸偏小将导致无法对文件进行全面的访问。 在本节开始也曾提到过,创建文件映射对象是不需要花费什么系统资源的,因此遵循"宁多勿缺"的原则,一般应将文件映射对象的大小设置为文件大小的相同值。函数最后的参数将可以为映射对象命名。如果想打开一个已存在的文件映射对象,该对象必须要命名。对该名字字符串的要求仅限于未被其它对象使用过的名字即可。*) hMap=::CreateFileMapping((HANDLE)-1,NULL,PAGE_READWRITE,0,sizeof(TShareMem),_T("NewMap")); if ( hMap == NULL ) { iErrCode = GetLastError(); AfxMessageBox("不能创建内存映射文件!"); } //写共享内存区,将内存映射为文件 (*创建了一个内存映射文件对象并得到其有效句柄后,该句柄即可用来在进程的虚拟地址空间中映射文件的一个映像。在内存映射文件对象已经存在的情况下,映像可被任意映射或取消映射。在文件映像被映射时,仍然必须由系统来为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交。在进程的地址空间中,一个足够大的连续地址空间(通常足以覆盖整个文件映像)将被指定给此文件映像。尽管如此,内存的物理页面还是根据在实际使用中的需求而进行分配的。真正分配一个对应于内存映射文件映像页面的物理内存页面是在发生该页的缺页中断时进行的,这将在第一次读写内存页面中的任一地址时自动完成。MapViewOfFile()即负责映射内存映射文件的一个映像,函数的第一个参数为CreateFileMapping()所返回的内存映射文件对象句柄,第二个参数指定了对文件映像的访问类型,可能取值有FILE_MAP_WRITE、FILE_MAP_READ、FILE_MAP_ALL_ACCESS和 FILE_MAP_COPY等几种,具体的设置要根据文件映射对象允许的保护模式而定。这种机制为对象的创建者提供了对映射此对象的方式进行控制的能力, 接下来的2个参数分别指定了内存映射文件的64位偏移地址的低32位和高32位地址,该地址是从内存映射文件头位置到映像开始位置的距离。最后的参数指定了视图的大小,如果设置为0,前面的偏移地址将被忽略,系统将会把整个文件映射为一个映像。MapViewOfFile()如果成功执行,将返回一个指向文件映像在进程的地址空间中的起始地址的指针。如果失败,则返回NULL。在进程中,可以为同一个文件映射对象创建多个文件映像,这些映像可以在系统中共存和重叠,也可以与对应的文件映射对象大小不相一致,但不能大于文件映射对象的大小。*) pShareMem = (TShareMem*)MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0); memcpy(pShareMem,strShare,strShare.GetLength()); //或者代码如下:strcpy(pShareMem->Data,szBuf); //或者用WriteProcessMemory(),代码如下: //HANDLE hProcess=::GetCurrentProcess(); //WriteProcessMemory(hProcess,pShareMem,szBuf, 256,0);
//打开刚刚创建的共享内存区,这里可以是另外一个进程中的代码 hMap = ::OpenFileMapping( FILE_MAP_WRITE, false, _T("NewMap")); if (hMap == 0 ) { iErrCode = GetLastError(); AfxMessageBox("不能定位内存映射文件块!"); } //读共享内存区 hAddress = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0); // pShareMem = (TShareMem*)MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0); if ( hAddress == NULL ) AfxMessageBox("Can''t View Memory Map"); //对szBuf进行赋值,每个都是0 memset(szBuf,0,sizeof(szBuf)); //拷贝共享内存区的内容 CopyMemory(szBuf,hAddress,255); //或者用strcpy(szBuf, hAddress); //或者用ReadProcessMemory // HANDLE hProcess=::GetCurrentProcess(); // ReadProcessMemory(hProcess,pShareMem,szBuf, 256,0); AfxMessageBox(szBuf); //最后不要忘记释放内核对象 CloseHandle(hMap); // 如果忘记关闭对象,在程序继续运行时将会出现资源泄漏。 UnmapViewOfFile(hAddress); (* 当不再需要保留映射到进程地址空间区域中的文件映像数据时,可通过调用UnmapViewOfFile()函数将其释放该函数的输入参数为调用MapViewOfFile()时所返回的指向文件映像在进程的地址空间中的起始地址的指针。在调用MapViewOfFile()后,必须确保在进程退出之前能够执行UnmapViewOfFile()函数,否则在进程终止之后先前保留的区域将得不到释放,即使再次启动进程重复调用 MapViewOfFile()系统也总是在进程的地址空间中保留一个新的区域,而此前保留的所有区域将得不到释放。*) } void main(void) { CreateMap(); }