ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

hexedit十六进制编辑控件集成指南:从嵌入到深度定制

hexedit十六进制编辑控件集成指南:从嵌入到深度定制 简介HexEdit是一款基于VC开发的十六进制编辑控件面向需要直接读写、搜索、替换二进制文件的Windows桌面开发者适用于软件调试、数据恢复、逆向工程及自定义文件解析等场景。控件提供双视图编辑界面可同时展示二进制与ASCII字符支持插入、删除、查找、替换等基础操作同时具备高亮显示、文件解析与文档/视图框架便于开发者理解控件内部机制并进行二次扩展。压缩包共53个文件以h/cpp源码为主包含演示工程、项目工程文件sln/vcxproj、资源定义文件rc/ico以及htm升级说明等文档还附带编译生成的exe、obj、pdb等可执行与中间文件包体约2.02MB整体目录结构清晰适合对照源码学习和调试。借助完整的示例程序与控制台测试对话框可直接查看HexEditCtrl的接口调用、高亮设置、文件读写流程和工程组织方式对评估十六进制编辑能力的开发者有实用价值。目前已有97人学习下载适合正在准备在VC项目中嵌入二进制编辑功能的中高级开发者参考。1. 把二进制编辑器塞进自己的界面别从零造轮子用十六进制编辑器扒过别人的固件、修过损坏的文件头、或者对着通讯报文找过错位字节的人应该都体会过这种别扭系统里的十六进制工具确实好用但它永远是一个独立窗口没法嵌进你自己正在开发的调试器、协议分析器或者上位机软件里。这份资源解决的就是这个痛点——它是一套可直接编译进 VC 工程的 hexedit 十六进制编辑控件源码不是命令行工具也不是独立 exe而是能像普通控件一样放进你的对话框、属性页、甚至 SDI 界面里的可编辑组件。适合的人群很明确做嵌入式上位机、固件分析工具、网络报文解析器以及任何需要在自家软件里直接查看和修改二进制数据的一线开发者。它把地址栏、十六进制区、ASCII 区、查找、字节编辑这些基础能力一次性给你省掉从零写一个编辑器的全部工作。2. 控件是拿来用的场景判断、能力边界与选型理由2.1 这种东西和独立十六进制编辑器有什么不一样理解这个控件的定位关键是搞清楚它和完整十六进制编辑器之间的差别。独立工具比如你平时用的那些绿色小软件通常把整套功能绑在一起文件关联、大文件映射、磁盘扇区读取、脚本批量替换。而这类源码控件走的是相反的路——它只提供一块可以交互的区域渲染十六进制字节流、处理键盘鼠标事件让你在对话框里摆放它就像摆放一个 Edit 控件那样。它内部做的是数据管理你自己决定数据从哪来、改完之后往哪写。所以选型之前先做一个判断你的需求是“把某个文件完整地打开让用户改”还是“在程序里某一块数据需要可视化和就地修改”如果是前者直接用成熟独立工具就行如果是后者这份资源才有价值。举个例子我在做某个通讯调试工具时需要把收到的数据帧以十六进制展示在界面下方同时允许手工修改某个字节重新组包——这种情况独立编辑器根本接不进来控件方案是唯一合理的路。资源本身的编辑控件形态一般会拆成三层结构最左侧是地址栏显示偏移量中间是十六进制区每个字节占两字符宽度空白分隔可以光标定位最右侧是 ASCII/字符区。三层联动滚动是这类控件的基本功拿到源码后先确认三层列宽和字体渲染是否固定这决定了后续定制量。2.2 明确边界大文件、连续流和稀疏数据谁不适合控件擅长的是中等规模的块状数据编辑。我的经验是单文件控制在几十兆字节以内体验最好。超过这个量级滚动、查找、撤销三块的性能会明显吃力因为内存里的数据副本和绘制刷新是同步开销的。还有一个容易被忽略的边界连续字节流。如果数据是实时不间断到达的比如串口抓包每分钟几千帧那它不适合直接当监控视图——它本质上是“静态数据编辑器”不适合做流式追加。数据到达后先积累成缓冲区再一次性加载进去或者做成定时快照刷新这才符合控件的设计前提。另外稀疏数据和大空洞比如磁盘镜像里大量 0xFF 填充区也很考验控件的压缩策略。有些控件实现里对连续重复字节做了折叠显示有些没有。拿到这份资源后可以先拿一个几十兆的镜像文件试试滚动流畅度再决定是否需要修改数据存储层。2.3 集成路径选哪种对话框、属性页还是子窗口实际接入时一般有三条路可以走挑哪条取决于你的工程框架。第一种也是最常见的一种放进 MFC 对话框直接在资源编辑器里画一个 Custom Control 占位区域初始化时把控件位置铺满这个区域。这条路改动最小适合工具类软件的上位机界面。第二种放进属性页PropertySheet的某一页里适合做“数据查看是软件中一个标签页”的产品形态。属性页的字体环境、背景色和对话框有些差异需要注意背景刷新的问题。第三种放到普通子窗口里通过 Create 指定父窗口句柄适合非 MFC 框架比如 Win32 SDK WTL或者想在分割窗口里动态布局的场景。这种路径自由度最高但也需要你这个控件容器本身支持窗口消息转发——它的鼠标滚轮、键盘消息都要正确到达子窗口。我的建议是只要工程是 MFC 对话框或文档视图结构就走第一种如果是插件形式或有复杂的动态布局需求走第三种。别上来就选第三种会多绕一层窗口管理和消息反射的排查工作量。3. 从源码到装入你的对话框编译、创建与首屏加载3.1 编译前必须做的三个确认把资源包解压之后不要急着往工程里拖文件。先花十分钟确认三件事能省掉后面一整个下午的排查时间。第一确认字符集设置。现在的 VS 工程默认是 Unicode而这类老控件源码很可能在 ANSI 时代写的或者相反。进工程属性 - 常规 - 字符集看当前用的是“使用 Unicode 字符集”还是“使用多字节字符集”。如果控件源码里大量使用 CString 和 TCHAR 宏那它多半能兼容两种如果直接用了 char* 和 LPSTR就必须把工程切到多字节字符集或者做一层宽窄字符转换的封装。第二确认控件源码里有没有依赖 MFC 的特定版本。有些版本用了 CString、CRect 这些 MFC 基础类有些则尽量自包含以支持纯 SDK 工程。如果你只想让它独立工作不想拖整个 MFC 框架进工程那就需要选用纯 Win32 实现的那份源码。第三确认编译警告级别。这类控件代码通常比较老在老编译器上零警告在 VS2019 以上可能产生一堆 C4996比如 strcpy 被标记为不安全。直接的做法是把警告级别调到 3 而不是 4或者在包含头文件之前加上警告抑制宏别花时间去逐条改代码。一个很常见的编译失败点是源码里用到的大小写不敏感的文件名——把 HexEdit.h 写成了 hexedit.hWindows 文件系统不区分但一提交到大小写敏感的仓库或打包给别人构建就立刻报“无法打开包含文件”。我用这类控件时会在第一步就把所有文件名统一并确认了一次编译通过。3.2 控件窗口的创建静态占位和动态创建的两套代码最稳的做法是先在对话框资源编辑器里放一个 Custom Control 占位控件把它的类名改成控件注册的窗口类名然后在 OnInitDialog 里把它的位置和大小拉满。这套流程的好处是对话框能正常使用 Tab 键切换焦点资源编辑器里可以直观摆位代码量最小。// 对话框头文件里声明控件句柄或封装对象 HWND m_hHexEdit; // OnInitDialog 里绑定占位控件 CWnd* pPlaceHolder GetDlgItem(IDC_HEXEDIT_PLACEHOLDER); CRect rc; pPlaceHolder-GetWindowRect(rc); ScreenToClient(rc); pPlaceHolder-DestroyWindow(); // 动态创建真正的控件实例 m_hHexEdit CreateHexEditWindow(WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL, rc, m_hWnd, IDC_HEXEDIT_REAL);这段代码要拆开理解GetDlgItem 拿到的是资源里的占位控件它的唯一使命是提供一个矩形区域DestroyWindow 把占位控件销毁避免它留在界面上碍事CreateHexEditWindow 才是真正的创建入口第四个参数传入父窗口句柄第五个参数是控件 ID后续所有消息路由和 SetDlgItemText 之类的调用都要基于这个新的 ID。如果你不想在资源编辑里放占位控件也可以直接在 OnInitDialog 里算坐标然后创建但要注意对话框的字体、DPI 缩放都会影响控件实际尺寸写死像素坐标在 96 DPI 和 150% 缩放下显示效果差别很大。资源编辑器占位的方式能天然适配 DPI 缩放因为对话框管理器会帮你处理对话框单位的换算。创建控件的窗口类注册一般在控件的 DLL 或静态库初始化函数里完成。如果资源包是静态库形式记得在对话框进程启动时显式调用一次 InitHexEdit 之类的注册函数如果是 DLL 形式通常在 DLL 的 DllMain 里自动注册但有些实现放在一个导出初始化函数里漏调一次就会出现“创建窗口失败找不到十六进制窗口类”的报错。3.3 首屏把数据喂进去全量加载与缓冲区有效性控件创建出来是空白的接下来的问题是数据从哪来以什么形式进来。资源包一般会提供两类接口一类是直接读文件路径控件内部用 CFile 读整个文件到内存另一类是接收外部缓冲区指针。// 方式一直接加载文件路径 BOOL bOK HexEdit_LoadFile(m_hHexEdit, _T(C:\\data\\firmware.bin)); // 方式二外部数据缓冲区加载数据已在内存中避免重复IO BYTE* pBuf (BYTE*)malloc(1024 * 1024); GetPacketData(pBuf); // 假设这是你从网络上取到的原始数据 HexEdit_OpenBuffer(m_hHexEdit, pBuf, 1024 * 1024, TRUE);方式一是最省事的但有几个细节要留意。LoadFile 内部如果实现不当会把整个文件读进一个大缓冲区遇到几百兆的文件会瞬间吃掉几百兆内存而文件长度超过 int 上限2GB时基本必挂。好一点的实现会用文件映射CreateFileMapping MapViewOfFile这样大数据量也不会内存爆炸但代价是写入后需要 FlushViewOfFile 才能真正落盘。方式二更灵活本地调试时可以先抓包、再塞进去展示。BOOL 类型的最后一个参数表示“控件是否接管这个缓冲区的生命周期”传 TRUE 表示控件释放时会帮你 free 掉这块内存传 FALSE 表示一个月后由你自己管理释放。这里是最容易踩坑的地方——生命周期语义不清楚的话轻则内存泄漏被工具检查警告重则二次释放崩溃。还有一类特殊情况你只想让用户查看十六进制内容禁止任何修改。这通常在控件里会有一个只读模式开关开户位置一般叫 EnableReadOnly 或 SetReadOnly 的成员函数。开启只读不意味着查找和跳转被禁用只是限制编辑操作这对做“报表型”展示页面非常实用。4. 编辑、查找、断点与对比四个核心功能的技术实现要点4.1 查找十六进制串、ASCII 串和范围限定查找是使用频率最高的功能。控件一般提供两种查找模式十六进制串输入“4D 5A 90 00”和文本串输入“MZ”。底层是同一个查找算法只是编码方式不同。// 查找十六进制模式输入字符串忽略空格大小写不敏感 int nOffset HexEdit_Find(m_hHexEdit, 4D 5A 90 00, FIND_HEX, // 查找模式十六进制 0, // 从偏移 0 开始找 -1); // -1 表示搜到文件末尾 // 查找文本模式直接传 ASCII 字符串 int nTextOffset HexEdit_Find(m_hHexEdit, RIFF, FIND_TEXT, nCurrentPos 1, // 从上次找到的位置之后继续 -1);返回的 nOffset 是文件内的绝对偏移量找到后通常配合滚动函数让光标跳到那个位置if (nOffset 0) { HexEdit_SetSelection(m_hHexEdit, nOffset, nOffset 4); HexEdit_EnsureVisible(m_hHexEdit, nOffset); }几个参数的实际含义要理解对。起始偏移传 0 表示从头找但如果你在循环查找“下一个匹配项”需要把起始偏移设成上一次找到的位置加 1否则会陷入无限循环——每次都从文件头重新找永远找到同一个位置。查找性能方面如果是几十兆的文件一次次从头扫每次查找都会是线性开销连续点“下一个”时体感会越来越卡。最好先确认控件内部是否有索引优化没有的话就自己把查找范围限制在合理区间内。自动跳转到找到的位置是查找功能完整性的重要部分需要控件同时提供了地址定位和选区高亮的配合。如果控件实现良好还会有额外的状态提示比如在状态栏显示“找到偏移 0x12345678”这种信息我在集成时通常会把这个返回值直接接到自己的状态栏逻辑上让用户获得反馈。4.2 字节写入与撤销栈直接改内存缓冲区不涉及文件句柄控件对所编辑数据的修改本质上就是改那块外部传入的缓冲区或内部持有的缓冲对象。编辑动作通过消息、键盘输入或者代码调用来完成不限于是用户手动操作。实际开发中软件里还有一类需求是代码自动修改某些字节——比如把某个配置字段的 CRC 计算完写回去。// 直接写入一个字节到指定偏移 BOOL bOK HexEdit_WriteByte(m_hHexEdit, 0x100, 0x5A); // 写入多个字节从偏移 0x100 开始连续写 4 字节 BYTE pNewData[] { 0x5A, 0xA5, 0x00, 0xFF }; HexEdit_WriteBlock(m_hHexEdit, 0x100, pNewData, 4);WriteByte 的第一参数是控件句柄第二参数是偏移量第三参数是新值。它只是更新内存数据并不会自动替你落盘。保存的动作一般单独提供常见的是 SaveFile 或专为“回写原文件”设计的接口因为这不是每次修改后都会触发的操作。撤销栈是这类控件最容易实现出体验差异的功能。实现得好的控件你连续改 20 个字节按 20 次 CtrlZ 能一步步回退每一部都精确还原。实现得粗糙的控件要么只支持一层撤销改错了只能手动改回去要么整块撤销一下子回到最初状态中间的操作全部丢失。拿到资源后先做一遍撤销压力测试随机改 50 个字节连续 Undo 50 次看最后一次是否真的回到初始状态。这一步测试能快速判断控件的编辑核心是否可靠。如果栈深度有限制记得在界面和代码里控制好用户可以连续撤销的步数上限。4.3 断点与跳跃调试器风格的地址定位十六进制编辑器里“断点”的含义比调试器里宽松得多通常指的是在某个偏移位置做一个书签标记方便反复回来检查。这类功能在做协议分析时非常有用报文头、CRC 区域、负载起点各打一个标记来回切换不用反复滚动屏幕。断点相关接口一般长这样// 添加一个断点标记 int nMarkId HexEdit_AddBookmark(m_hHexEdit, 0x2C00, _T(PackHeader)); // 跳转到指定断点 HexEdit_JumpToBookmark(m_hHexEdit, nMarkId);AddBookmark 的第一个参数是偏移量第二参数是给这个断点起的名字返回值是这个断点的 ID后续删除和跳转都用这个 ID 操作。断点的视觉呈现方式一般是在地址栏左侧画一个红色圆点或在对应行做整行高亮具体形式取决于控件实现。除了断点跳跃功能还包括“跳转到指定偏移量”比如在地址输入框里输入 0x2A40直接滚到那个位置。这里有个体验细节要注意跳转到文件末尾或超出文件长度的偏移时控件应当做钳制处理把位置限制在合法范围内而不是产生越界访问。集成时要测试一下边界输入比如输入一个负数或超大值看控件是否会友好地拒绝。断点列表的量一般维持在一百个以内体验较好太多会让绘制层和书签管理变得繁琐。如果你的业务需要大量标记比如解析一份几万行的配置文件每个字段打一个标记建议自己维护一组 offset 到名称的映射表而不是依赖控件内置的断点机制。4.4 文件对比差异统计与快速逐跳这个功能是很多十六进制编辑控件的加分项。文件对比可以辅助排查“两个固件版本之间到底改了哪里”这类经典问题。控件提供的对比接口一般按字节比较两个缓冲区返回差异块的起始偏移和长度。// 对比当前缓冲区与文件中的另一个文件 int nDiffCount HexEdit_CompareFile(m_hHexEdit, _T(C:\\data\\firmware_v2.bin), 0, // 当前缓冲区对比起点 0, // 外部文件对比起点 1024 * 1024); // 对比长度返回值 nDiffCount 是差异块的数量注意是“块”的数量不是“字节”数量。连续几个字节不同会被合并成一个差异块。对比完成后通常需要逐跳浏览这些差异位置int nFirstDiff HexEdit_GetFirstDiff(m_hHexEdit); HexEdit_SetSelection(m_hHexEdit, nFirstDiff, nFirstDiff 1);对比性能方面如果控件用 memcmp 按块比较几十兆文件通常一两秒内完成如果实现成逐字节循环比对那同样的文件可能要十几秒。做固件分析时我会倾向先把对比区域缩小到实际有变化的区间比如跳过文件尾部的空白填充区这样既省时间也能规避全文件对比时控件内部可能出现的效率瓶颈。文件对比还有一个实践要点外部文件加载失败时控件的行为。Reader 打开一个不存在或权限不够的文件控件应该返回明确错误码而不崩溃。我在接这个接口时都会先把外部文件是否存在校验一遍别指望控件帮你做这个检查自己多做一步提前防御后面会省很多排错力气。5. 集成 hexedit 的常见坑字符集、缓存刷新、坐标换算与滚动性能5.1 字符集不匹配导致中文显示乱码或编译失败现象工程是 Unicode 字符集控件读到的字节被解释成宽字符ASCII 区里的中文显示成乱码或者编译时出现一大堆“无法将参数从‘const char *’转换为‘LPCTSTR’”的错误。原因老控件源码里用 char 处理字符串而 Unicode 工程要求宽字符接口。问题出在顶层 API 的字符串参数传递层面不一定是显示乱码这一层。解决优先把控件工程和宿主工程统一到同一字符集。若控件源码是大改不动就在集成层做一个转换壳把所有宽字符文件名转换成 UTF-8 或 ANSI 的多字节字符串再调用控件内部接口。我一般会写一个小工具函数CStringA WideToAnsi(const CString strWide) { int nLen WideCharToMultiByte(CP_ACP, 0, strWide, -1, NULL, 0, NULL, NULL); CStringA strA; WideCharToMultiByte(CP_ACP, 0, strWide, -1, strA.GetBuffer(nLen), nLen, NULL, NULL); strA.ReleaseBuffer(); return strA; }这个转换函数度必须放在集成层而不是改动控件源码因为不同版本的控件内部存储格式不一样混改容易引入新的问题。5.2 修改字节后界面不刷新光标虽然移动了但显示还是旧值现象调用 HexEdit_WriteByte 成功返回 TRUE但控件画面上显示的仍然是原来的字节直到下一次鼠标点击或滚动才恢复正常。原因数据缓冲区的确被更新了但控件没有发出刷新指令Invalidate绘制层不知道自己需要重画。这类控件常见的问题是写接口和刷新接口分离写之后必须主动调用刷新函数好一点的实现会内部自动触发重绘差的则不会。解决每次调用 WriteByte 或 WriteBlock 之后强制调用控件的重绘接口或者向控件窗口发送 WM_PAINT 消息。HexEdit_WriteByte(m_hHexEdit, 0x100, 0x5A); // 强制刷新视图区域 InvalidateRect(m_hHexEdit, NULL, TRUE); UpdateWindow(m_hHexEdit);这个坑在做“代码自动修改字节”时尤其隐蔽——用户操作有键盘事件驱动刷新代码调用则容易忘记刷新。我后来养成的习惯是把“写字节 强制刷新”封装成一个函数所有修改都走这一个入口。5.3 地址越界写入往文件末尾后面写数据控件行为异常现象调用 WriteByte 时传入了超过数据长度的偏移比如文件只有 4096 字节却往偏移 0x5000 写了一个字节。结果要么返回 FALSE 不生效要么直接破坏内部数据结构的长度字段导致后续显示出现乱码或崩溃。原因控件内部可能存在一个不严谨的写入实现没有对偏移量做边界校验就开始了操作。有些实现会直接把缓冲区的长度改掉甚至用越界偏移计算新长度时产生负数长度。解决编码层面强制校验写入前自己检查偏移量写入长度是否超过当前数据缓冲区大小。if (nOffset nBufSize || nOffset nWriteLen nBufSize) { // 拒绝写入并提示 return FALSE; }这里有个细节值得注意某些情况下你想实现“扩展文件长度后写入”比如在文件末尾追加 100 字节数据。这个需求应该走专门的扩展接口通常是 ResizeBuffer 或 AppendData而不是直接越界写。先用扩展接口把缓冲区扩大再写新位置的数据。5.4 大文件加载缓慢 拖动滚动条卡顿鼠标滚轮更卡现象加载一个 200MB 的 bin 文件等了十几秒才显示出来显示之后拖动滚动条有明显掉帧鼠标滚轮一次滚多行时界面像幻灯片。原因控件在加载阶段就把全部文件读进内存并做了逐字节的界面初始化工作比如把每个偏移量的坐标都预计算了一遍。滚动时如果没做有效缓存只重画可视区每一帧都要全量重算字形位置和字符宽度必然卡顿。解决分批加载数据并使用更快的滚动方式。如果控件实现了虚拟模式只渲染可视区域的前后几行那加载大数据应该很快如果没实现就自己限流不让大文件直接进入到首屏展示或者把数据量裁剪到可视范围内再交给控件。相对来说加载 200MB 文件的预期时间应该在 12 秒内超过这个数就该从控件实现层面找原因。5.5 坐标换算问题鼠标点击某行选中的字节不是预期的那个现象点十六进制区第七行中间位置控件选中的却是一整行或选到了相邻行的开头字节光标定位总是差一两个字符位。原因源控件可能固定了等宽字体和列宽而宿主对话框的字体环境不一样导致字符宽度计算错误。控件内部一般采用“字节偏移 (行号 * 每行字节数) (列号 / 每字节字符宽度)”的计算模式字体变了列宽就变了命中区域自然偏移。解决确认控件创建时是否设置了自己的字体如果没有在创建后主动调用控件提供的设置字体接口并传入一个等宽字体// 使用等宽字体保证字符宽度一致 LOGFONT lf { 0 }; lf.lfHeight -14; lf.lfPitchAndFamily FIXED_PITCH | FF_MODERN; _tcscpy_s(lf.lfFaceName, _T(Consolas)); HFONT hFont CreateFontIndirect(lf); HexEdit_SetFont(m_hHexEdit, hFont);Consolas、Courier New 这类字体在 Windows 全平台上都有用它们可以保证十六进制区列对齐。如果控件自带的字体偏好设置能设置字符间距也可以顺手调一下但别改间距太狠否则又会破坏固定列宽的对齐。6. 定制扩展的方向从基础集成走向深度改造拿到这份资源后很多人问的最多的问题是“这个控件能不能改成我的风格”——比如背景涂黑、字节颜色区分、特定地址高亮。答案是可以的但要看清楚扩展点在哪儿。第一处可改的是绘制层。大多数十六进制控件在 OnDraw 或类似的地方集中绘制三个区域你要做的只是找到绘制 ASCII 区的那几行代码插入一个判断如果当前字节值小于 0x20 或者大于 0x7E就换一种颜色画。比如非打印字符用灰色、控制字符用红色// 伪代码示意在 ASCII 区绘制逻辑中插入特殊配色 COLORREF crColor IsPrintableByte(pData[i]) ? RGB(0,0,0) : RGB(160, 160, 160); pDC-SetTextColor(crColor);第二处可改的是数据来源对接。控件的默认数据源是文件或内存缓冲区但你可以把它的数据接口重新绑定到一个自己实现的数据源上——比如从远程设备读取数据块控件每次请求某段地址时就从设备实时读取而不是本地内存。这种改造把控件从“文件编辑器”变成“远程数据查看器”很多远程调试工就是这么做的。第三处可改的是事件通知。控件在修改字节、移动光标、执行查找后通常会给父窗口发 WM_NOTIFY 消息你可以在对话框的消息反射里拦截这些通知把事件转发给业务逻辑。比如用户修改了某个关键字段后自动触发重算校验和。这类拦截属于标准 MFC 反射机制控件部分的封装一般已经提供了消息类型定义直接查它的头文件里 WM_NOTIFY 的 code 值即可对接。我自己的习惯是做一个新工具时把控件修改字节的动作拦截到日志里记录谁在什么时间改了什么地址——调试过程中这个日志能帮你省下大量定位问题的时间。每接一个新项目我都会先用五分钟把这个控件的字体验证、撤销栈深度、越界写入防护这三项跑一遍确认无误才开始做功能开发。从那以后集成这类控件基本没有再出过运行时崩溃的意外这份资源里的源码虽然需要你自己动手打磨的地方还不少但把它跑通、改透之后你手里等于多了一把处理二进制数据的称手工具省下来的时间绝对值得。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表