系统有时需要控制一些程序不能同时运行,也就是多个程序互斥运行,还包括禁止 同一程序 运行多个实例,这可以用 内存映射文件 来完成。内存映射文件 可以创建一个 没有和磁盘文件 相联系 的内存对象,通过名字在进程间共享,使用这个特性,来实现这个设计。以MFC自动生成的对话框程序为例,在程序初始化阶段,CWinApp派生类的InitInstance函数开始处,添加下面的代码:
HANDLE hMap=CreateFileMapping((HANDLE)0Xffffffff,NULL,PAGE_READWRITE,0,128,"MutexRunning"); if(hMap==NULL) { AfxMessageBox("创建用于互斥运行的内存映射文件对象失败"); return false; } else if(GetLastError() == ERROR_ALREADY_EXISTS) { //已经存在这个同名对象,说明已经有需要互斥的其它程序运行了 LPVOID lpMem=MapViewOfFile(hMap,FILE_MAP_WRITE,0,0,0); CString str=(char*)lpMem; UnmapViewOfFile(lpMem); CloseHandle(hMap); //关闭这个对象 AfxMessageBox(str,MB_OK|MB_ICONSTOP); return false; } else { //经过上面的检查,说明我是第一个运行的程序 LPVOID lpMem=MapViewOfFile(hMap,FILE_MAP_WRITE,0,0,0); //这里可以写入该程序运行的描述信息,上面报错提示的就是这个信息 strcpy((char*)lpMem,"已经有一个相同的程序在运行!"); UnmapViewOfFile(lpMem); }
当程序运行结束了,要记住调用CloseHandle(hMap)关闭这个对象句柄,可以在InitInstance函数最后return false之前调用。
其它非对话框类的程序,可以在各自的初始化和终止阶段添加类似代码。只是内存映射文件对象的句柄hMap可能在不同的函数中使用。那就要定义成全局的或是CWinApp派生类的成员变量了。
如果要考虑的更周到,使用 同样可以在进程间共享的 多线程同步对象类,如CMutex类对象,来控制对这个内存映射文件对象的读写访问。
CreateFileMapping 、MapViewOfFile、UnmapViewOfFile函数用法及示例
内存映射API函数CreateFileMapping创建一个有名的共享内存:
HANDLE CreateFileMapping(
HANDLE hFile, // 映射文件的句柄,//设为0xFFFFFFFF以创建一个进程间共享的对象
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全属性
DWORD flProtect, // 保护方式
DWORD dwMaximumSizeHigh, //对象的大小
DWORD dwMaximumSizeLow,
LPCTSTR lpName // 必须为映射文件命名
);
与虚拟内存类似,保护方式可以是PAGE_READONLY或是PAGE_READWRITE。如果 多进程 都对同一 共享内存 进行写访问,则必须保持相互间同步。映射文件 还可以指定PAGE_WRITECOPY标志,可以保证其原始数据不会遭到破坏,同时允许其他进程在必要时自由的操作数据的拷贝。
在创建映射文件对象后可以调用MapViewOfFile函数 映射到本进程 的 地址空间 内。下面说明创建一个名为MySharedMem的长度为4096字节的有名映射文件:
HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),NULL,PAGE_READWRITE,0,0x1000,"MySharedMem");
并映射缓存区视图(映射到本进程 的 地址空间 内):
LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);
其他 进程 访问共享对象,需要获得对象名并调用OpenFileMapping函数。
HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,FALSE,"MySharedMem");
一旦其他进程获得映射对象的句柄,可以象创建进程那样调用MapViewOfFile函数来映射对象视图。用户可以使用该对象视图来进行数据读写操作,以达到数据通讯的目的。
当用户进程结束使用 共享内存 后,调用UnmapViewOfFile函数以取消 其地址空间内 的 视图:
if (!UnmapViewOfFile(pszMySharedMapView))
{
AfxMessageBox("could not unmap view of file");
}
以下是一个示例程序:
void CreateFileMappingEx() { DWORD timebegin = ::timeGetTime(); HANDLE fp=CreateFile(TEXT("E://jyzhj2.rar"),//这里输入需要复制的文件 src GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if(fp == NULL) { cout<<"错误"<<endl; return; } DWORD dwBytesInBlock=GetFileSize(fp,NULL); //文件长度 //创建文件映射内核对象,句柄保存于hFileMapping HANDLE hFileMapping = CreateFileMapping(fp, NULL, PAGE_READWRITE, 0,//(DWORD)(dwBytesInBlock >> 16), dwBytesInBlock,//(DWORD)(dwBytesInBlock & 0x0000FFFF), NULL); int dwError = GetLastError(); //释放文件内核对象 CloseHandle(fp); //偏移地址 __int64 qwFileOffset = 0; //将文件数据映射到进程的地址空间 LPVOID pbFile=(LPVOID)MapViewOfFile( hFileMapping, FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF), dwBytesInBlock); HANDLE wp = CreateFile( TEXT("E://仙剑5.rar"),//这里输入 需要粘贴的文件 dst GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH, NULL); HANDLE hFileMapping2 = CreateFileMapping( wp, NULL, PAGE_READWRITE, 0,//(DWORD)(dwBytesInBlock >> 16), dwBytesInBlock,//(DWORD)(dwBytesInBlock & 0x0000FFFF), NULL); CloseHandle(wp); LPVOID pbFile2 = (LPVOID)MapViewOfFile( hFileMapping2, FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF), dwBytesInBlock); memcpy(pbFile2,pbFile,dwBytesInBlock); UnmapViewOfFile(pbFile2); UnmapViewOfFile(pbFile); CloseHandle(hFileMapping2); CloseHandle(hFileMapping); DWORD timeend = ::timeGetTime(); cout<<"CreateFileMapping和MapViewOfFile程序运行时间为"<<timeend - timebegin<<endl; }
hMap = CreateFileMapping(&HFFFFFFFF, Security, PAGE_READWRITE, 0, dataLen, mapFileName)
Mapdress=MapViewOfFile(&&&)
UnmapViewOfFile(Mapdress)(成功执行了)
每次都CloseHandle(hMap)(返回值1成功了)
循环完
--------------------------------------------------------------------
【homezj】:
hMap增加好象不是你的问题所在呀,CloseHandle后,再CreateFileMapping,返回的hMap不相同很正常,它是系统自动分配的。
Security结构中有一个继承成员,设为非零时,当前线程就可以保留同一映射的handle,这个结构你指定了吗?注意,结构长度要指定正确才有效。
----------------------------------------------------------
【S1010070】:
With Security '安全对象
.nLength = Len(Security)
.lpSecurityDescriptor = 0
.bInheritHandle = 1 ' Doesn't really matter
End With
但是我试验过,当hMap的值只要超过60000000之后,程序就会出现内存不能为写的错误
----------------------------------------------------------
【homezj】:
也许是磁盘缓冲的延时,以前听过Map文件,不是实时释放的说法。还有,你这个Map是用于什么目的,是进程间交换数据吗?其它程序有没有对它的操作?mapFileName是不是只用统一的一个名称?这东西我不熟悉,只是觉得CreateFileMapping这样频繁使用不太正常。
这是题外话了,内存映射文件的主要目的是在频繁交换数据时减少IO操作,你这样建了又放,而且不断反复,IO读写不但没少反而可能会增多,因为每次建放映射将是对整个文件长度的一次IO读写,比直接在磁盘上局部读写数据量还大。若非要这样做,你不如,不要映射,直接用磁盘文件算了。
可能MS也是没想到会出现这种用法,所以没考虑缓存会影响实时更新,或干脆就没打算支持这种用法。
我建议,CreateFileMapping是不是可以只用一次,保留handle,在定时器中使用。当然MapViewOfFile等可能也只需用一次,因不了解你的目的,请自己酌情考虑。
----------------------------------------------------------
【S1010070】:
首先谢谢你的分析,
这个程序的主要目的是实时的监视数据,当数据达到规定值执行某个动作,当然数据是来自另一个程序,当时就是从内存映射提取数据了,我把把文件结果填充好以后,放入指定文件名的内存映射文件,然后SendMessage令一个程序,它来填充数据,我得到数据后UnmapViewOfFile(Mapdress)-----CloseHandle(hMap)
你的意思我明白,但是软件结构就要改动很大,很麻烦了,这个软件又着急用,不知这位仁兄有何妙解?
----------------------------------------------------------
【homezj】:
是交换数据,我想按理mapFileName是统一的,也就是说只须一个进程去创建映射文件,而且CreateFileMapping只须在程序启动时执行一次,另一进程可用OpenFileMapping获取handle或本进程用DuplicateHandle复制一个handle传给它,每次Timer事件也不必CloseHandle,最后退出时别忘就行了。
map与unmap在timer中执行。这样可保证只建一个映射文件,我想改动起来,不会难吧?
不断Create并Close,对内存映射文件可能不适用,若用原来的方案,实在想不出解决办法。因为其用法本身可能就不对!