You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MemoryMappedFiles持久化与进程间通信问题求助

解决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的命名空间和访问逻辑上:

  1. 会话隔离导致的命名空间差异:服务进程通常跑在LocalSystem账户下,而用户应用是当前登录用户的上下文。Windows的内存映射文件在不同用户会话下,哪怕文件名一样,也会因为私有命名空间的隔离,导致两边找不到同一个对象——服务创建的MMF默认在全局命名空间,而用户应用如果不指定,只会在当前会话的私有空间里找,自然碰不到一起。

  2. GetMemoryMapFile的逻辑坑:你用类级别的file变量缓存MMF,但如果是多线程场景或者对象被意外释放,会导致重新创建;另外,read分支用OpenExisting,如果服务还没创建文件,用户应用会直接抛异常(但你catch了,所以返回空对象,看起来像没读到),而CreateOrOpen在不同会话下会直接新建一个MMF,而非共享已有的。

  3. 读写细节的隐式问题:写入时如果序列化后的字节长度超过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共享的奇怪问题(理论上支持,但实际调试中容易踩坑)。


测试建议

  1. 先在同一用户上下文下测试两个进程的通信(比如把服务改成当前用户账户运行),确认MMF能正常共享;
  2. 再切换到LocalSystem账户运行服务,加上Global\前缀后,确认用户应用能读取到服务写入的数据;
  3. 调试时可以用Windows Sysinternals的VMMap工具查看两个进程的内存映射,确认它们是否映射了同一个MMF对象。

内容的提问来源于stack exchange,提问作者Andreas Reiff

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:52:37