您的位置:首页 > 其它

Windows进程通信 -- 共享内存(1)

2014-10-11 11:05 525 查看
共享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信。因为是通过内存操作实现通信,因此是一种最高效的数据交换方法。

共享内存在Windows中是用FileMapping实现的,从具体的实现方法上看主要通过以下几步来实现:

1、调用CreateFileMapping创建一个内存文件映射对象;

HANDLECreateFileMapping(
HANDLEhFile,//handletofiletomap
LPSECURITY_ATTRIBUTESlpFileMappingAttributes,
//optionalsecurityattributes
DWORDflProtect,//protectionformappingobject
DWORDdwMaximumSizeHigh,//high-order32bitsofobjectsize
DWORDdwMaximumSizeLow,//low-order32bitsofobjectsize
LPCTSTRlpName//nameoffile-mappingobject
);


通过这个API函数将创建一个内存映射文件的内核对象,用于映射文件到内存。与虚拟内存一样,内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交
给该区域。它们之间的差别是,物理存储器来自一个已经位于磁盘上的文件,而不是系统的页文件。

hFile:用于标识你想要映射到进程地址空间中的文件句柄。该句柄可以通过调用CreateFile函数返回。这里,我们并不需要一个实际的文件,所以,就不需要调用CreateFile创建一个文件,hFile这个参数可以填写INVALID_HANDLE_VALUE;

lpFileMappingAttributes:参数是指向文件映射内核对象的SECURITY_ATTRIBUTES结构的指针,通常传递的值是NULL;

flProtect:对内存映射文件的安全设置(PAGE_READONLY以只读方式打开映射;PAGE_READWRITE以可读、可写方式打开映射;PAGE_WRITECOPY为写操作留下备份)

dwMaximumSizeHigh:文件映射的最大长度的高32位。

dwMaximumSizeLow:文件映射的最大长度的低32位。如这个参数和dwMaximumSizeHigh都是零,就用磁盘文件的实际长度。

lpName:指定文件映射对象的名字,别的进程就可以用这个名字去调用OpenFileMapping来打开这个FileMapping对象。


如果创建成功,返回创建的内存映射文件的句柄,如果已经存在,则也返回其句柄,但是调用GetLastError()返回的错误码是:183(ERROR_ALREADY_EXISTS),如果创建失败,则返回NULL;


2、调用MapViewOfFile映射到当前进程的虚拟地址上;

如果调用CreateFileMapping成功,则调用MapViewOfFile函数,将内存映射文件映射到进程的虚拟地址中;

LPVOIDMapViewOfFile(
HANDLEhFileMappingObject,//file-mappingobjecttomapinto
//addressspace
DWORDdwDesiredAccess,//accessmode
DWORDdwFileOffsetHigh,//high-order32bitsoffileoffset
DWORDdwFileOffsetLow,//low-order32bitsoffileoffset
DWORDdwNumberOfBytesToMap//numberofbytestomap
);


hFileMappingObject:CreateFileMapping()返回的文件映像对象句柄。

dwDesiredAccess:映射对象的文件数据的访问方式,而且同样要与CreateFileMapping()函数所设置的保护属性相匹配。

dwFileOffsetHigh:表示文件映射起始偏移的高32位.

dwFileOffsetLow:表示文件映射起始偏移的低32位.

dwNumberOfBytesToMap:文件中要映射的字节数。为0表示映射整个文件映射对象。


3、在接收进程中打开对应的内存映射对象

在数据接收进程中,首先调用OpenFileMapping()函数打开一个命名的文件映射内核对象,得到相应的文件映射内核对象句柄hFileMapping;如果打开成功,则调用MapViewOfFile()函数映射对象的一个视图,将文件映射内核对象hFileMapping映射到当前应用程序的进程地址,进行读取操作。(当然,这里如果用CreateFileMapping也是可以获取对应的句柄)

HANDLEOpenFileMapping(
DWORDdwDesiredAccess,//accessmode
BOOLbInheritHandle,//inheritflag
LPCTSTRlpName//pointertonameoffile-mappingobject
);


dwDesiredAccess:同MapViewOfFile函数的dwDesiredAccess参数

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

lpName:指定要打开的文件映射对象名称。
4、进行内存映射文件的读写

一旦MapViewOfFile调用成功,就可以像读写本进程地址空间的内存区一样,进行内存的读写操作了。

//读操作:
if(m_pViewOfFile)
{
//readtextfrommemory-mappedfile
TCHARs[dwMemoryFileSize];

lstrcpy(s,(LPCTSTR)m_pViewOfFile);
}
//写操作:
if(m_pViewOfFile)
{
TCHARs[dwMemoryFileSize];
m_edit_box.GetWindowText(s,dwMemoryFileSize);

lstrcpy((LPTSTR)m_pViewOfFile,s);

//Notifyallrunninginstancesthattextwaschanged
::PostMessage(HWND_BROADCAST,
wm_Message,
(WPARAM)m_hWnd,
0);
}


5、清理内核对象

在用完后,要取消本进程地址空间的映射,并释放内存映射对象。

//取消本进程地址空间的映射;
UnmapViewOfFile(pLocalMem);

pLocalMem=NULL;
//关闭文件映射内核文件
CloseHandle(hFileMapping);


6、简单例子

下面写一个简单的例子来说明:

例子:在一个进程的文本对话框中输入文本,同时在另一个进程的文本对话框中显示之前输入的内容。

constDWORDdwMemoryFileSize=4*1024;//指定内存映射文件大小

constLPCTSTRsMemoryFileName=_T("D9287E19-6F9E-45fa-897C-D392F73A0F2F");//指定内存映射文件名称

constUINTwm_Message=
RegisterWindowMessage(_T("CC667211-7CE9-40c5-809A-1DA48E4014C4"));//注册消息


指定消息处理函数

BEGIN_MESSAGE_MAP(CIpcDlg,CDialog)
//{{AFX_MSG_MAP(CIpcDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_DESTROY()
ON_REGISTERED_MESSAGE(wm_Message,OnMessageTextChanged)
ON_EN_CHANGE(IDC_EDT_TEXT,OnChangeEdtText)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


LRESULTCIpcDlg::OnMessageTextChanged(WPARAMwParam,LPARAMlParam)
{
if(wParam==(WPARAM)m_hWnd)
return0;

//Gettextfrommemorymappedfileandsetittoeditbox
GetTextFromMemoryMappedFile();

return0;
}

窗口初始化函数中进行内存映射文件的初始化:

voidCIpcDlg::Initialize()
{
m_edit_box.SetLimitText(dwMemoryFileSize-1);
m_hFileMapping=CreateFileMapping(
INVALID_HANDLE_VALUE,//systempagingfile
NULL,//securityattributes
PAGE_READWRITE,//protection
0,//high-orderDWORDofsize
dwMemoryFileSize*sizeof(TCHAR),//low-orderDWORDofsize
sMemoryFileName);//name
DWORDdwError=GetLastError();//ifERROR_ALREADY_EXISTS
//thisinstanceisnotfirst(otherinstancecreatedfilemapping)

if(!m_hFileMapping)
{
MessageBox(_T("Creatingoffilemappingfailed"));
}
else
{
m_pViewOfFile=MapViewOfFile(
m_hFileMapping,//handletofile-mappingobject
FILE_MAP_ALL_ACCESS,//desiredaccess
0,
0,
0);//mapallfile

if(!m_pViewOfFile)
{
MessageBox(_T("MapViewOfFilefailed"));
}

//Nowwehavem_pViewOfFilememoryblockwhichiscommonfor
//allinstancesofthisprogram
}

if(dwError==ERROR_ALREADY_EXISTS)
{
//Someotherinstanceisalreadyrunning,
//gettextfromexistingfilemapping
GetTextFromMemoryMappedFile();
}
}


内存映射对象内容的读取及显示:

voidCIpcDlg::GetTextFromMemoryMappedFile()
{
if(m_pViewOfFile)
{
//readtextfrommemory-mappedfile
TCHARs[dwMemoryFileSize];

lstrcpy(s,(LPCTSTR)m_pViewOfFile);

//Writetexttoeditbox.
//SetWindowTextraisesEN_CHANGEeventand
//OnChangeEditBoxiscalled.EnsurethatOnChangeEditBox
//doesnothingbysettingm_bNotifytoFALSE
m_bNotify=FALSE;

m_edit_box.SetWindowText(s);

m_bNotify=TRUE;
}
}


在文本框的OnChangeEdtText事件中写内存映射文件,并发wm_Message进行通知:

voidCIpcDlg::OnChangeEdtText()
{
if(m_bNotify)//changeisnotdonebySetWindowText
{
//writetexttomemory-mappedfile
if(m_pViewOfFile)
{
TCHARs[dwMemoryFileSize];
m_edit_box.GetWindowText(s,dwMemoryFileSize);

lstrcpy((LPTSTR)m_pViewOfFile,s);

//Notifyallrunninginstancesthattextwaschanged
::PostMessage(HWND_BROADCAST,
wm_Message,
(WPARAM)m_hWnd,
0);
}
}

}






至此,一个最简单的通过内存映射文件的进程通信的例子就完成了,但是这里存在一个问题:读和写之间的冲突没有很好的解决,内存映射文件是一个共享的资源,多个进程读写必然存在同步的问题,也许在这个例子中不会出现什么问题,但是实际项目中存在较高频率的并发读写的情况下,如何进行同步是一个必须解决的问题,未完待续…
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: