<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://wiki2.lessokaji.com/index.php?action=history&amp;feed=atom&amp;title=DObject%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%E5%8D%8F%E8%AE%AE</id>
	<title>DObject共享内存协议 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://wiki2.lessokaji.com/index.php?action=history&amp;feed=atom&amp;title=DObject%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%E5%8D%8F%E8%AE%AE"/>
	<link rel="alternate" type="text/html" href="https://wiki2.lessokaji.com/index.php?title=DObject%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%E5%8D%8F%E8%AE%AE&amp;action=history"/>
	<updated>2026-05-16T16:53:14Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.40.0</generator>
	<entry>
		<id>https://wiki2.lessokaji.com/index.php?title=DObject%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%E5%8D%8F%E8%AE%AE&amp;diff=1035&amp;oldid=prev</id>
		<title>Artheru：​Initial bilingual draft (auto-published)</title>
		<link rel="alternate" type="text/html" href="https://wiki2.lessokaji.com/index.php?title=DObject%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%E5%8D%8F%E8%AE%AE&amp;diff=1035&amp;oldid=prev"/>
		<updated>2026-05-16T14:00:10Z</updated>

		<summary type="html">&lt;p&gt;Initial bilingual draft (auto-published)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;lt;languages/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 概述 / Overview ==&lt;br /&gt;
'''DObject''' 是 MDCS 的跨进程 ''命名共享内存 pub/sub'' 通讯原语。Medulla 把传感器数据、上下位 IO、调度状态等都通过 DObject 发布；Detour / Clumsy / SimpleComposer / 监控工具按名字订阅。整个 MDCS 的进程间通讯只有这一条总线。&lt;br /&gt;
&lt;br /&gt;
'''DObject''' is MDCS's cross-process named-shared-memory pub/sub primitive. Medulla publishes sensor data, upper/lower IO, scheduler state via DObject; Detour / Clumsy / SimpleComposer / monitoring tools subscribe by name. It's the sole IPC bus in MDCS.&lt;br /&gt;
&lt;br /&gt;
实现：`D:\src\Fundamentals\FundamentalLib\DObject.cs:18-344`。&lt;br /&gt;
Implementation: `D:\src\Fundamentals\FundamentalLib\DObject.cs:18-344`.&lt;br /&gt;
&lt;br /&gt;
== 存储 / Storage ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! 平台 / Platform !! 后端 / Backing&lt;br /&gt;
|-&lt;br /&gt;
| Windows || `MemoryMappedFile(&amp;quot;io_shared&amp;quot;, 256 MB)` + meta `MemoryMappedFile(&amp;quot;io_shared_meta&amp;quot;, 1024 B)`&lt;br /&gt;
|-&lt;br /&gt;
| Linux || `$TMPDIR/io_shared` 文件 / file-backed fallback&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
容量 256 MB 是预分配上限；具体 DObject 槽按需扩展但不能超过总容量。&lt;br /&gt;
&lt;br /&gt;
The 256 MB pool is preallocated; per-DObject slots grow on demand but the pool cap is hard.&lt;br /&gt;
&lt;br /&gt;
== 二进制布局 / Binary layout ==&lt;br /&gt;
&lt;br /&gt;
  [meta 1024 B]&lt;br /&gt;
    [magic: 4 B  0x90 0x97 0x92 0x96]&lt;br /&gt;
    [FundamentalLib version hash: 4 B]&lt;br /&gt;
    [process count: 1 B]&lt;br /&gt;
    [PID list: 32 B (8 × int32)]&lt;br /&gt;
    [reserved]&lt;br /&gt;
&lt;br /&gt;
  [payload area ≤ 256 MB]&lt;br /&gt;
    slot 0:&lt;br /&gt;
      [next_offset: int32]&lt;br /&gt;
      [name_len:    int32]&lt;br /&gt;
      [name:        ASCII (≤ 32 B)]&lt;br /&gt;
      [size:        int32]&lt;br /&gt;
      [reserved:    int32]&lt;br /&gt;
      [payload:     N B]&lt;br /&gt;
    slot 1: same&lt;br /&gt;
    ...&lt;br /&gt;
    EOF: next_offset = 0&lt;br /&gt;
&lt;br /&gt;
== API ==&lt;br /&gt;
=== 构造 / Constructor ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
var dobj = new DObject(string name, int defSize = 1024);&lt;br /&gt;
// name ≤ 32 chars; creates new slot or attaches to existing&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 写入 / Post ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
dobj.Post(byte[] payload, bool recording = false);&lt;br /&gt;
// 1. acquires per-DObject named Mutex (&amp;quot;io_mutex_&amp;lt;name&amp;gt;&amp;quot;)&lt;br /&gt;
// 2. Marshal.Copy(payload) into the slot&lt;br /&gt;
// 3. signals per-DObject EventWaitHandle (wakes all Wait())&lt;br /&gt;
// 4. if recording == true, also append to .dorec file&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 读取 / Read ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
var reader = dobj.Reader(int offset, int len);&lt;br /&gt;
// reader is a delegate; on each invoke it:&lt;br /&gt;
// 1. acquires Mutex&lt;br /&gt;
// 2. copies [offset, offset+len) into a fresh byte[]&lt;br /&gt;
// 3. releases Mutex, returns the bytes&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 等待 / Wait ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
bool gotNewFrame = dobj.Wait(int timeoutMs);&lt;br /&gt;
// blocks on the EventWaitHandle; returns true on signal, false on timeout&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 并发模型 / Concurrency ==&lt;br /&gt;
* '''每 DObject 一把命名 Mutex''' (`io_mutex_&amp;lt;name&amp;gt;`) 保护 Post/Read 互斥。&lt;br /&gt;
* '''每 DObject 一个 EventWaitHandle''' 用于 `Wait()` 唤醒。&lt;br /&gt;
* Mutex + EventWaitHandle 都是系统级原语（Windows: 命名 Mutex + AutoReset Event；Linux: FSWEventWaitHandle）。&lt;br /&gt;
* 读者使用 `Wait` 阻塞到下次写入；轮询读取也 OK 但浪费 CPU。&lt;br /&gt;
&lt;br /&gt;
* Per-DObject named Mutex protects Post/Read mutual exclusion.&lt;br /&gt;
* Per-DObject EventWaitHandle wakes `Wait()`.&lt;br /&gt;
* Both are kernel-level primitives — cross-process safe.&lt;br /&gt;
&lt;br /&gt;
== 版本校验 / Version safety ==&lt;br /&gt;
Meta 块的第二个 4 字节存 ''FundamentalLib 编译哈希''。读取时若 `X.conf.EnsureFlibSame = true` 且哈希不匹配 → 抛异常。这能立刻发现&amp;quot;同一系统里加载了两个不同版本的 FundamentalLib&amp;quot;的灾难性 bug。&lt;br /&gt;
&lt;br /&gt;
The meta block stores the compiling FundamentalLib's version hash. On read, if `X.conf.EnsureFlibSame = true` and the hash differs → throw. Catches the &amp;quot;two different FundamentalLib.dll versions loaded in one system&amp;quot; disaster bug.&lt;br /&gt;
&lt;br /&gt;
【发现】 这是 MDCS 调试中最有用的护栏 —— 重新构建 FundamentalLib 但忘了重建消费者时，立刻抛错。&lt;br /&gt;
The single most useful guardrail in MDCS debugging.&lt;br /&gt;
&lt;br /&gt;
== 录制 / Recording ==&lt;br /&gt;
`dobj.Recording = true` 时每次 `Post()` 同时把数据追加到磁盘 `.dorec` 流。&lt;br /&gt;
With `dobj.Recording = true` every `Post()` also appends to a disk `.dorec` stream.&lt;br /&gt;
&lt;br /&gt;
格式 / Format: `[name] [DateTime.ToBinary] [RND id] [length] [payload]`。&lt;br /&gt;
&lt;br /&gt;
清理 / Cleanup: 按 ''年龄''（`RecordingKeepSeconds`）和 ''总磁盘大小''（`RecordingDiskSizeLimitMB`）双重边界，自动滚动。&lt;br /&gt;
Auto-cleanup bounded by both age (`RecordingKeepSeconds`) and total disk size (`RecordingDiskSizeLimitMB`).&lt;br /&gt;
&lt;br /&gt;
回放工具见 [[Special:MyLanguage/使用手册 - 数据录制与回放手册|使用手册 - 数据录制与回放手册]]。&lt;br /&gt;
&lt;br /&gt;
== 常见 DObject 槽 / Common slot names ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! 名字 / Name !! 发布者 / Producer !! 订阅者 / Subscriber !! 内容 / Payload&lt;br /&gt;
|-&lt;br /&gt;
| `Lidar2D`（或厂商后缀）|| Medulla 雷达插件 || Detour || 打包的 `LidarOutput`（点云 + tick）&lt;br /&gt;
|-&lt;br /&gt;
| `pose` || Detour || Clumsy, SimpleComposer || 6-DoF 位姿 + 协方差 + tick&lt;br /&gt;
|-&lt;br /&gt;
| `MedullaCartLowerIO&amp;lt;tag&amp;gt;` || Medulla CartActivator || SimpleComposer || `[AsLowerIO]` 字段 JSON&lt;br /&gt;
|-&lt;br /&gt;
| `MedullaCartUpperIO&amp;lt;tag&amp;gt;` || SimpleComposer / Clumsy || Medulla CartActivator || `[AsUpperIO]` 字段 JSON&lt;br /&gt;
|-&lt;br /&gt;
| 自定义 || 任意 || 任意 || 自定义二进制&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 使用模式 / Patterns ==&lt;br /&gt;
=== 写者侧 / Publisher pattern ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
public class MyLidar : Lidar2DIOObject&lt;br /&gt;
{&lt;br /&gt;
    void Loop()&lt;br /&gt;
    {&lt;br /&gt;
        while (true)&lt;br /&gt;
        {&lt;br /&gt;
            var cloud = ReadFromDevice();&lt;br /&gt;
            cachedLidar = cloud;&lt;br /&gt;
            frame += 1;&lt;br /&gt;
            tick = DateTime.Now.Ticks;&lt;br /&gt;
            output();  // 内部调用 dobj.Post(serializedCloud)&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 读者侧 / Subscriber pattern ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
var dobj = new DObject(&amp;quot;Lidar2D&amp;quot;);&lt;br /&gt;
var reader = dobj.Reader(0, 8 * 1024);  // expected payload size&lt;br /&gt;
while (true)&lt;br /&gt;
{&lt;br /&gt;
    if (dobj.Wait(5000))&lt;br /&gt;
    {&lt;br /&gt;
        var bytes = reader();&lt;br /&gt;
        var output = LidarOutput.Deserialize(bytes);&lt;br /&gt;
        ProcessFrame(output.points);&lt;br /&gt;
    }&lt;br /&gt;
    else&lt;br /&gt;
    {&lt;br /&gt;
        Console.WriteLine(&amp;quot;Lidar timeout!&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 注意事项 / Caveats ==&lt;br /&gt;
* '''DObject 名 ≤ 32 字符''' —— 超长会截断 / silently truncated.&lt;br /&gt;
* '''Post 是阻塞的''' —— 所有订阅者都未释放 Mutex 之前你写不进 / blocks while any subscriber holds the mutex.&lt;br /&gt;
* '''Reader 不重试''' —— 槽消失或重建时 reader 失效，需要重新 `new Reader`.&lt;br /&gt;
* '''跨主机不工作''' —— 同主机进程间专用 / Single-host only; not designed for cross-machine.&lt;br /&gt;
* '''256 MB 是硬限''' —— 大量大尺寸槽要监控用量 / monitor usage if many large slots.&lt;br /&gt;
&lt;br /&gt;
== 相关页面 / See also ==&lt;br /&gt;
* [[Special:MyLanguage/DObject|DObject (legacy page)]]&lt;br /&gt;
* [[Special:MyLanguage/Medulla软件架构|Medulla软件架构]]&lt;br /&gt;
* [[Special:MyLanguage/使用手册 - 数据录制与回放手册|数据录制与回放手册]]&lt;br /&gt;
* [[Special:MyLanguage/核心开发指南|核心开发指南]]&lt;br /&gt;
&lt;br /&gt;
[[Category:开发手册]]&lt;br /&gt;
[[Category:二次开发相关说明]]&lt;/div&gt;</summary>
		<author><name>Artheru</name></author>
	</entry>
</feed>