MemoryMappedFiles持久化与进程间通信问题求助
问题背景
我在使用.NET的MemoryMappedFiles实现IPC时碰到了两个头疼的问题:
- 每次请求同名MMF时,好像都会拿到新的文件,之前写入的字节下次访问就没了,只有靠持久化对象才能临时解决,但我觉得这根本没必要;
- 跨进程通信时,一个服务进程和一个用户应用进程始终获取不同的MMF对象,完全看不到对方的修改——而且两个进程用的
FileName完全一致,多次调用也没变化。
我是基于经典的IPC示例代码稍作修改的,调整read参数和相关条件逻辑后也没改善效果。
原实现代码
MemoryMappedFile file = null; private MemoryMappedFile GetMemoryMapFile(bool read) { if (file != null) return file; var security = new MemoryMappedFileSecurity(); var everyone = new System.Security.Principal.SecurityIdentifier(System.Security.Principal.WellKnownSidType.WorldSid, null); security.SetAccessRule( new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(everyone, MemoryMappedFileRights.FullControl, System.Security.AccessControl.AccessControlType.Allow)); MemoryMappedFile mmf; if (read) mmf = MemoryMappedFile.OpenExisting(FileName, MemoryMappedFileRights.Read, HandleInheritability.Inheritable); else mmf = MemoryMappedFile.CreateOrOpen(FileName, this.length, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, security, HandleInheritability.Inheritable); file = mmf; return mmf; } public Transfer ReadEntry() { try { var mf = this.GetMemoryMapFile(read: true); byte[] arr = new byte[length]; int offset = 0; using (var accessor = mf.CreateViewAccessor(0, length)) { accessor.ReadArray(offset, arr, offset, length); } var str = System.Text.Encoding.UTF8.GetString(arr, 0, length); return Serializer.DeserializeFromText<Transfer>(str); } catch (Exception) { return new Transfer(); } } public void WriteEntry(Transfer entry) { try { var mf = this.GetMemoryMapFile(read: false); int offset = 0; var str = Serializer.SerializeToText(entry); byte[] arr = System.Text.Encoding.UTF8.GetBytes(str); using (var accessor = mf.CreateViewAccessor(0, this.length)) { accessor.WriteArray(offset, arr, offset, arr.Length); } } catch { } }
后来我还尝试了简化版的获取方法,但不确定是否可行:
private MemoryMappedFile GetMemoryMapFile() { var mmf = MemoryMappedFile.CreateOrOpen(FileName, this.length); return mmf; }
问题根源拆解
你的核心问题其实出在服务与用户进程的权限隔离以及MMF的命名空间和访问逻辑上:
会话隔离导致的命名空间差异:服务进程通常跑在
LocalSystem账户下,而用户应用是当前登录用户的上下文。Windows的内存映射文件在不同用户会话下,哪怕文件名一样,也会因为私有命名空间的隔离,导致两边找不到同一个对象——服务创建的MMF默认在全局命名空间,而用户应用如果不指定,只会在当前会话的私有空间里找,自然碰不到一起。GetMemoryMapFile的逻辑坑:你用类级别的
file变量缓存MMF,但如果是多线程场景或者对象被意外释放,会导致重新创建;另外,read分支用OpenExisting,如果服务还没创建文件,用户应用会直接抛异常(但你catch了,所以返回空对象,看起来像没读到),而CreateOrOpen在不同会话下会直接新建一个MMF,而非共享已有的。读写细节的隐式问题:写入时如果序列化后的字节长度超过
this.length,会直接截断;另外,ViewAccessor虽然Dispose了,但跨进程场景下,数据同步可能有延迟,需要显式刷新。
针对性解决方案
1. 强制使用全局命名空间
要让服务和用户应用共享同一个MMF,必须在文件名前加上Global\前缀(比如Global\MySharedIPCBuffer),这样不管是服务还是用户应用,都能访问到同一个全局对象。注意:LocalSystem账户默认有权限创建全局命名空间对象,你配置的WorldSid权限也已经覆盖了用户应用的访问权限。
2. 简化并修复MMF获取方法
不需要区分read/write分支,直接用CreateOrOpen即可——它会自动复用已存在的MMF,不存在则创建。同时去掉不必要的缓存(或者确保缓存线程安全),避免缓存导致的对象隔离:
private MemoryMappedFile GetMemoryMapFile() { var security = new MemoryMappedFileSecurity(); var everyone = new System.Security.Principal.SecurityIdentifier(System.Security.Principal.WellKnownSidType.WorldSid, null); security.SetAccessRule( new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(everyone, MemoryMappedFileRights.FullControl, System.Security.AccessControl.AccessControlType.Allow)); // 关键:加上Global\前缀,强制使用全局命名空间 return MemoryMappedFile.CreateOrOpen($"Global\{FileName}", this.length, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, security, HandleInheritability.Inheritable); }
小提示:如果你的
FileName本身有特殊字符,确保加上Global\后符合Windows的命名规则。
3. 修复读写逻辑的细节
- 确保读写用的
length完全一致,避免序列化后的字节数组超过MMF大小导致截断; - 写入完成后显式调用
Flush,确保数据同步到内存,跨进程能立即看到:
using (var accessor = mf.CreateViewAccessor(0, this.length)) { accessor.WriteArray(offset, arr, offset, arr.Length); accessor.Flush(); // 显式刷新,消除跨进程同步延迟 }
- 调试阶段别吞掉所有异常,至少输出异常信息,方便排查问题。
4. 统一进程位数
确保服务和用户应用都是64位或者都是32位——混合位数的进程可能会出现MMF共享的奇怪问题(理论上支持,但实际调试中容易踩坑)。
测试建议
- 先在同一用户上下文下测试两个进程的通信(比如把服务改成当前用户账户运行),确认MMF能正常共享;
- 再切换到
LocalSystem账户运行服务,加上Global\前缀后,确认用户应用能读取到服务写入的数据; - 调试时可以用Windows Sysinternals的
VMMap工具查看两个进程的内存映射,确认它们是否映射了同一个MMF对象。
内容的提问来源于stack exchange,提问作者Andreas Reiff

