共享进程间数据结构的简单包装器






4.87/5 (34投票s)
2001 年 9 月 10 日
5分钟阅读

235561

6320
一个简单的模板类,用于创建内存映射的共享数据结构。
本文内容
在高级 Windows 编程中,一个常见的任务是在两个或多个进程之间共享数据。关于这个主题已经写了许多文章,但很多对于我所需来说都过于复杂。我需要的是一种非常简单的方法来在我的应用程序的几个运行实例之间共享数据。由于这些实例之间的交互相当复杂,我希望自己处理同步等问题,而不是去寻找一个支持我所需同步级别的框架。
本文不包含内容
我说过,本文是关于一种在多个进程之间共享内存的简单方法。提供的代码在读取和写入数据块的访问方面没有任何同步措施。同样,所有连接到共享内存的客户端都可以完全访问共享内存。虽然为该类添加安全模型几乎是微不足道的,但我决定不这样做,以保持代码的简洁。
介绍 CSharedStruct
在本文中,内存通过我创建的一个名为 CSharedStruct
的类进行共享。这个类封装了共享内存区域。代码相对简单,而且很短,如下所示:
////////////////////////////////////////////////////////////////////// #if !defined(AFX_CSharedStruct_H__86467BA6_5AFA_11D3_863D_00A0244A9CA7__INCLUDED_) #define AFX_CSharedStruct_H__86467BA6_5AFA_11D3_863D_00A0244A9CA7__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define SHARED_NAME_SIZE 256 template <class StructType> class CSharedStruct { private: HANDLE m_hFileMapping; char m_hSharedName[SHARED_NAME_SIZE]; DWORD m_dwMaxDataSize; StructType *m_pvData; BOOL m_bCreated; public: CSharedStruct(); VOID Release(); BOOL Acquire(char *Name); StructType *operator->(); }; template <class StructType> StructType *CSharedStruct<StructType>::operator->() { return m_pvData; } template <class StructType> CSharedStruct<StructType>::CSharedStruct() { m_hFileMapping = NULL; } template <class StructType> VOID CSharedStruct<StructType>::Release() { if (m_pvData) { UnmapViewOfFile(m_pvData); } if (m_hFileMapping) { CloseHandle(m_hFileMapping); } } template <class StructType> BOOL CSharedStruct<StructType>::Acquire(char *Name) { m_dwMaxDataSize = 0; m_hFileMapping = CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(StructType), Name); if (m_hFileMapping == NULL) { return FALSE; } m_dwMaxDataSize = sizeof(StructType); strncpy(m_hSharedName, Name, SHARED_NAME_SIZE - 1); m_pvData = (StructType *) MapViewOfFile( m_hFileMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); if (m_pvData == NULL) { CloseHandle(m_hFileMapping); return FALSE; } return TRUE; } #endif
首先要注意的是,这是一个基于模板的类。虽然模板语法可能会使包含文件有点难读,但它使得访问共享数据的代码更加简洁。要创建一个共享内存对象,我们首先声明我们想要共享的结构,然后将其包装在 CSharedStruct
模板中,例如:
typedef struct _MyData { int x; char text[256]; } MyData; CSharedStruct<MyData> m_SharedData("SharedDataName");
传递给构造函数的文本字符串用于标识要使用的共享内存对象。因此,如果我们想共享多个内存缓冲区,我们需要给每个缓冲区一个唯一的名称。如果我们想访问另一个应用程序中的共享内存缓冲区,我们**必须**将相同的名称传递给构造函数。
//m_SharedData1 and m_SharedData2 actually 'point' to the same data CSharedStruct<MyData> m_SharedData1("Alpha"); CSharedStruct<MyData> m_SharedData2("Alpha"); //m_SharedData3, on the other hand, 'points' to a new block of shared memory CSharedStruct<MyData> m_SharedData3("Beta");
请记住,这些名称在**所有**进程中都需要是唯一的。如果您编写了一个使用名为“Shared”的共享结构的 myprogram.exe,而我编写了一个使用具有相同名称的 CSharedStruct
的 myotherprogram.exe,那么我们将指向相同的数据!因此,我建议使用 Platform SDK 中的 guidgen.exe 程序来创建唯一的 GUID
,并将其附加到您使用的任何名称后面,以确保安全。
同时也要认识到,这些共享内存段可以看到其他进程。如果有人知道了共享内存段的名称(例如通过使用 SysInternals 的 HandleEx 程序),他们就可以查看您的共享数据。如果您对此感到不安,请在将数据放入共享内存段**之前**对其进行加密,并为文件映射实现安全属性。
如果出于某种原因,您想使用默认构造函数,您必须通过显式调用 Acquire(char *Name)
来“初始化”共享内存缓冲区。我不确定您为什么会想这样做,但以防万一有需要。
最后,要访问结构的数据成员,请使用重载为指向我们模板类封装的共享内存的 ->
运算符。一个例子展示了这是多么容易做到:
m_SharedData.x = 255; strcpy(m_SharedData->text, "Hello Shared World!");
这是如何工作的?
CSharedStruct
类使用 Windows 内置的内存映射文件操作。这真的是一个非常简单的 API 调用:
m_hFileMapping = CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(StructType), Name);
CreateFileMapping
返回一个文件映射对象的句柄,通常用于将磁盘上的文件映射到用户模式可寻址的虚拟地址。但是,如果将 INVALID_HANDLE_VALUE
作为要映射的文件句柄传递,操作系统将使用一部分页面文件来支持它返回的虚拟地址。下一个参数,在这种情况下是 NULL
,指向一个安全属性对象。如果您关心共享数据的安全性,这里应该传递除 NULL
之外的值。PAGE_READWRITE
授予我们对共享内存的读写访问权限。同样,如果需要安全并且某些客户端只需要对数据的只读访问,这也可以更改。接下来的两个参数是以 64 位数字形式分割成两个 32 位整数的共享内存大小。除非您共享的数据超过 2GB,否则 dwMaximumSizeHigh
始终为零。最后,我们传入要访问的共享内存对象的名称。如果您在用户模式应用程序和 Windows NT/2000 服务之间共享数据,请确保在安装了终端服务的情况下,在名称前加上“Global”,否则您的共享结构将**不会**指向相同的数据。
示例应用程序
包含的示例使用 CSharedStruct
来共享一个简单的数据结构。虽然上面提供的代码不需要 MFC,但为了简单起见,示例是在 MFC 中构建的。它非常直接,除了 CSharedStruct
对象的声明之外,只有很少的代码值得关注。OnSet()
和 OnGet()
只是同步共享内存和用户界面。
要测试应用程序,您需要运行两个实例。我建议正常运行一个(例如,**不**调试),然后调试第二个实例,如果您想了解代码是如何工作的。在其中一个实例中输入 Number 和 Text 字段的值,然后按“Set”,然后切换到另一个实例,然后按“Get”……演示示例就这么简单!
更新,勘误
- 2001 年 9 月 17 日
更新了示例和源代码,修复了
Release()
方法中的两个错误,这些错误可能导致资源泄露。感谢 Stanley He 指出这一点。 - 2001 年 9 月 18 日
更新了示例和源代码以支持服务/用户进程交互。以前,如果服务先创建了文件映射对象,那么用户进程就无法访问它。简单的解决方案是修改文件映射对象的 ACL,给予所有人所需的访问权限。更健壮的安全问题留给读者练习。演示应用程序已更新,现在包括一个简单的服务(使用 Joerg Koenig 的 CNTService 类向导创建)。