1. Windows.h 原生开发里那些绕不开的窗口函数如果你在用 C/C 写 Windows 原生程序#include Windows.h几乎是每个项目的第一行。这个头文件里藏着大量 Win32 API其中ShellExecuteA、FindWindowA、GetCursorPos、SetWindowPos这四个函数是桌面自动化、窗口管理、启动器类工具里出现频率最高的组合。ShellExecuteA负责启动外部程序或打开文件FindWindowA按窗口类名和标题定位目标窗口GetCursorPos拿到鼠标当前位置SetWindowPos则用来移动、缩放、调整窗口的 Z 序。它们能做什么简单说你可以写一个工具按快捷键启动某个程序找到它的窗口把窗口挪到鼠标附近再调整成指定大小。适合谁适合正在学 Win32 编程的初学者、需要做桌面小工具的开发者以及想把 AI 能力接进原生 Windows 程序的工程师。问题在于这类开发调试往往卡在环境配置和 API 调用验证上编译能过运行没反应窗口句柄拿到了移动却失败想接大模型做智能窗口管理又不知道 Key 和请求链路怎么配。这篇就围绕这套函数组合把开发环境配置、可复制的配置骨架、请求验证动作和常见报错一次讲清楚让你能跟着做出来。2. 用 TaoToken 统一 Key 打通 API 调用链路在 Windows 原生开发里接大模型最烦的是每个模型厂商一套 Key、一套域名、一套鉴权格式。TaoToken 的思路是提供一个统一的 API 通道你只需要一个 Key就能通过兼容接口调用不同模型。对 Win32 开发者来说这意味着你可以在 C 里用WinHTTP或libcurl发一个标准请求把窗口信息、鼠标坐标作为上下文传进去让模型返回窗口该放哪、该开什么程序。配置入口很直接先到官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 了解整体能力然后在控制台创建 Key。API 基址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为请求前缀使用。如果你只是先验证模型对话是否通可以用模型对话页面快速试一条请求如果打算长期在编码和 Agent 场景里用Coding Plan 更适合Key 的创建和管理在 API Keys 页面完成接入细节看接入文档。这里要强调一点TaoToken 是正规的 API 聚合通道不是所谓的中转代理你拿到的 Key 就是标准鉴权凭证请求格式和主流兼容接口一致。对 Win32 项目来说你不需要改动太多代码结构只要把请求地址和鉴权头替换掉即可。3. 可复制的配置骨架与函数调用示例3.1 settings.json 与 config.toml 骨架很多现代编辑器比如 VS Code、Cursor、部分 CLI 工具用 JSON 或 TOML 存配置。下面这份settings.json骨架可以直接放进你的项目或编辑器配置目录把YOUR_API_KEY换成你在 API Keys 页面拿到的真实 Key{ taotoken: { api_base: https://taotoken.net/api, api_key: YOUR_API_KEY, default_model: claude-sonnet, timeout_ms: 30000, max_retries: 2 }, win32_tools: { target_window_class: Notepad, target_window_title: 无标题 - 记事本, move_offset_x: -500, move_offset_y: 0, window_width: 300, window_height: 400 } }如果你用的是支持 TOML 的工具链等价骨架如下[taotoken] api_base https://taotoken.net/api api_key YOUR_API_KEY default_model claude-sonnet timeout_ms 30000 max_retries 2 [win32_tools] target_window_class Notepad target_window_title 无标题 - 记事本 move_offset_x -500 move_offset_y 0 window_width 300 window_height 400这两份配置的作用是把 API 通道参数和窗口操作参数分离方便你在代码里读取。实际项目里可以用nlohmann/json解析 JSON或用toml解析 TOML。3.2 ShellExecuteA 启动程序ShellExecuteA的原型里第一个参数是父窗口句柄通常传0或NULL第二个是操作类型open表示打开第三个是路径后面三个参数分别是参数、工作目录和显示方式。一个最小可运行示例#include Windows.h int main() { HINSTANCE result ShellExecuteA( 0, open, notepad.exe, 0, 0, 1 ); if ((INT_PTR)result 32) { MessageBoxA(0, 启动失败, 错误, MB_OK); return 1; } return 0; }编译命令用 MSVC 开发者命令行cl /EHsc launch.cpp user32.lib shell32.lib运行后记事本会被打开。注意ShellExecuteA返回值小于等于 32 都表示失败这是很多人第一次写会忽略的点。3.3 FindWindowA 定位窗口窗口启动后用FindWindowA按类名和标题找句柄。记事本的类名是Notepad标题会随文件名变化所以更稳的做法是只传类名、标题传NULLHWND win FindWindowA(Notepad, NULL); if (win NULL) { MessageBoxA(0, 没找到记事本窗口, 提示, MB_OK); return 1; }如果你要精确匹配标题就传完整标题字符串。实测下来标题里带空格或中文时确保源码文件保存为 UTF-8 并在编译时加/utf-8否则FindWindowA会匹配失败。3.4 GetCursorPos 与 SetWindowPos 联动拿到鼠标位置后把窗口移动到鼠标左侧 500 像素处并设置成 300x400POINT xy; xy.x 0; xy.y 0; GetCursorPos(xy); SetWindowPos( win, NULL, xy.x - 500, xy.y, 300, 400, 0 );SetWindowPos的第二个参数是 Z 序插入位置传NULL表示不改变 Z 序最后一个参数是标志位传0表示按给定坐标和尺寸调整。如果你想让窗口置顶可以把标志改成SWP_SHOWWINDOW或加上HWND_TOPMOST作为第二个参数。3.5 把 API 请求接进窗口逻辑下面这段用WinHTTP发一条请求到 TaoToken 的 API 基址把当前鼠标坐标作为上下文发给模型让模型返回一个建议的窗口位置。代码骨架如下#include Windows.h #include winhttp.h #include string #include iostream #pragma comment(lib, winhttp.lib) std::wstring SendToTaoToken(const std::wstring jsonBody) { HINTERNET session WinHttpOpen( LWin32Client/1.0, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 ); HINTERNET connect WinHttpConnect( session, Ltaotoken.net, INTERNET_DEFAULT_HTTPS_PORT, 0 ); HINTERNET request WinHttpOpenRequest( connect, LPOST, L/api/v1/chat/completions, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE ); std::wstring headers LContent-Type: application/json\r\nAuthorization: Bearer YOUR_API_KEY\r\n; WinHttpSendRequest( request, headers.c_str(), -1L, (LPVOID)jsonBody.c_str(), (DWORD)(jsonBody.size() * sizeof(wchar_t)), (DWORD)(jsonBody.size() * sizeof(wchar_t)), 0 ); WinHttpReceiveResponse(request, NULL); std::string response; DWORD size 0; do { DWORD downloaded 0; WinHttpQueryDataAvailable(request, size); if (size 0) break; char* buffer new char[size 1]; ZeroMemory(buffer, size 1); WinHttpReadData(request, buffer, size, downloaded); response.append(buffer, downloaded); delete[] buffer; } while (size 0); WinHttpCloseHandle(request); WinHttpCloseHandle(connect); WinHttpCloseHandle(session); return std::wstring(response.begin(), response.end()); }请求体可以这样构造把鼠标坐标和窗口信息塞进去std::wstring body L{\model\:\claude-sonnet\,\messages\:[{\role\:\user\,\content\:\鼠标在(800,600)窗口宽300高400建议放哪\}]}; std::wstring result SendToTaoToken(body); std::wcout result std::endl;编译时记得链接winhttp.lib并且把YOUR_API_KEY换成真实 Key。这段代码跑通说明你的 Win32 程序和 TaoToken 的请求链路已经打通。4. 验证请求与成功结果配置写完后按下面顺序验证每一步都能看到明确结果。第一步单独编译并运行ShellExecuteA示例确认记事本能被打开。如果没反应先检查返回值是否小于等于 32。第二步在记事本已经打开的前提下运行FindWindowA示例确认能拿到非空句柄。你可以加一行std::cout win std::endl;打印句柄地址。第三步运行GetCursorPos和SetWindowPos联动代码把鼠标移到屏幕中间观察记事本窗口是否移动到鼠标左侧 500 像素、尺寸变成 300x400。这一步成功说明窗口操作链路完整。第四步用WinHTTP发请求到https://taotoken.net/api观察返回内容。如果返回 JSON 里带模型回复说明 Key 和通道都正常。你也可以先用模型对话页面手动发一条消息确认 Key 本身有效再回到代码里排查。一个典型的成功返回长这样{ id: chatcmpl-xxxx, object: chat.completion, choices: [ { index: 0, message: { role: assistant, content: 建议把窗口放在鼠标左侧避免遮挡当前操作区域。 } } ] }看到choices数组里有内容就说明整条链路通了。5. 本篇常见报错排查5.1 ShellExecuteA 返回 2 或 5返回值 2 通常是文件找不到检查路径是否正确、是否用了双反斜杠或正斜杠。返回值 5 是拒绝访问常见于目标程序需要管理员权限。解决办法是以管理员身份运行你的程序或者换一个不需要提权的目标。5.2 FindWindowA 返回 NULL最常见的原因是类名或标题写错。记事本的类名是Notepad但不同 Windows 版本标题格式可能不同。建议先用 Spy 或EnumWindows遍历确认。另一个原因是目标窗口还没创建完成ShellExecuteA是异步的启动后要加Sleep(500)再查找。5.3 SetWindowPos 移动无效如果窗口句柄有效但移动没反应检查目标窗口是否被最小化。最小化状态下SetWindowPos可能不生效先调用ShowWindow(win, SW_RESTORE)恢复。另外某些程序会自己重设位置这种情况需要循环调用或改用MoveWindow。5.4 WinHTTP 请求返回 401401 表示鉴权失败。检查Authorization头里Bearer后面是否有空格Key 是否复制完整。注意 API 基址是https://taotoken.net/api不要多加斜杠或路径。如果还是 401去 API Keys 页面重新生成一个 Key 再试。5.5 编译报错 unresolved external symbol这是链接库缺失。ShellExecuteA需要shell32.libFindWindowA、GetCursorPos、SetWindowPos需要user32.libWinHTTP需要winhttp.lib。在编译命令里加上这些库或在代码里用#pragma comment(lib, xxx.lib)。5.6 中文标题匹配失败源码文件编码和编译器编码不一致会导致字符串比较失败。MSVC 下加/utf-8编译选项并确保文件保存为 UTF-8 无 BOM。如果还是不行改用FindWindowW配合宽字符。6. 继续把链路用起来走到这里你已经有了一个能启动程序、定位窗口、读取鼠标位置、移动窗口并且能通过 TaoToken 统一 Key 发请求的 Win32 骨架。接下来最实际的动作是去 API Keys 页面创建一个正式 Key把配置里的YOUR_API_KEY替换掉然后跑一遍第 4 节的验证流程。如果你更想先确认模型返回质量可以直接在模型对话页面手动发几条窗口管理相关的提示词看看返回是否符合预期。打算长期在编码和 Agent 场景里用这套通道的话Coding Plan 的额度方式更适合持续调用。接入过程中遇到鉴权或请求格式问题接入文档里有完整的请求示例和字段说明对照着改就行。
