memorymappedfile 是需亲手调试的底层设施,涉及跨会话隔离、页面对齐、同步机制与句柄泄漏等核心难点。

MemoryMappedFile 不是“高级教程”能讲清的工具,它是需要你亲手踩坑、调偏移、盯同步、查句柄泄漏的底层设施。用错一次,进程卡死、数据错乱、内存不释放——这些不是警告,是常态。
为什么 CreateOrOpen 会抛出“拒绝访问”?
这不是权限配置没加 Global\ 前缀这么简单。Windows 会根据当前会话(Session)隔离命名对象。服务进程(Session 0)和桌面用户进程(Session 1)默认无法看到彼此的命名内存映射文件。
- 服务端若以 LocalSystem 身份运行,必须显式使用
Global\MyMapName;客户端也得用完全相同的名称,不能只写MyMapName - 如果仍失败,检查目标进程是否启用了“提升的权限”(UAC 提权),而另一方没提权——这会导致安全上下文不匹配
-
CreateOrOpen在跨会话场景下可能静默失败,建议改用CreateFromFile或CreateNew并捕获UnauthorizedAccessException做明确 fallback
CreateViewAccessor 的偏移和长度怎么算才不越界?
它不像数组有边界检查,越界写入不会立刻报错,而是悄悄覆盖相邻内存或触发访问冲突(AV),错误堆栈里根本看不到 MemoryMappedFile 的影子。
- 偏移量
offset必须是系统页面大小的整数倍(通常是 4096 字节),否则CreateViewAccessor直接抛ArgumentException - 长度
length加上offset不能超过映射文件总大小,但这个总大小是你创建时指定的,不是源文件实际长度(CreateFromFile时尤其容易误判) - 结构体字段对齐必须手动对齐:比如
double写在偏移 8 是安全的,但写在偏移 9 就可能跨页、读取异常,甚至被优化器重排——用[StructLayout(LayoutKind.Sequential, Pack = 1)]强制紧凑布局
为什么两个进程读到的数据总是旧的?
这不是缓存问题,是典型的同步缺失。MemoryMappedFile 本身不提供任何读写顺序保证,也不刷新 CPU 缓存行。
- 不要依赖“写完就立刻能读”,必须配合同步原语:
Mutex控制临界区,EventWaitHandle通知数据就绪,Interlocked更新标志位 - 常见错误:只在写入后
Set()一个事件,但没保证写操作已对其他 CPU 核心可见——需在Set()前插入Thread.MemoryBarrier()或用volatile字段标记状态位 - 如果用
MemoryMappedViewStream,记得调用Flush();但ViewAccessor没有Flush方法,它的写入是直接落内存的,靠的是同步机制而非 IO 刷盘
句柄泄漏比你想象中更隐蔽
MemoryMappedFile 和 MemoryMappedViewAccessor 都持有操作系统内核句柄。一旦漏掉 Dispose(),句柄数持续上涨,直到进程被系统强制终止(错误码 0x80070006)。
- 嵌套
using是必须的:using (var mmf = ...) { using (var accessor = ...) { ... } },少一层就可能泄漏 - 不要把
mmf存在静态字段里长期持有——它不会自动释放,且不同进程间不能共享同一实例 - 调试时可用 Process Explorer 查看进程的 “Handle” 数量,搜索
Section类型句柄,数量持续增长就是泄漏信号
真正难的从来不是“怎么创建”,而是“怎么确保每次读写都原子、可见、可预测”。别信封装好的“共享内存类”,先亲手用 ViewAccessor.Write 和 Mutex 跑通一对进程,再谈抽象。否则,你只是把 bug 从内存偏移里,搬进了封装层的构造函数里。










