Windows上CAtlFileMapping共享内存的使用以及内部机制
前言:
使用CEF载入网页。做JS与C++交互时。须要向主窗体发送一些消息来通知界面做对应的处理。可是,因为CEF使用chrome内核是多进程架构的。渲染引擎与主程序都不在同一个进程里面。因此。理所当然的就想到了使用共享内存了。为了更easy地使用,我们选择的是ATL里面封装的共享内存操作类:CAtlFileMapping。
CAtlFileMapping使用:
定义结构体,包括你所须要共享的数据,这里我们仅仅须要共享主窗体的句柄
//自己定义进程共享数据结构体 struct PROCESS_SHARE_DATA { HWND hMainWnd; };
使用</span>VS自带GUID生成工具创建一个GUID,来唯一标识这块共享内存</p><p>const TCHAR szShareGuid[] = _T("4F836C8D-F55E-4D88-A0BF-9ACDC0A33B31");定义共享内存全局变量
extern CAtlFileMapping<PROCESS_SHARE_DATA> g_ShareData;
初始化这块共享内存,并给数据赋值
g_ShareData.MapSharedMem(sizeof(PROCESS_SHARE_DATA), szShareGuid); ((PROCESS_SHARE_DATA*)g_ShareData)->hMainWnd=pMainWnd->GetHWND();好了。这样共享内存数据已经初始化完毕了。CEF进程在创建后也能够直接使用这个数据了。
打开共享内存,取出须要的数据
g_ShareData.OpenMapping(szShareGuid, sizeof(PROCESS_SHARE_DATA)); HWND hWnd = ((PROCESS_SHARE_DATA*)g_ShareData)->hMainWnd;然后,把这块共享内存句柄关闭
g_ShareData.Unmap();挺简单的吧。我们无需知道内部的API调用及实现,就能够轻松在多个进程间共享数据了。
可是。作为程序猿。我们应该知其然知其所以然。
那就进去看看实现吧,反正ATL的代码都能够看到。
内部实现:
首先是MapSharedMem,创建一块共享内存,
HRESULT MapSharedMem(</span>
_In_ SIZE_T nMappingSize, _In_z_ LPCTSTR szName, _Out_opt_ BOOL* pbAlreadyExisted = NULL, _In_opt_ LPSECURITY_ATTRIBUTES lpsa = NULL, _In_ DWORD dwMappingProtection = PAGE_READWRITE, _In_ DWORD dwViewDesiredAccess = FILE_MAP_ALL_ACCESS) throw() { ATLASSUME(m_pData == NULL); ATLASSUME(m_hMapping == NULL); ATLASSERT(nMappingSize > 0); ATLASSERT(szName != NULL); // if you just want a regular chunk of memory, use a heap allocator m_nMappingSize = nMappingSize; ULARGE_INTEGER nSize; nSize.QuadPart = nMappingSize; m_hMapping = ::CreateFileMapping(INVALID_HANDLE_VALUE, lpsa, dwMappingProtection, nSize.HighPart, nSize.LowPart, szName); if (m_hMapping == NULL) return AtlHresultFromLastError(); if (pbAlreadyExisted != NULL) *pbAlreadyExisted = (GetLastError() == ERROR_ALREADY_EXISTS); m_dwViewDesiredAccess = dwViewDesiredAccess; m_nOffset.QuadPart = 0; m_pData = ::MapViewOfFileEx(m_hMapping, m_dwViewDesiredAccess, m_nOffset.HighPart, m_nOffset.LowPart, m_nMappingSize, NULL); if (m_pData == NULL) { HRESULT hr; hr = AtlHresultFromLastError(); ::CloseHandle(m_hMapping); m_hMapping = NULL; return hr; } return S_OK; }通过CreateFileMapping传入一个INVALID_HANDLE_VALUE创建一个与物理文件无关的内存映射。传入映射内存的大小。
然后调用MapViewOfFileEx将其映射到内存中。返回内存首地址m_pData,这是一个CAtlFileMappingBase的成员变量。
然后我们查看CAtlFileMapping的定义
template <typename T = char> class CAtlFileMapping : public CAtlFileMappingBase { public: operator T*() const throw() { return reinterpret_cast<T*>(GetData()); } };
派生自CAtlFileMappingBase,然后对*进行了重载。使用reinterpret_cast将内存映射地址转换成我们的数据结构的地址,这样我们就能够通过CAtlFileMapping指针来直接訪问我们定义的数据结构的数据了。
其它进程使用共享内存,再看OpenMapping函数的实现
HRESULT OpenMapping( _In_z_ LPCTSTR szName, _In_ SIZE_T nMappingSize, _In_ ULONGLONG nOffset = 0, _In_ DWORD dwViewDesiredAccess = FILE_MAP_ALL_ACCESS) throw() { ATLASSUME(m_pData == NULL); ATLASSUME(m_hMapping == NULL); ATLASSERT(szName != NULL); // if you just want a regular chunk of memory, use a heap allocator m_nMappingSize = nMappingSize; m_dwViewDesiredAccess = dwViewDesiredAccess; m_hMapping = ::OpenFileMapping(m_dwViewDesiredAccess, FALSE, szName); if (m_hMapping == NULL) return AtlHresultFromLastError(); m_dwViewDesiredAccess = dwViewDesiredAccess; m_nOffset.QuadPart = nOffset; m_pData = ::MapViewOfFileEx(m_hMapping, m_dwViewDesiredAccess, m_nOffset.HighPart, m_nOffset.LowPart, m_nMappingSize, NULL); if (m_pData == NULL) { HRESULT hr; hr = AtlHresultFromLastError(); ::CloseHandle(m_hMapping); m_hMapping = NULL; return hr; } return S_OK; }
由于我们的共享内存创建时,使用了一个唯一的GUID标识,通过API OpenFileMapping来找到这个共享内存的内核对象,然后MapViewOfFileEx相同的将其映射到当前进程的内存空间里面,这样我们就能够訪问共享内存里面的数据了。
使用完毕后。内核对象都要释放掉。再看Unmap的实现代码
HRESULT Unmap() throw() { HRESULT hr = S_OK; if (m_pData != NULL) { if (!::UnmapViewOfFile(m_pData)) hr = AtlHresultFromLastError(); m_pData = NULL; } if (m_hMapping != NULL) { if (!::CloseHandle(m_hMapping) && SUCCEEDED(hr)) hr = AtlHresultFromLastError(); m_hMapping = NULL; } return hr; }首先解除当前进程的内存映射,然后关闭映射内核对象的句柄。
总结:
使用CreateFileMapping创建一个唯一标识(GUID)的内存映射,MapViewOfFileEx将其映射到当前进程的内存空间。保存须要共享的数据到这块内存中去。
其它进程訪问共享内存时。OpenFileMapping通过唯一标识(GUID)获取这块共享内存的内核句柄,调用MapViewOfFileEx映射到自己的内存空间,然后就能够读取里面的数据了,使用完毕后使用UnmapViewOfFile解除映射。CloseHandle关闭内核对象句柄。
尽管使用CAtlFileMapping更加简单、方便。可是我们还是须要了解内存映射的机制的。