【共享内存】利用MFC写的进程间通信——内存共享

进程通信——共享内存


对共享内存的理解

共享内存,就是一块由内核管理的物理内存,但是在不同进程间是可以调用API来得到自己进程相应HANDLE,因此可以对其进行读写、修改、执行(视获取方式)。其实在这里作为grean hand的我一开始就没弄懂虚拟地址和物理地址,其实每一个进程用的地址都是虚拟地址,即使是核心对象HANDLE,都是OS给出的虚拟地址。这样说清楚,我们就可以清晰地理解共享内存到是怎么“存放”的。这对共享内存中放置指针,而指针到底指向哪里也可以解释通。

windows下将要用到的API

//用来创建共享内存API
①HANDLE CreateFileMapping(HANDLE hFile,
   LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
   DWORD fProtect,
   DWORD dwMaximumSizeHigh,
   DWORD dwMaximumSizeLow,
   LPCTSTR lpName
   );

hFile//CreateFileMapping本来是用作磁盘文件映射到内存的,这里没有实际的文件,我们只是需要一个内存的映射,所以这里设为0xFFFFFFFF

 lpFileMappingAttributes//安全属性,具体查MSDN,这里我们不做特别要求,就设为NULL

fProtect//文件的读取方式(保护),PAGE_XXXX

dwMaximumSizeHigh//映射区大小的高32位,因为用不到,这里设为0

dwMaximumLow//映射区大小的低32位,这里就视你需要用到的虚拟内存而定了

lpName//共享内存的名字,不同进程之间要调用这块内存就需要用这个唯一值来调用,所以,设一个能记而又有意义的名字吧


//获取共享内存的地址,转换为自己设定的结构(当然系统类型也行的)

②LPVOID MapViewOfFile(HANDLE hFileMappingObject,

DWORD dwDisiredAccess,

DWORD dwFileOffsetHigh,

DWORD dwFileOffsetLow,

DWORD dwNumberOfBytesToMap

);

hFileMappingObject//这个是刚才映射文件的句柄

dwDisiredAccess//对共享内存的权限

dwFileOffsetHigh//反映一个偏移距离,它由系统的内存分配精度决定,这里设0

dwFileOffsetLow//反映一个偏移距离,它由系统的内存分配精度决定,这里设0

dwNumberOfBytesToMap//需要读取的共享内存大小,设为0的话就是都读取

//卸载过程,用两个API

③BOOL UnmapViewOfFile(LPVOID lpBaseAddress);

lpBaseAddress//共享内存的地址(所以说要了解,这里都是进出的虚拟地址)

   HANDLE CloseHandle(HANDLE handle);//这里的handle就是共享内存的句柄了




我们先来说说P2P的方式吧。(相当于每个进程都有创建Mapping File的能力)

在MFC我们自己的APP类头文件中定义将要写入的结构体:

struct MySharedMemory;//前置声明

extern MySharedMemory *SharedMemory;
//这个就是我们将要用到的结构体指针,写成extern是为了在Dialog中也能用
extern HANDLE hDataMutex;
//用一个简单的Mutex来防止race condition

struct MySharedMemory{
BOOL bFlag;
//查看是否有新内容(这里这样写是为了下一次用指针时做个对比)
char str[256];
//以数组的形式来写(用来和指针做对比)
};


在APP的源文件中读上述变量进行定义:

//在InitInstance()函数中,在Dialog生成之前

hDataMutex = CreateMutex(NULL, TRUE, "Data Mutex");


HANDLE hMapFile = CreateFileMapping((HANDLE)0xFFFFFFFF,//生成一块名为"My Shared Memory"的共享内存,由此可以看出是P2P
NULL,
PAGE_READWRITE,
0,
sizeof(MySharedMemory),
"My Shared Memory");
DWORD dwMapErr = GetLastError();
SharedMemory = (MySharedMemory*)MapViewOfFile(hMapFile,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (dwMapErr == ERROR_ALREADY_EXISTS)//如果该共享内存已经存在
{
SharedMemory->bFlag = true;
}
else
{
SharedMemory->bFlag = false;
}


ReleaseMutex(hDataMutex);


这样我们的Mapping File就有了。接下里就是实现读写操作,这个应该没问题。


//生成相应的读按钮事件

void CRepeatDlg::OnBnClickedReceive()
{
// TODO:  在此添加控件通知处理程序代码
WaitForSingleObject(hDataMutex, INFINITE);//查看是否可读


if (SharedMemory->bFlag)
{
m_Output.SetWindowTextA(SharedMemory->str);
SharedMemory->bFlag = FALSE;
}


ReleaseMutex(hDataMutex);
}


//生成相应的写按钮事件

void CRepeatDlg::OnBnClickedWrite()
{
// TODO:  在此添加控件通知处理程序代码
WaitForSingleObject(hDataMutex, INFINITE);


CString Tmp;
m_Input.GetWindowTextA(Tmp);
strcpy(SharedMemory->str, Tmp);
SharedMemory->bFlag = true;


ReleaseMutex(hDataMutex);
}


注:这里我在做的时候,遇到一个“MFC下Enter键关闭程序”的问题,一开始想重载DIALOG的PreTranslateMessage()来获取VK_RETURN,用CEdit的Setsel()和ReplaceSel()来解决问题的,但是发现直接把CEdit中Want Return、MultiLine、Auto VScroll都设为true即可。

好了,再来看一下指针的形式


//APP头文件中

struct MySharedMemory{
BOOL bFlag;
char *str;//当做指针
};


//APP源文件中

hMapFile = CreateFileMapping((HANDLE)0xFFFFFFFF,
NULL,
PAGE_READWRITE,
0,
sizeof(MySharedMemory)+256,//加上一块和数组时一样大的区域
"My Shared Memory");

……

SharedMemory->str = sizeof(MySharedMemory)+(char*)SharedMemory;//让指针指向空出的区域


我觉得这下你该想明白了,无论如何传递,我们绝对不能超出这块OS给我们规定的区域,否则就是进程非法访问内存,指针的地址在这个时候的操作不要忘了对其进行相应的偏移和基地址。


现在来看看指针版的C/S(更有助于理解指针)

就像我在上面写的一样,这可不是移除了几个控件而已。不过其实也不难,只是换了个API,让其中一个Sever端没有了创建共享内存的能力而已,但是也出来一点错,来看看。


先再了解一个API

HANDLE OpenFileMapping(DWORD dwDisiredAccess,

     BOOL bInheritHandle,

     LPCTSTR lpName

    );

dwDisiredAccess//对映射区的权限

bInheritHandle//如这个函数返回的句柄能由当前进程启动的新进程继承,则这个参数为TRUE

lpName//表示唯一的共享内存名,若不存在,GetLastError()=2


来看看改动吧


//在APP头文件中加入

extern char *MyProcessSharedStringMemory;//如同我所写的一样,这个是存储自己指针用的,等会儿会说到为 //这么麻烦


//在APP源文件中修改

hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "My Shared Memory");//这里名字我用了和前//面一样的

……//判断一下是否有Sever打开


//获取指针

MyProcessSharedStringMemory = (char*)MyMemory + sizeof(CMySharedMemory);

//MyMemory->str = (char*)MyMemory + sizeof(CMySharedMemory);

正如所见,一开始我用了下面的那句,但是发现,如果在Sever端还没写时就在Client中读,

会出错。考虑了一下,发现是这里会把指针修改,这样Sever在写之前指针就被改了,造成了非法访问内存。


所以说在共享内存中指针的使用是要加倍小心的。


这里给出《Multithreading Applications in Win32 The Complete Guide to Threads》中给出的建议

i 不要把 C++ collection classes 放到共享内存中。
i 不要把拥有虚函数之 C++ 类放到共享内存中。
i 不要把 CObject 派生类之 MFC 对象放到共享内存中。
i 不要使用“point within the shared memory”的指针。
i 不要使用“point outside of the shared memory”的指针。
i 使用“based”指针是安全的,但是要小心使用。


很喜欢编程,喜欢深入了解一切的实现,作为新人,希望前辈们多多指正,多多鼓励,谢谢。

你可能感兴趣的:(成长,MFC,共享内存)