ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TaoToken 视角下的 VC 自杀程序:DeleteFile 与 ShellExecuteEx 自删除方案

TaoToken 视角下的 VC 自杀程序:DeleteFile 与 ShellExecuteEx 自删除方案 1. VC 自删除程序到底解决什么问题DeleteFile 为什么删不掉自己在 Windows 上写 VC 程序很多人第一次尝试“程序删自己”都会撞上同一堵墙调用DeleteFile(自身路径)返回失败系统提示“无法删除文件拒绝访问源文件可能正被使用”。这不是权限不够而是 Windows 的映像加载机制决定的——只要进程还在运行它的可执行文件映像就被系统锁定任何进程都无法删除它。这个场景在真实开发里非常常见卸载程序需要删掉自己的安装包、临时工具跑完要清理自身、绿色软件退出时不留残留。核心检索词就是VC 自删除它要解决的是“正在执行的 exe 如何把自己从磁盘上抹掉”。适合谁看写过 MFC 或 Win32 小工具、做过安装卸载逻辑、或者单纯好奇“自杀程序”原理的 C 开发者。难点在于时序删除动作必须发生在“本进程已经退出、文件句柄已释放”之后但删除命令又得由某个还活着的进程来执行。于是思路就分成两条主流路线。第一条是ShellExecuteEx CMD 路线主程序创建一个隐藏的命令行窗口进程把del 自身路径作为参数传给它然后通过调整两个进程的 CPU 优先级让主程序抢先退出、CMD 进程随后执行删除。这条路线依赖COMSPEC环境变量拿到 cmd.exe 路径用GetShortPathName把长路径转成 8.3 短路径避免空格问题是 VC6 时代最经典的写法。第二条是CreateProcess 克隆自身路线Jeffrey Richter 的 DeleteMe 就是代表程序先把自己复制到临时目录用CreateProcess启动这个副本副本用WaitForSingleObject等原进程退出后再DeleteFile原文件而副本自己则通过FILE_FLAG_DELETE_ON_CLOSE在句柄关闭时被系统自动删除。两条路线都绕不开三个热词DeleteFile、ShellExecuteEx、CreateProcess。理解它们的配合方式就理解了自删除的全部精髓。下面我会先讲清楚前置准备再给出可直接复制的配置和代码最后演示运行后文件真的消失的验证动作以及几个我踩过的坑。2. TaoToken 前置准备把 API Key、Base URL、Model ID 三件套配齐在动手写自删除代码之前先把开发环境里要用到的模型调用链路准备好。如果你打算用 AI 辅助生成或审查这段 C 代码或者把自删除逻辑接到某个自动化脚本里就需要一个稳定的模型接入点。TaoToken 提供的就是这样一个统一入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。接入的核心是三件套Base URL API Key Model ID。Base URL 固定为https://taotoken.net/apiAPI Key 需要到控制台生成Model ID 则根据你要用的模型填写。这三者缺一不可很多 401 报错就是因为 Key 没带对或者 Base URL 写成了带路径的完整地址。生成 Key 的入口在控制台的 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。进去之后新建一个 Key复制出来保存好它只会完整显示一次。如果你用的是 Claude Code 这类命令行工具接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各客户端的配置示例。对于长期做编码和 Agent 任务的场景可以考虑 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合高频调用。如果只是想先验证模型能不能通用模型对话页面最快https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。这里要强调一个常见误区Base URL 不要自己拼/v1/chat/completions之类的后缀客户端通常会自动补全。你只需要填https://taotoken.net/api这个根地址。Key 的格式一般是一串以特定前缀开头的字符串填错一个字符就会 401。配置好之后建议先用一个最小请求验证连通性确认 Key 和 Base URL 没问题再去折腾自删除代码。这样出问题时能快速定位是模型接入的问题还是 C 代码的问题不会两件事混在一起排查。3. 可复制配置VC 工程设置与自删除代码片段这一节给出可以直接落地的配置。先看工程层面在 Visual Studio或 VC6里新建一个 Win32 控制台或 MFC 对话框工程字符集建议用 Unicode但下面代码为了兼容老工程用了 TCHAR 宏两种字符集都能编译。链接器不需要额外库shell32.lib在 MFC 工程里默认已链接纯 Win32 工程需要手动加上#pragma comment(lib, shell32.lib)。先给一份 JSON 形式的接入配置方便你在支持配置文件的项目里直接引用路径和字段名保持和客户端约定一致{ base_url: https://taotoken.net/api, api_key: sk-你的Key粘贴在这里, model_id: claude-3-5-sonnet, timeout: 60 }如果你用的是 TOML 风格的配置比如某些 CLI 工具等价写法是[provider] base_url https://taotoken.net/api api_key sk-你的Key粘贴在这里 model_id claude-3-5-sonnet接下来是核心的自删除代码。第一种用ShellExecuteEx拉起 CMD 删除自身适合 MFC 对话框按钮触发void CSelfDeleteDlg::OnButtonSelfKill() { SHELLEXECUTEINFO sei { 0 }; TCHAR szModule[MAX_PATH] { 0 }; TCHAR szComspec[MAX_PATH] { 0 }; TCHAR szParams[MAX_PATH * 2] { 0 }; if ((GetModuleFileName(0, szModule, MAX_PATH) ! 0) (GetShortPathName(szModule, szModule, MAX_PATH) ! 0) (GetEnvironmentVariable(_T(COMSPEC), szComspec, MAX_PATH) ! 0)) { _tcscpy_s(szParams, _T(/c del )); _tcscat_s(szParams, szModule); _tcscat_s(szParams, _T( nul)); sei.cbSize sizeof(sei); sei.fMask SEE_MASK_NOCLOSEPROCESS; sei.hwnd 0; sei.lpVerb _T(open); sei.lpFile szComspec; sei.lpParameters szParams; sei.lpDirectory 0; sei.nShow SW_HIDE; if (ShellExecuteEx(sei)) { SetPriorityClass(sei.hProcess, IDLE_PRIORITY_CLASS); SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, szModule, 0); EndDialog(0); } } }第二种用CreateProcess克隆自身思路更干净副本负责删原件副本自己靠FILE_FLAG_DELETE_ON_CLOSE自动消失int WINAPI _tWinMain(HINSTANCE h, HINSTANCE b, LPTSTR psz, int n) { if (__argc 1) { TCHAR szPathOrig[MAX_PATH] { 0 }; TCHAR szPathClone[MAX_PATH] { 0 }; GetModuleFileName(NULL, szPathOrig, MAX_PATH); GetTempPath(MAX_PATH, szPathClone); GetTempFileName(szPathClone, _T(Del), 0, szPathClone); CopyFile(szPathOrig, szPathClone, FALSE); HANDLE hfile CreateFile(szPathClone, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); TCHAR szCmdLine[512] { 0 }; HANDLE hProcessOrig OpenProcess(SYNCHRONIZE, TRUE, GetCurrentProcessId()); _stprintf_s(szCmdLine, _T(%s %d \%s\), szPathClone, (int)(INT_PTR)hProcessOrig, szPathOrig); STARTUPINFO si { sizeof(si) }; PROCESS_INFORMATION pi { 0 }; CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, si, pi); CloseHandle(hProcessOrig); CloseHandle(hfile); } else { HANDLE hProcessOrig (HANDLE)(INT_PTR)_ttoi(__targv[1]); WaitForSingleObject(hProcessOrig, INFINITE); CloseHandle(hProcessOrig); DeleteFile(__targv[2]); } return 0; }编译配置上注意两点一是工程属性里“C/C → 代码生成 → 运行库”保持默认的 /MD 或 /MT 都行但如果你用了_tcscpy_s这类安全函数需要包含tchar.h和strsafe.h二是如果开了“将警告视为错误”ShellExecuteEx的返回值检查要写全否则会报 C4996 之类的警告。4. 验证请求与成功结果运行后文件真的消失了吗代码写完最关键的一步是验证。不要只看编译通过就以为成了自删除的坑几乎全在运行时。验证动作分三步。第一步把编译出的 exe 复制到一个干净的测试目录比如D:\selftest\app.exe不要放在工程输出目录里避免误删源码相关文件。第二步双击运行如果是对话框程序就点“开始自杀”按钮如果是控制台克隆版就直接运行。第三步立刻打开资源管理器盯着这个目录几秒内你会看到app.exe从列表里消失。这里有个细节SHChangeNotify(SHCNE_DELETE, ...)的作用就是通知资源管理器刷新如果你当前正停在该目录文件项会马上消失如果不调用它文件其实已经被删了但资源管理器可能还显示着旧图标刷新一下才不见。所以“看起来没删”有时只是没刷新用命令行dir确认最可靠。对于克隆版验证时还要看临时目录。副本 exe 会被创建在%TEMP%下名字类似DelXXXX.tmp。正常情况下副本进程退出后因为FILE_FLAG_DELETE_ON_CLOSE这个临时文件也会自动消失。你可以开两个窗口一个盯测试目录一个盯%TEMP%观察原件和副本先后消失的过程。如果你把自删除逻辑接到了模型调用链路上比如让程序跑完某个任务后自删那验证时还要确认 API 请求已经成功返回。可以用模型对话页面发一条测试消息https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 确认 Base URL 和 Key 工作正常再跑自删除程序这样能排除“是模型没通还是删除没生效”的混淆。实测下来最容易出问题的是路径带空格。比如程序放在C:\Program Files\test\app.exe如果不转 8.3 短路径del C:\Program Files\test\app.exe会被 CMD 解析成删C:\Program直接失败。所以GetShortPathName那一步不能省。另一个是权限如果程序装在Program Files下且没有管理员权限del会因为权限不足失败这时需要以管理员身份运行或者改用先复制到用户可写目录再删除的策略。成功的结果应该是测试目录里 exe 消失%TEMP%里没有残留副本任务管理器里没有遗留进程。三个条件都满足才算真正跑通。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth自删除本身是纯本地 C 逻辑但一旦和模型接入、CLI 工具混用报错就会五花八门。这一节把高频错误对照着讲清楚。401 Unauthorized几乎都是 Key 的问题。检查 API Key 是否完整复制、有没有多余空格、是不是用错了环境的 Key。Base URL 必须是https://taotoken.net/api不要写成带/v1的完整路径。如果用的是 Claude Code 或 Cline 这类工具确认配置文件里的字段名和文档一致Key 要放在正确的位置。local proxy failed这个报错通常出现在客户端配置了本地代理端口但代理没启动或者 Base URL 指向了本地地址。解决办法是把 Base URL 改回https://taotoken.net/api并检查客户端设置里有没有残留的http://127.0.0.1:xxxx代理项。注意这里说的是客户端自身的代理配置字段不是让你去搭什么网络工具纯粹是配置文件写错了。reading choices 相关报错一般出现在流式响应解析阶段说明返回的 JSON 结构和客户端预期不一致。常见原因是 Model ID 填错或者请求发到了不支持该模型格式的端点。确认 Model ID 拼写正确并且 Base URL 没有多加路径。如果客户端支持先用非流式模式测一次能通再开流式。OAuth 报错多见于 Claude Code 这类需要登录态的工具。如果你用的是 API Key 模式就不该走 OAuth 流程检查配置里是不是同时存在两套认证方式导致冲突。正确做法是只保留 API Key 认证把 OAuth 相关的 token 字段清掉。Claude Code 的接入配置在文档里有完整示例https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你用的是 CC Switch、Cline MCP 或 Codex 的auth.json记住三件套必须写全Base URL、Key、Model ID。少任何一个都会报错。auth.json里字段名要和工具要求一致比如有的工具用apiKey有的用api_key大小写敏感。自删除代码本身的报错则集中在ShellExecuteEx返回 FALSE多半是COMSPEC没取到或参数拼接错误、DeleteFile返回失败文件还被占用说明主进程没退干净、克隆版里_ttoi转换进程句柄溢出32 位下句柄是 32 位64 位下要小心截断。排查时用GetLastError()打印错误码比猜快得多。6. 把自删除接进你的工具链从验证到长期使用跑通自删除之后下一步是把它用起来。如果你只是偶尔写个小工具ShellExecuteEx那套就够了代码短、依赖少。如果你要做的是需要长期运行、频繁自清理的 Agent 或编码助手建议把模型接入和自删除逻辑分开管理避免耦合。长期编码和 Agent 场景更适合用 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它的调用配额和稳定性更适合高频任务。而临时验证模型是否可用还是用模型对话页面最快https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。Key 的管理别忘了定期轮换控制台入口https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。接入细节随时查文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后分享一个实用技巧调试自删除时先把del命令换成copy或者写日志到文件确认命令行参数拼接正确、进程优先级调整生效再换成真正的删除。这样能避免“程序一跑就没了、连日志都来不及看”的尴尬。等你确认时序没问题再放开删除一次成功。
返回列表