简单windows共享内存封装类

来源:互联网 发布:js狙击枪 编辑:程序博客网 时间:2024/06/14 19:44

在维护公司的一个项目的时候发现了一个共享内存类,看了一下注释,发现是chrome里头的代码,所以就把chrome的代码翻出来看了一个,果然写的不错,考虑的情况也确实比较多,想想之前看过了《windows核心编程》这本书也有讲,所以就把书中的相关章节又看了一遍,写这篇文章就算是一个总结吧

先上代码:


#include <Windows.h>#include <string>#include <process.h>class SharedMemory{public:    SharedMemory(BOOL bReadOnly = FALSE) : m_hLock(NULL),        m_hFileMap(NULL),        m_pMemory(NULL),        m_bReadOnly(FALSE),        m_dwMappedSize(0),        m_strName(L"")    {        }    BOOL Create(const std::wstring& strName, DWORD dwSize)    {        if (dwSize <= 0)            return FALSE;        HANDLE handle = ::CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, dwSize, strName.empty() ? NULL : strName.c_str());        if (!handle)            return FALSE;        // 已经存在了        if (GetLastError() == ERROR_ALREADY_EXISTS)        {            Close();            return FALSE;        }        m_hFileMap = handle;        m_dwMappedSize = dwSize;        return TRUE;    }    BOOL Open(const std::wstring& strName, BOOL bReadOnly)    {        m_hFileMap = ::OpenFileMappingW(bReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, FALSE, strName.empty() ? NULL : strName.c_str());        if (!m_hFileMap)            return FALSE;        m_bReadOnly = bReadOnly;        return TRUE;    }    BOOL MapAt(DWORD dwOffset, DWORD dwSize)    {        if (!m_hFileMap)            return FALSE;        if (dwSize > ULONG_MAX)            return FALSE;        ULARGE_INTEGER ui;        ui.QuadPart = static_cast<ULONGLONG>(dwOffset);        m_pMemory = ::MapViewOfFile(m_hFileMap,            m_bReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, ui.HighPart, ui.LowPart, dwSize);        return ( m_pMemory != NULL );    }    void Unmap()    {        if (m_pMemory)        {            ::UnmapViewOfFile(m_pMemory);            m_pMemory = NULL;        }    }    LPVOID GetMemory() const { return m_pMemory; }    HANDLE GetHandle() const    {        return m_hFileMap;    }    // 锁定共享内存    BOOL Lock(DWORD dwTime)    {        // 如果还没有创建锁就先创建一个        if (!m_hLock)        {            std::wstring strLockName = m_strName;            strLockName.append(L"_Lock");            // 初始化的时候不被任何线程占用            m_hLock = ::CreateMutexW(NULL, FALSE, strLockName.c_str());            if (!m_hLock)                return FALSE;        }        // 哪个线程最先调用等待函数就最先占用这个互斥量        DWORD dwRet = ::WaitForSingleObject(m_hLock, dwTime);        return (dwRet == WAIT_OBJECT_0 || dwRet == WAIT_ABANDONED);    }    void Unlock()    {        if (m_hLock)        {            ::ReleaseMutex(m_hLock);        }    }    SharedMemory::~SharedMemory()     {        Close();        if (m_hLock != NULL)        {            CloseHandle(m_hLock);        }    }    void Close()    {        Unmap();        if (m_hFileMap)        {            ::CloseHandle(m_hFileMap);            m_hFileMap = NULL;        }    }private:    HANDLE m_hLock;    HANDLE m_hFileMap;    LPVOID m_pMemory;    std::wstring m_strName;    BOOL m_bReadOnly;    DWORD m_dwMappedSize;    SharedMemory(const SharedMemory& other);    SharedMemory& operator = (const SharedMemory& other);};


共享内存的原理:

共享内存其实是一种特殊的文件映射对象,而文件映射对象本质上又是虚拟内存(这个知识点可以看《windows核心编程》或者在网上找资料了解学习一下)。

虚拟内存一般是通过页交换文件来实现的,这个页交换文件是什么呢?一般就是我们C盘中的pagefile.sys文件,如下图所示:




不过也有通过文件来实现的,比如我们双击exe文件,这时其实是用这个exe文件本身来作为虚拟内存来使用(参见《windows核心编程》中的讲解)。

而内存映射文件实现原理也就是通过文件来实现虚拟内存的,实现原理和双击exe的原理是类似的。这个文件基本上可以是任意文件,打开之后作为虚拟内存映射到进程

的地址空间中。

我们先来大致看一下虚拟内存的使用过程吧:

1. 在进程地址空间中预订区域(VirtualAlloc)

2. 给区域调拨物理存储器(VirtualAlloc)

3. 使用虚拟内存(memcpy等函数)

4. 撤销调拨物理存储器,释放所预订的区域(VirtualFree)

这里所说的物理存储器一般是指页交换文件(pagefile.sys)


我们再来看一下内存映射文件的使用过程:

1. 打开或者创建文件(CreateFile)

2. 创建文件映射对象(CreateFileMapping)

3. 映射文件视图(MapViewOfFile)

4. 使用内存映射文件(memcpy等函数)

5. 撤销文件视图(UnMapViewOfFile)

6. 关闭映射文件句柄(CloseHandle)


之前说内存映射文件本质上是虚拟内存,那么这两个过程又是怎么对应的呢?

其实这两个过程基本上是一一对应的,只不过在上层使用的时候我们感觉不到而已。

打开文件或创建文件可以看成是在准备虚拟内存的物理存储器。

创建文件映射对象可以看成是预订区域

映射文件试图可以看成是给区域调拨物理存储器,这个物理存储器就是之前打开或者创建的文件

撤销文件视图可以看成是撤销调拨物理存储器

关闭映射文件句柄可以看成是释放区域

整个过程基本上就是这样。


一个普通的文件映射对象的使用代码大致是这样子:

HANDLE hFile = CreateFile(...)HANDLE hFileMap = CreateFileMapping(hFile, ...);PVOID pView = MapViewOfFile(hFileMap,FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);memcpy(pView, ...);UnmapViewOfFile(pView);CloseHandle(hFileMap);CloseHandle(hFile);

而共享内存的代码有什么不同呢?

主要区别是物理存储器不一样,普通的内存映射文件都是使用磁盘上的文件作为物理存储器,而共享内存使用的是也交换文件(pagefile.sys)。

这个区别如何体现的代码上呢?就是在调用函数CreateFileMapping的时候第一个参数是INVALID_HANDLE_VALUE。

整个过程基本上讲解完了,下面来分析一下SharedMemory这个类。


我们要在不同的进程中进行共享,那么我们需要创建一个命名的对象(其他进程间通信方式有:粘贴板,Socket,WM_COPY消息,邮槽,管道等)。

还有一个问题是需要在不同线程之间进行同步,否则数据有可能会乱套。这里使用了一个互斥量。

现在顺便问一个问题,能用关键段吗?

不能,因为关键段不能跨进程使用,而且这种场合使用需要一个等待时间,关键段也是不支持的。

至于关键段和互斥量的具体区别也可以参见《windows核心编程》里头的讲解。

代码中的Lock函数会让第一个调用的线程占有互斥量,第二个调用者等待,用起来也是比较方便。


代码应该不用过多解释吧,下面来一下使用示例:


#include <Windows.h>#include <string>#include <process.h>#include <tchar.h>#define MAP_FILE_NAME (L"Global\\TestName")#define MAP_SIZE (4*1024)#define STRING_BUF (L"helloword")UINT __stdcall ThreadFunc(LPVOID lParam){    SharedMemory *pSharedMemory = NULL;    pSharedMemory = new SharedMemory();    pSharedMemory->Lock(INFINITE);    pSharedMemory->Open(MAP_FILE_NAME, FALSE);    pSharedMemory->MapAt(0, MAP_SIZE);    LPVOID pVoid = pSharedMemory->GetMemory();    int length = wcslen(STRING_BUF);    WCHAR *pbuf = new WCHAR[length + 1];        memcpy(pbuf, pVoid, length*2);    pbuf[length] = L'\0';    pSharedMemory->Unlock();    pSharedMemory->Close();    delete[] pbuf;    delete pSharedMemory;    int i = 0;    while(true)        Sleep(5000);    return 1;}int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd ){    SharedMemory *pSharedMemory = NULL;    pSharedMemory = new SharedMemory();    pSharedMemory->Lock(INFINITE);    pSharedMemory->Create(MAP_FILE_NAME, MAP_SIZE);    pSharedMemory->MapAt(0, MAP_SIZE);    LPVOID pVoid = pSharedMemory->GetMemory();    wcscpy((WCHAR*)pVoid, STRING_BUF);    pSharedMemory->Unlock();    _beginthreadex(NULL, 0, ThreadFunc, NULL, NULL, 0);     Sleep(30000);    delete pSharedMemory;        return 0;}

这里是在同一个进程的不同线程来测试的,用不同的进程也是没有问题的。

每个线程在使用的时候一定要加锁解锁,否则就有可能出现数据不一致的问题。

这个过程又一个很重要的问题,就是创建共享之后,不要调用Close函数把共享内存关闭了,这个过程在虚拟内存中相当于

是否了申请的区域,如果这样的话其他进程就不能使用了,OpneFileMapping会失败,也就是不能共享数据了。

之前发现OpenFileMapping失败了搞了半天才明白过来,看来之前对内存映射文件的理解还是不够深刻-_-

注意,释放pSharedMemory的时候也调用了Close函数,所以要保证其他进程在通信过程中不要释放pSharedMemory指针。


参考资料:

1. 《windows核心编程》

2. chrome源码中SharedMemory类的代码(在src\\base\\memory路径下)

3. http://www.cnblogs.com/kex1n/archive/2011/08/10/2133389.html

0 0
原创粉丝点击