news 2026/10/12 11:56:09

EasyHook完整Demo解析:从DLL注入到API Hook的Windows函数拦截实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EasyHook完整Demo解析:从DLL注入到API Hook的Windows函数拦截实战

简介:一份基于 EasyHook 的函数钩子完整演示程序,面向 C++ 开发者、Windows 系统编程与逆向安全学习者,目标是解决如何对系统函数稳定挂钩并快速复用到自己工程中的实际问题。示例由 Hook 动态库与注入程序两部分构成:动态库封装了一套完整的挂钩机制,使用者在钩子数组中填写函数名和对应的新函数指针,即可扩展任意 Win32 函数的钩子,无需再修改底层重复代码;注入程序是基于 MFC 的对话框界面,只要输入目标进程号就能完成注入,操作直观,整个工程代码风格清晰,可在 VS2010 环境下稳定编译。资源共 36 个文件,以头文件与 C++ 源码、DLL 运行库、LIB 导入库及工程配置文件为主,包体仅 278KB,便于下载与学习。目前已有 2091 人学习下载,适合需要为程序增加监控、调试或扩展功能的开发者和希望快速用 EasyHook 做二次封装的技术人员。

1. 函数钩子不是黑匣子:EasyHook 的完整 Demo 到底能替你省多少事

做 Windows 客户端开发的人,早晚会撞上一个需求:在不改被调试程序源码的前提下,去看它内部某个 API 到底被调了几次、传了什么参数。初学者第一反应是往上堆调试断点,但等目标程序没有 PDB、或者是个不归你管的老模块时,才知道什么叫绝望。EasyHook 这种函数钩子库就是为了这一类场景存在的,它能在运行期直接拦截 Win32 乃至任意 native 函数,把调用日志、参数快照、返回值全部抓出来。这次拆的是一份基于 VS2010 C++ 的完整稳定 Demo,不是那种只有几个头文件的空壳,而是拿来改改就能跑的完整程序。适合正在做逆向分析、插件开发、或者被某位老 C++ 模块折磨的从业者,新手按步骤走也能在半小时内把第一个 Hook 挂上去。

2. 选型与原理:为什么 EasyHook 比老式 DLL 注入更稳,VS2010 版好在哪

2.1 钩子机制的三层拆解:从 SetWindowsHookEx 到 EasyHook 的绕行

Windows 上做钩子,最老套的路子是SetWindowsHookEx,但它只能钩消息层面的东西,比如键盘鼠标、消息队列,钩不到CreateFile这种被进程直接调用的导出函数。想要拦住这类函数,传统做法是写一个 DLL,然后让它注入到目标进程里,再自己手工改函数入口处的字节码,也就是俗称的 inline hook。这个方案有两个让所有做过的人都皱眉的点:一是注入过程本事就很容易被杀软盯上,二是你改完别人函数头五个字节,得保证自己写的跳转代码足够健壮,否则目标函数一被递归调用,栈直接给你干碎。

EasyHook 的做法是用所谓的 trampoline(蹦床)机制,它在被钩函数开头放一条绝对跳转,跳到 EasyHook 自定义的桩函数上,同时把原始函数头几条指令原样搬到一片新分配的内存里,在那里再补一条跳转回到原函数剩余代码。这样目标进程里别的模块调这个函数时,会先踩进钩子里,你的回调执行完,如果想放行,就继续执行搬移后的原始代码,整个调用链不丢上下文。中招的概率比手工改字节码小很多,因为 trampoline 的生成、参数栈处理、甚至 x64 下的寄存器传参,EasyHook 都帮你打点好了。

这份 VS2010 C++ 版 Demo 之所以我觉得值得下,是因为它把 EasyHook 的三个典型层都给你铺好了:

  • 第一层是RemoteHook,负责在远程目标进程里安装钩子,处理跨进程的注入与通信;
  • 第二层是HookManager(或者版本里对应的管理模块),维护钩子句柄、回调地址、被钩函数与 trampoline 的映射关系;
  • 第三层是具体的 Hook 方法,就是你自己写的回调函数,签名为int HookProc(...)或类似形式,EasyHook 在触发时把原函数参数按约定传进来。

这个分层非常重要,因为 DebugDemo 里看起来无非是一堆代码,但你在自己项目里复用时,必须搞清楚哪些是你不能碰的,哪些是你必须改的。比如 RemoteHook 的安装方式需要注入 DLL,如果你只改了回调内容却忘了适配目标进程位数,那照样跑不起来。

2.2 这东西到底稳在哪:线程级拦截与托管/非托管边界的处理

很多人听到 "EasyHook" 以为它是专门给 .NET 用的,其实它是同时支持托管和非托管的库。VS2010 这个老版本里有几个核心机制,比后来某些简化版靠谱得多:

一是线程劫持的命脉——ACL。EasyHook 在安装钩子时不是简单地CreateRemoteThread塞一个 LoadLibrary 进去,它用了一种带恢复机制的注入流程。注入线程跑完后,目标进程会恢复到正常线程调度,不会因为多线程竞争而随机崩。Demo 里Inject的调用参数里有一个RunTime或者是超时配置,这就是管这个的。我一般会把它设成 5 秒以上,尤其是目标进程在启动早期,线程太多、调度太乱时,超时太短会直接注入失败。

二是它对手动修改机器码的大小做了严格校验。EasyHook 的HookManager在安装钩子时,会判断被钩函数头部可替换的指令长度是不是满足最小 5 个字节(x86)或 12 个字节左右的约束,如果函数太短、或者开头是一个跳转表,它就会报NtHookManager初始化错误。这个错误早些年特别常见,因为新手喜欢钩一些像GetTickCount这种只占几字节的小函数,EasyHook 分分钟教你做人。

三是托管与非托管的边带处理。EasyHook 在回调触发时会自动帮你把调用方线程的上下文切到可调试状态,避免你一边在回调里访问托管堆、一边原始函数还在用非托管栈导致内存碰撞。Demo 里有一个明显的分隔线:REMOTE_ENTRY相关代码处理注入和通信,HookRoutes或HookProc部分只负责把从钩子处拿到的参数转发出来。这个设计值得学习——它把易错部分锁在库里,你能改的部分保持纯 C++ 接口,可读性和稳定性同时保证了。

2.3 为什么还要用 VS2010 的老版本

既然 EasyHook 有新版,为什么还要回头找这份 VS2010 C++ 版本?最现实的一个原因:很多遗留项目的主工程还锁在 VS2010,编译环境、SDK、第三方库全是老版本的,你贸然引入新版 EasyHook 的动态库,编译出来的东西动不动就报_MSC_VER冲突,或者ntdll.lib链接阶段报一堆unresolved external symbol。这份 Demo 是跟着 VS2010 工程配置走的,Platform Toolset 是v100,引入库和 DLL 的位数也明确是 32 位还是 64 位,省掉你自己来回试错的时间。

第二个原因是稳定性边界清楚。新版 EasyHook 加入了更多现代编译器适配和反注入保护,但对于只想在本地调试工具上做 API 拦截的人,这些新特性反而可能引入兼容性黑匣子。老版本代码路径简单,调起来顺,出了问题你能按着源码一行行跟进去。我见过某开发者把新版 EasyHook 动态库扔进生产工具,结果因为在回调里调了OutputDebugString被目标进程主动拉黑,换回老版本反而没事——倒不是说老版本多神,而是它的行为模式是多年验证过的。

所以我的建议是:如果你是接手了一个老工程,目标程序也是 x86 或 x64 的老模块,直接用这份 VS2010 Demo 做底子最合适;如果你是从零起步做新项目,也可以先拿它搞明白机制,再决定要不要换新版。

3. 把 Demo 跑起来:VS2010 下的编译、注册、注入三步走

3.1 工程结构与关键文件

解压后你会看到几个工程文件和文件夹,布局大同小异。典型结构有:

  • EasyHook.sln:主解决方案文件,双击就能在 VS2010 里打开。
  • DemoClient:被注入的目标进程示例。它往往是一个简单对话框或控制台程序,里面会调用MessageBox、CreateFile或某个自定义导出函数,用来证明钩子确实生效。
  • DemoInjector:负责把钩子 DLL 注入目标进程的模块。它本身也是个可执行程序,接受目标进程名或进程 ID 作为参数。
  • DemoHookDll:包含实际钩子回调的 DLL。这里面的HookProc就是你在钩子触发时要执行的业务逻辑。
  • 其他像EasyHook32.dll、EasyHook64.dll以及EasyHook.lib是库本身。

先打开解决方案,确认每个工程左边的项目图标有没有被 VS 正确识别。如果显示为“不可用”,右键选 Reload Project。最容易出的坑是路径里有中文或空格,尤其是路径中的“测试”这类目录,导致#include查找失败。我一般会先把整个包复制到D:\EasyHookDemo\这种纯英文路径下再解压。

3.2 编译顺序和配置选项

编译顺序很关键。先编译 EasyHook 基础库(通常会被标记为EasyHook32或EasyHook64),再编译DemoHookDll,最后编译DemoInjector和DemoClient。如果直接按解决方案里从上到下的顺序按 F7,有时候会因为DemoHookDll依赖的 lib 文件还没生成而报找不到EasyHook.lib。遇到这种情况不用慌,单独右键基础库工程重新生成一次。

VS2010 的编译环境要记得设置这几个选项(我按自己在两台机器上跑过的经验说):

  • 在项目属性的C/C++ -> Code Generation里,Runtime Library建议保持/MTd(Debug)或/MT(Release),而不要用/MDd或/MD。因为被注入目标进程可能本身是静态链接 CRT 的,你用动态 CRT,回调里调用fopen时会因为 CRT 句柄表不一致踩到多重初始化问题。这个坑非常隐蔽,表现出来就是回调第一次执行正常,第二次就崩溃。
  • 在C/C++ -> Preprocessor里确保定义了WIN32和_DEBUG之类的基本宏,如果找不到,用默认配置即可,但注意不要把 EasyHook 自带的宏覆盖掉。
  • 在Linker -> General里,Additional Library Directories要指向包含EasyHook.lib的目录;Linker -> Input里要明确写上EasyHook.lib,否则你会遇到LNK2019无法解析的外部符号。

编译成功后,把生成的EasyHook32.dll或EasyHook64.dll放到最终的输出目录里,最好和DemoHookDll.dll放一起,因为注入时会加载同一目录下的 DLL。

3.3 第一个能跑的注入实验

启动DemoClient,假设它的进程名是DemoClient.exe。这时候先别急着注入,打开任务管理器,确认目标进程的位数:如果它是 32 位,则必须使用 32 位版本的DemoInjector;如果是 64 位,则要编译并运行 x64 配置下的DemoInjector。位数不匹配是最容易忽略的第一大坑,报错通常是Access is denied或莫名其妙的File not found。

然后在命令行里执行注入命令:

DemoInjector.exe -p DemoClient.exe -h DemoHookDll.dll

这里-p指定目标进程名,-h指定钩子 DLL 的路径。如果你想用进程 ID,也可以用-pid 1234之类的参数,具体格式看DemoInjector的源码里ParseCommandLine函数。

执行后,观察DemoClient里是否弹出了日志输出窗口,或者在DemoHookDll里用OutputDebugString写出的调试信息是否在 Debug View 里可见。如果钩子没触发,先回到DemoClient源码里找到它真正调用的那个函数。

// 在 DemoClient 中,比如有一处调用是被钩目标 ::MessageBoxW(NULL, L"Hello EasyHook", L"Demo", MB_OK);

而DemoHookDll里的回调应该是这样:

// 钩子回调:拦截 MessageBoxW int __stdcall HookedMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) { // 这里已经把参数抓到了 OutputDebugStringW(lpText); // 放行原始调用 return RealMessageBoxW(hWnd, L" hooked! ", lpCaption, uType); }

这个回调对应的 trampoline 指针RealMessageBoxW是 EasyHook 在你安装钩子时自动填充的。你只修改lpText,其他参数原样放行,这就证明钩子已经跑在目标进程内。值得注意的参数说明:__stdcall不能省略,因为你钩的是 Win32 API,调用约定必须是__stdcall或对应类型,否则栈平衡不对,必崩。

我第一次跑这个 Demo 时,卡在-h参数给了相对路径,导致 DLL 没被加载。后来改成绝对路径就通了。如果你的钩子总是不生效,先看注入器输出有没有Successfully injected字样,有的话再检查目标进程是不是真的把这个 call 走到了你钩的函数上。有的函数在版本更新后,系统里实际调用的是MessageBoxW的底层实现,钩在导出层就无效,这时候需要换一个更底层的NtUserMessageBox之类的,但这不是本 Demo 的问题,看你的目标而定。

4. 核心参数与接口说明:怎么把钩子改造成自己业务要的样子

4.1 入口函数与安装参数

Demo 里最核心的安装代码是InstallHook(不同的版本叫法可能不一样),它的典型签名和参数如下:

bool InstallHook( const wchar_t* targetProcessName, const wchar_t* hookDllPath, const wchar_t* hookFunctionName, void* originalFunctionPtr, void* callbackPtr, void** trampolinePtr);

这段代码是承接上层指令和底层注入的关键。关键参数逐一说:

  • targetProcessName:目标进程名,必须带扩展名,例如L"DemoClient.exe"。不要传全路径,EasyHook 内部会通过快照查找进程;传全路径反而可能因为权限不足找不到。
  • hookDllPath:指向你的钩子 DLL 所在路径,刚才强调过要用绝对路径。
  • hookFunctionName:你打算钩哪个导出函数名,比如MessageBoxW。
  • originalFunctionPtr:目标进程里那个函数的真实地址。Demo 里通常用GetProcAddress(GetModuleHandleW(L"user32.dll"), "MessageBoxW")来获取。
  • callbackPtr:你自己写的回调函数指针,必须和原函数有相同的调用约定。
  • trampolinePtr:出参。EasyHook 会在这里返回一个可以调用的原始函数指针,你的回调里如果要放行原始逻辑,就调用它。

安装钩子后,别忘了对应使用UninstallHook或Unhook来复原。Demo 里通常会写一个挂钩的 CPluginDlg 或线程来管理钩子生命周期。如果你不恢复,进程退出时 EasyHook 会帮你清理,但如果你在一个会频繁创建销毁的进程里反复安装卸除,建议主动调Unhook,避免 trampoline 内存泄漏。

4.2 文件、网络、注册表等常见拦截点的配置方法

把 Demo 改成实际业务用的钩子,难点不在 EasyHook 本身,而是选好钩的地址和回调要处理的参数。这里分享我拆过几个项目的经验:

文件操作:钩CreateFileW或ReadFile,重点是拿到lpFileName和dwDesiredAccess。在回调里不要直接做重活,因为目标进程可能正处于文件加锁状态。我曾看到一个做法是在回调里把文件名写到一个共享内存队列,再由外部监控线程去分析,这样可以避免因为你的fopen造成文件句柄冲突。

网络请求:钩send/recv或者WSAConnect。注意,ws2_32.dll里的导出函数在 Windows 8 以后有些被KernelBase.dll转发,GetProcAddress时要考虑转发函数的情况,否则拿到的地址可能不是实际执行体,钩子不生效。另一个常见问题是你钩send的回调会被频繁调用,如果它在回调里分配内存、格式化字符串,性能会急剧下降。要提前设置一个高频调用检测,比如每秒最多处理多少条,否则在高吞吐服务器上会拖垮目标进程。

注册表:钩RegOpenKeyExW或RegQueryValueExW,这类回调相对低频,但要注意HKEY句柄的访问。有一些程序会用RegOpenKeyEx探测是否存在某个键,然后根据返回值决定行为。你可以在回调里模拟存在或不存在,但别在回调里调用 Reg 系列函数,会造成递归钩子。如果确实需要查询原始数据,使用 trampoline 指针去调用原始函数。

4.3 返回值、错误码与回调内的异常处理

回调函数的返回值语义必须和原函数完全一致。打个比方,如果你钩MessageBoxW,原函数返回的是int表示点击了哪个按钮;你回调里如果不小心把它写成了void,编译器不会报错,但运行时栈上返回值的位置会被后续逻辑误读,轻则按钮行为错乱,重则内存访问越界。所以我的习惯是:先从函数头文件里复制原始声明,再做两个改造:把函数名改成Hooked...,把末尾加一个Real...指针参数。

EasyHook 在回调里如果抛出了 C++ 异常(未捕获),它会尝试捕获并包装成错误码返回,但这不代表安全,因为 C++ 异常跨过 trampoline 的边界后,原始函数的栈帧可能已经被破坏。最好的办法是回调里用try/catch包住所有可能出错的代码,尤其不要catch(...)完还不处理,直接把异常吞掉——那样你可能永远不知道钩子已经失效了。

// 一个安全的回调骨架 int __stdcall HookedSend(SOCKET s, const char* buf, int len, int flags) { int ret = SOCKET_ERROR; try { SendLog(s, buf, len); // 你的业务逻辑 ret = RealSend(s, buf, len, flags); // 放行原逻辑 } catch (...) { // 记录错误,但不要再次触发钩子 OutputDebugStringA("send hook exception"); } return ret; }

参数说明:RealSend是 EasyHook 填充的 trampoline 函数指针。在 try 块里先做业务,再放行原调用,这个顺序不能反,因为如果原调用先执行了,你的回调再抛异常,目标进程可能已经开始处理网络事件了。另外,buf指针指向的数据块在回调返回后可能会被释放,所以如果你需要异步记录数据,要拷贝一份,而不是保存指针。

5. 避坑与常见问题:钩子不生效、崩溃、权限不足的 5 条血泪记录

5.1 注入总是失败,返回 Access is denied

现象:DemoInjector 执行后,输出提示OpenProcess失败或注入超时,查看 Windows 事件日志没有明显错误。

原因:目标进程权限比你的注入器高,常见的要么是管理员进程,要么是正在被调试器附着的进程。EasyHook 的注入需要以相同的完整性级别打开目标进程,否则OpenProcess(PROCESS_ALL_ACCESS)直接失败。

解决:用管理员身份运行你的注入器。最好是 VS2010 也启动管理员权限,这样调试时能直接命中。如果目标进程是系统服务,你要先获得 SeDebugPrivilege 权限。Demo 源码里通常没加,你需要自己加一段提升权限的辅助代码:

// 启用 SeDebugPrivilege HANDLE hToken; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { TOKEN_PRIVILEGES tp; LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid); tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, 0); CloseHandle(hToken); }

这段代码之后再用 DemoInjector 去注入高权限进程,成功率会高很多。

5.2 钩子看着装上了,但回调一次都不触发

现象:InstallHook返回成功,注入也显示成功,但目标进程行为完全没有变化。

原因之一:钩错了函数地址。有些 Win32 API 在不同进程中是被动态转发或延迟加载的,GetProcAddress拿到的可能是一个条形桩函数(shim),而不是实际的函数体。原因之二:目标进程的对应 DLL 还没加载,GetModuleHandle返回 NULL,导致GetProcAddress失败或拿到错误地址。

解决:在安装钩子前先强制加载目标 DLL。以CreateFileW为例,你可以先在你的注入器进程里加载一次kernelbase.dll获取地址,然后通过 EasyHook 的GetHookFunctionAddress之类的辅助函数读取目标进程的同名函数地址。或者干脆把GetProcAddress的调用放在注入 DLL 的 DllMain 里,这样能确认 DLL 确实加载了。更实用的做法是把预期函数地址打印出来,与目标进程里用调试器读取的地址对比,看看是否一致。

5.3 回调执行几次后进程崩溃,栈显示离奇的地址

现象:第一次 Hook 生效,第二次调用时目标进程直接异常退出,用调试器看是内存访问违例。

原因:极可能是 trampoline 内存保护页设置有问题,或者是回调函数里用了与原始函数不一致的调用约定,导致栈不平衡。我就遇到过某开发者把被钩函数声明成__cdecl,而实际 API 是__stdcall,前几个参数刚好碰巧能传对,到第四个参数时栈顶偏了,直接崩。

解决:严格核对函数原型。用def文件或 MSDN 上的声明复制粘贴,不要凭记忆手写。如果检查了签名没问题,就要考虑是否回调中调用了目标进程尚未加载、或被重定向的 CRT 函数。回调中尽量只做参数记录,不做动态字符串操作;确实需要时用StringCbPrintfA并用固定缓冲区。

5.4 注入后目标进程出现白色窗口,或者 UI 失去响应

现象:目标进程没有崩,但界面卡住,任务管理器显示 CPU 占用为 0,看起来像死锁。

原因:EasyHook 在安装钩子时会挂起目标进程的所有线程,等 trampoline 生成完毕后恢复。如果有一个线程恰好停在某个同步对象上,而该同步对象的释放逻辑就在你要钩的函数内部,那么注入线程与业务线程会互相等待,形成死锁。这个坑在高频调用同步区域的函数时特别明显。

解决:不要在目标进程临启动时立刻注入,等它完全启动后再试。还有一种方案是安装钩子时设置额外的超时参数,让 EasyHook 在挂起线程前先尝试获取线程上下文。如果程序里存在进程内通信锁,最好在注入前通过共享内存发出一个“请暂停业务处理”的信号,然后再安装钩子,完成后恢复业务。

5.5 同一函数在 32 位和 64 位下表现不一致

现象:同一个HookProc,用 32 位 DemoInjector 注入 32 位进程没问题,换到 64 位进程后,回调能触发但参数错位,字符串打印出来是乱码。

原因:x64 下函数的传参约定与 x86 完全不同,x64 使用rcx,rdx,r8,r9和栈混合传参。如果工程里默认编译的是 x86 的钩子 DLL,而目标进程是 x64,那么回调被调用时,EasyHook 把栈上的参数按 x86 格式拿到,再按 x64 格式解释,数据错位是必然。

解决:一定要为 x64 目标单独编译一份 x64 版本的钩子 DLL 和注入器。Demo 源码里通常在解决方案配置管理器上有Win32和x64两个配置,记得右键配置管理器,新建一个 x64 平台,而不是直接在工具栏上切平台。因为工程文件里的库依赖和输出路径都可能不一样,直接切平台会导致链接到旧库。另一个易错点是#pragma comment(lib, "EasyHook.lib")在不同配置下要指向对应的 32 或 64 位 lib,如果有条件,可以把库统一复制为EasyHook32.lib和EasyHook64.lib,避免混淆。

6. 进阶技巧:用日志把钩子行为变成可回放的调试证据

钩子装上、回调能跑,这只是第一步。真正让人头疼的是目标进程出问题时,你怎么把当时的调用现场还原出来。我的建议是把 Demo 里的日志模块改造成三个级别:普通日志、详细参数日志、内存快照日志。普通日志记录函数名、线程 ID、时间戳;详细参数日志把各参数展开,比如字符串内容、缓冲区大小;内存快照日志在关键数据块被释放前复制一份到独立文件。

实现时,不要直接在回调里写磁盘,因为目标进程的 I/O 路径可能被重定向或占满。常见做法是开一块共享内存环形缓冲区,回调只负责往缓冲区里写数据,外部监控进程用ReadFile或 Memory Mapped File 读取并落盘。这样可以避免钩子对目标进程造成 I/O 影响,也能保证高频调用下不遗漏记录。

另外一个很实用的技巧是用OutputDebugString配合调试器过滤,但不建议长期开。因为我踩过一个坑:目标进程如果有反调试策略,它会禁用调试输出通道,导致你的记录全部丢失还不敢报错。从那以后,我每次做钩子项目都会强制走一遍完整流程:先跑通 Demo 的原始安装,再钩一个低频测试函数验证回调,确认稳定后,再换到真正要跟踪的高频函数,并且所有业务日志都通过共享内存输出。希望这个流程帮到你,少走那些黑匣子一样的弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 11:55:13

Windows沙箱批量创建失败深度复盘:容量评估与资源保护机制

10月8号那天上午的故障,到现在总算能心平气和地坐下来写一份完整的复盘了。当时的情况用"蒙了"来形容一点都不夸张——Codex平台的Windows沙箱批量创建失败,主流程被拖住,业务方连续在群里问情况,而我们日志翻了半天才找…

作者头像 李华
网站建设 2026/10/12 11:53:40

OpenClaw工具拆解:sessions_list 与 sessions_history 的调用链与排错

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 11:52:58

AI日报实战:从信息采集到归档的完整流程与避坑指南

1. 一份“AI日报”到底在记录什么先说说我做这件事的起因。从去年开始,我每天早上到工位的第一件事,就是花二十分钟把过去二十四小时里跟人工智能相关的动态过一遍——新模型发布、开源项目更新、行业应用落地、工具链变动、值得关注的论文和产品。信息源…

作者头像 李华
网站建设 2026/10/12 11:52:41

反编译ctrip.rar:用javap与CFR提取机票订单业务骨架

简介:基于JSP实现的携程网仿制项目,面向Java Web初、中级学习者,通过复刻在线旅行平台的登录、搜索、预订等核心流程,还原从用户请求到后台处理再到结果展示的完整链路。压缩包共128个文件,约963KB,包含Jav…

作者头像 李华
网站建设 2026/10/12 11:52:27

Claude Code 失忆症的救星:claude-mem 长期记忆实践

Claude Code 用久了,你大概率会跟我一样遇到同一个坎:这玩意每次开新对话都像得了失忆症。上午刚跟它敲定的技术选型、目录约定、接口返回值结构,下午新开一个 session 它全部不认,你得把同样的背景从头再讲一遍。遇到复杂项目&am…

作者头像 李华
网站建设 2026/10/12 11:52:03

列车节能控制优化:物理建模+动态规划+XGBoost实战

简介:本资源是面向数学建模竞赛参赛者与自动化控制方向学习者的2023数维杯B题完整解决方案,聚焦节能列车运行控制优化这一典型运筹与动力学耦合问题。资源包含6个核心文件(5个Python脚本1份PDF研究报告),总大小1.17MB&…

作者头像 李华