標籤:
前言:
使用CEF載入網頁,做JS與C++互動時,需要向主視窗發送一些訊息來通知介面做相應的處理。但是,由於CEF使用chrome核心是多進程架構的,渲染引擎與主程式都不在同一個進程裡面。因此,理所當然的就想到了使用共用記憶體了。為了更容易地使用,我們選擇的是ATL裡面封裝的共用記憶體操作類:CAtlFileMapping。
CAtlFileMapping使用:
定義結構體,包含你所需要共用的資料,這裡我們只需要共用主視窗的控制代碼
//自訂進程共用資料結構體struct PROCESS_SHARE_DATA{HWNDhMainWnd;};
使用</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 allocatorm_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 allocatorm_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更加簡單、方便,但是我們還是需要瞭解記憶體映射的機制的。
Windows上CAtlFileMapping共用記憶體的使用以及內部機制