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

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

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

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

HANDLE CreateFileMapping(
  HANDLE hFile,              // handle to file to map
  LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
                             // optional security attributes
  DWORD flProtect,           // protection for mapping object
  DWORD dwMaximumSizeHigh,   // high-order 32 bits of object size
  DWORD dwMaximumSizeLow,    // low-order 32 bits of object size
  LPCTSTR lpName             // name of file-mapping object
);

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

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

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

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函数,将内存映射文件映射到进程的虚拟地址中;

LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,  // file-mapping object to map into
                              // address space
  DWORD dwDesiredAccess,      // access mode
  DWORD dwFileOffsetHigh,     // high-order 32 bits of file offset
  DWORD dwFileOffsetLow,      // low-order 32 bits of file offset
  DWORD dwNumberOfBytesToMap  // number of bytes to map
);
 
hFileMappingObject:CreateFileMapping()返回的文件映像对象句柄。
dwDesiredAccess: 映射对象的文件数据的访问方式,而且同样要与CreateFileMapping()函数所设置的保护属性相匹配。
dwFileOffsetHigh: 表示文件映射起始偏移的高32位.
dwFileOffsetLow: 表示文件映射起始偏移的低32位.
dwNumberOfBytesToMap :文件中要映射的字节数。为0表示映射整个文件映射对象。

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

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

HANDLE OpenFileMapping(
  DWORD dwDesiredAccess,  // access mode
  BOOL bInheritHandle,    // inherit flag
  LPCTSTR lpName          // pointer to name of file-mapping object
);
 
dwDesiredAccess:同MapViewOfFile函数的dwDesiredAccess参数
bInheritHandle :如这个函数返回的句柄能由当前进程启动的新进程继承,则这个参数为TRUE。

lpName :指定要打开的文件映射对象名称。

4、进行内存映射文件的读写

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

//读操作:
if ( m_pViewOfFile  )
{
        // read text from memory-mapped file
        TCHAR s[dwMemoryFileSize];

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

        lstrcpy( (LPTSTR) m_pViewOfFile, s);

        // Notify all running instances that text was changed
        ::PostMessage(HWND_BROADCAST,
            wm_Message,
            (WPARAM) m_hWnd,
            0);
}

5、清理内核对象

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

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

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

6、简单例子

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

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

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

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

const UINT    wm_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()
 

LRESULT CIpcDlg::OnMessageTextChanged( WPARAM wParam, LPARAM lParam )
{
    if ( wParam == (WPARAM) m_hWnd )
        return 0;
   
    // Get text from memory mapped file and set it to edit box
    GetTextFromMemoryMappedFile();
   
    return 0;
}

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

void CIpcDlg::Initialize()
{
    m_edit_box.SetLimitText(dwMemoryFileSize - 1);
    m_hFileMapping = CreateFileMapping(
        INVALID_HANDLE_VALUE,           // system paging file
        NULL,                           // security attributes
        PAGE_READWRITE,                 // protection
        0,                              // high-order DWORD of size
        dwMemoryFileSize*sizeof(TCHAR), // low-order DWORD of size
        sMemoryFileName);               // name
    DWORD dwError = GetLastError();     // if ERROR_ALREADY_EXISTS
    // this instance is not first (other instance created file mapping)

    if ( ! m_hFileMapping )
    {
        MessageBox(_T("Creating of file mapping failed"));
    }
    else
    {
        m_pViewOfFile = MapViewOfFile(
            m_hFileMapping,             // handle to file-mapping object
            FILE_MAP_ALL_ACCESS,        // desired access
            0,
            0,
            0);                         // map all file

        if ( ! m_pViewOfFile )
        {
            MessageBox(_T("MapViewOfFile failed"));
        }

        // Now we have m_pViewOfFile memory block which is common for
        // all instances of this program
    }

    if ( dwError == ERROR_ALREADY_EXISTS )
    {
        // Some other instance is already running,
        // get text from existing file mapping
        GetTextFromMemoryMappedFile();
    }
}

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

void CIpcDlg::GetTextFromMemoryMappedFile()
{
    if ( m_pViewOfFile  )
    {
        // read text from memory-mapped file
        TCHAR s[dwMemoryFileSize];

        lstrcpy(s, (LPCTSTR) m_pViewOfFile);

        // Write text to edit box.
        // SetWindowText raises EN_CHANGE event and
        // OnChangeEditBox is called. Ensure that OnChangeEditBox
        // does nothing by setting m_bNotify to FALSE
        m_bNotify = FALSE;  

        m_edit_box.SetWindowText(s);    

        m_bNotify = TRUE;
    }
}

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

void CIpcDlg::OnChangeEdtText()
{
    if ( m_bNotify)        // change is not done by SetWindowText
    {
        // write text to memory-mapped file
        if ( m_pViewOfFile )
        {
            TCHAR s[dwMemoryFileSize];
            m_edit_box.GetWindowText(s, dwMemoryFileSize);

            lstrcpy( (LPTSTR) m_pViewOfFile, s);

            // Notify all running instances that text was changed
            ::PostMessage(HWND_BROADCAST,
                wm_Message,
                (WPARAM) m_hWnd,
                0);
        }
    }

}

至此,一个最简单的通过内存映射文件的进程通信的例子就完成了,但是这里存在一个问题:读和写之间的冲突没有很好的解决,内存映射文件是一个共享的资源,多个进程读写必然存在同步的问题,也许在这个例子中不会出现什么问题,但是实际项目中存在较高频率的并发读写的情况下,如何进行同步是一个必须解决的问题,未完待续…

时间: 2024-10-05 01:02:33

Windows进程通信 -- 共享内存(1)的相关文章

Windows进程通信 -- 共享内存

享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 Windows 中是用 FileMapping 实现的,从具体的实现方法上看主要通过以下几步来实现: 1.调用 CreateFileMapping 创建一个内存文件映射对象: HANDLE CreateFileMapping( HANDLE hFile, // handle to file to map

Windows进程通信-共享内存空间

三个模块 1,game.exe,三个方法,控制台输入指令('A','B','R')分别控制三个方法的调用: 2,WGDll.dll,要注入到game进程中的dll文件: 3,myconsole.exe,用来注入dll文件的程序: 先开启game进程,然后用myconsole把dll注入到game,dll模块和myconsole模块利用共享内存实现进程通信,在myconsole的控制台输入指令,dllmokuai接受指令,调用game模块的方法,达到控制game的目的 game模块 #includ

Linux程序设计学习笔记----System V进程通信(共享内存)

转载请注明出处:http://blog.csdn.net/suool/article/details/38515863 共享内存可以被描述成内存一个区域(段)的映射,这个区域可以被更多的进程所共享.这是IPC机制中最快的一种形式,因为它不需要中间环节,而是把信息直接从一个内存段映射到调用进程的地址空间. 一个段可以直接由一个进程创建,随后,可以有任意多的进程对其读和写.但是,一旦内存被共享之后,对共享内存的访问同步需要由其他 IPC 机制,例如信号量来实现.象所有的System V IPC 对象

Windows进程间共享内存通信实例

抄抄补补整出来 采用内存映射文件实现WIN32进程间的通讯:Windows中的内存映射文件的机制为我们高效地操作文件提供了一种途径,它允许我们在WIN32进程中保留一段内存区域,把硬盘或页文件上的目标文件映射到这段虚拟内存中.注意:在程序实现中必须考虑各进程之间的同步问题. 在Windows操作系统下,任何一个进程不允许读取.写入或是修改另一个进程的数据(包括变量.对象和内存分配等),但是在某个进程内创建的文件映射对象的视图却能够为多个其他进程所映射,这些进程共享的是物理存储器的同一个页面. 因

Linux学习笔记(14)-进程通信|共享内存

共享内存是允许两个不相关的进程访问同一个逻辑内存的进程间通信方法,是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式. 不同进程之间共享的内存通常安排为同一段物理内存.进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址. 就好像它们是由用C语言malloc()分配的内存一样.

进程通信之内存共享篇

进程通信之_ 内存共享 概念:共享内存是被多个进程共享的一部分物理内存.共享内存是进程间的共享数最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以写入数据,所有进程就可以立刻看到其中的内容. 实现步骤;1.创建共享内存,使用shmget函数2.映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数 创建:int shmget (key_t key,int size,int shmflg)key 标识内存功效的键值0/ipc_private.成功返回

Windows环境下共享内存通信

一.引言 在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯.WIN32 API提供了许多函数使我们能够方便高效的进行进程间的通讯,通过这些函数我们可以控制不同进程间的数据交换. 进程间通讯(即:同机通讯)和数据交换有多种方式:消息.共享内存.匿名(命名)管道.邮槽.Windows套接字等多种技术."共享内存"(shared memory)可以定义为对一个以上的进程是可见的内存或存在于多个进程的虚拟地址空间.例如:如果两个进程使用相同的DLL,只把DLL的代码页装入内存

C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转

原文:C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing). 内存映射文件对于托管世界的开发人员来说似乎很陌生,但它确实已经是很远古的技术了,而且在操作系统中地位相当.实际上,任何想要共享数据的通信模型都会在幕后使用它. 内存映射文件究竟是个什么?内存映射文件允许你保留一块地址空间,然后将该物理存储映射到这块内存空间中进行操作.物理存储是文件管理,而内存

进程-IPC 共享内存和消息队列 (三)

详见:https://github.com/ZhangzheBJUT/linux/blob/master/IPC(%E4%B8%89).md 五 共享内存 5.1. 共享内存简介 共享内存指多个进程共享同一块物理内存,它只能用于同一台机器上的两个进程之间的通信.在进程的逻辑地址空间中有一段地址范围是用来进行内存映射使用的,该段逻辑地址空间可以映射到共享的物理内存地址上(进程空间介绍:http://blog.csdn.net/zhangzhebjut/article/details/3906025