应使用imgui::beginchild创建滚动区域,逐行手动渲染地址/十六进制/ascii三栏结构,按可视区域裁剪渲染,通过鼠标坐标逆向映射定位字节,维护编辑状态与脏字节集合实现安全覆盖式编辑。

ImGui::BeginChild + ImGui::TextUnformatted 实现基础十六进制视图
直接用 ImGui::TextUnformatted 渲染原始字节是行不通的——它不支持对齐、不可交互、无法定位光标。正确做法是用 ImGui::BeginChild 创建一个带滚动的固定区域,再逐行调用 ImGui::Text 或 ImGui::TextColored 手动拼出“地址 | 十六进制区 | ASCII区”三栏结构。关键点在于:每行显示 16 字节(标准),地址偏移用 ImFormatString 转成 8 位十六进制(如 "%08X"),十六进制字节之间加空格,ASCII 区非可打印字符统一替换成 '.'。
常见错误是把整块内存一次性转成字符串再渲染,导致大文件卡死或无法响应鼠标悬停。必须按可视区域计算起始行号和行数,只渲染当前可见的 20–50 行。
- 地址列宽度固定为 10 字符(含空格),用
ImGui::SetColumnWidth(0, 80.0f)锁死 - 十六进制区每字节占 3 字符(如
"FF "),共 16×3 = 48 字符 - ASCII 区固定 17 字符(16 字节 + 1 空格),用
ImGui::TextUnformatted避免自动换行截断 - 不要在循环里调用
ImGui::SameLine()多次——改用ImGui::Table(v1.89+)更稳定
如何响应鼠标点击并定位到指定字节偏移
ImGui 没有原生“字节级点击坐标换算”,得自己做逆向映射:监听 ImGui::IsItemHovered() 后,用 ImGui::GetItemRectMin() 和 ImGui::GetMousePos() 计算鼠标在当前行内的横向像素位置,再按每个字节在界面上的实际宽度(比如 3 字符 × 当前字体平均宽度)反推出是第几个字节。注意 ASCII 区和十六进制区要分开判断——用户可能点在 ASCII 列想编辑字符,也可能点在十六进制列想改数值。
容易踩的坑是忽略 DPI 缩放和字体变化。如果用了自定义缩放(io.FontGlobalScale),所有像素计算必须乘上该值;否则高分屏下点击偏移会严重失准。
- 十六进制区点击:x 偏移减去地址列宽度和空格,除以单字节像素宽 → 得字节索引
- ASCII 区点击:x 偏移减去前两列总宽,除以单字符像素宽 → 得字节索引(需 clamp 到 [0,15])
- 必须用
ImGui::CaptureKeyboardFromApp(true)抢占输入,否则 Tab/方向键无法聚焦到字节 - 光标状态建议存为两个整数:
m_CursorOffset(全局偏移)和m_CursorNibble(0=高位,1=低位,-1=未激活)
编辑字节时如何避免覆盖、插入或越界写入
十六进制编辑器默认是“覆盖模式”,不是“插入模式”。这意味着你不能让控件自动扩展缓冲区——用户敲 'A' 时,只应改当前字节的高 4 位;敲 'F' 时改低 4 位;敲回车才确认并移动光标。所有编辑操作必须作用于只读数据源的副本(如 std::vector<uint8_t></uint8_t>),原始文件或内存不可直写。
性能上,每次按键都 memcpy 整块内存是灾难性的。正确做法是维护一个“脏字节集合”(std::unordered_set<size_t></size_t>),只记录被改过的偏移,保存时遍历该集合回写即可。若数据源是 mmap 文件,更要确保写入前已用 mprotect() 或 VirtualProtect() 开启写权限。
- 键盘输入需拦截
ImGuiKey_0~ImGuiKey_F和ImGuiKey_Backspace,禁用其他键 - Backspace 应删除当前 nibble 并退格,不是删整个字节
- 越界检查必须在
WriteByte()接口内完成,不能依赖上层调用者 - 若底层是只读内存(如进程镜像),编辑前要弹窗提示“仅限预览”,并禁用所有写操作
为什么不能直接复用 ImHex 或 ImGuiMemoryEditor 的代码
ImHex 是完整应用,重度依赖 OpenGL/Vulkan 渲染管线和自定义着色器,它的内存视图早已脱离 ImGui 的 immediate-mode 逻辑,改用 GPU 加速绘制;而 ImGuiMemoryEditor(Dear ImGui 官方示例)只是 demo 级别:不支持搜索、无只读标记、不处理大文件分页、ASCII 区双击无法选中整行。两者都不能直接集成进你的 MFC/Qt/Win32 工程——前者编译依赖太重,后者功能缺失太多。
真正落地的方案是抄思路,不抄代码:取 ImGuiMemoryEditor 的地址/十六进制/ASCII 三栏布局逻辑,加上 silicondawn/memory-viewer 的视图抽象(start_addr, length, bytes_per_row),再自己实现搜索高亮和脏页管理。最易被忽略的是“编辑状态持久化”——用户切走又切回来时,光标位置、滚动偏移、当前编辑字节必须恢复,这需要你在 OnGUI() 外单独维护状态对象,而不是每次帧都重置。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











