
这次我们来看一个非常实用的 Windows 编程技巧如何在屏幕上任意位置绘制鼠标光标。这听起来像是一个简单的需求但对于需要模拟鼠标操作、创建演示视频、开发自动化测试脚本甚至是制作一些特殊视觉效果比如在 Kali Linux 这类无图形界面的远程桌面中“找回”光标的开发者来说这是一个核心且必须掌握的能力。这个项目的核心不是复杂的算法而是对 Windows GDI图形设备接口的精准运用。我们将绕过系统默认的光标绘制机制直接通过 GDI 在指定的屏幕坐标上“画”出一个光标。这意味着你可以完全控制光标的外观、位置和显示逻辑不受系统鼠标指针的束缚。对于需要批量生成带光标指示的截图、或者开发远程协助工具中光标同步功能的场景这个技术点非常关键。本文将带你从零开始用 C 和 Win32 API 实现一个在屏幕上随机位置绘制自定义鼠标光标的程序。我们会重点关注几个实际问题GDI 绘图的基本流程、如何获取和绘制光标资源、如何实现不干扰真实鼠标的“第二光标”、以及如何将这个功能封装成可调用的接口方便集成到你的自动化任务中。无论你是 Windows 客户端开发新手还是需要解决特定光标显示问题的老手这篇文章都能提供可直接运行的代码和清晰的实现思路。1. 核心能力速览在深入代码之前我们先快速了解这个方案能做什么、不能做什么以及它的资源门槛。能力项说明核心功能在 Windows 屏幕任意坐标包括当前鼠标位置之外绘制一个静态或动态的鼠标光标图像。技术基础Windows GDI (Graphics Device Interface) 和 Win32 API。不依赖 DirectX、OpenGL 等高级图形库。编程语言主要使用 C/C。原理可移植到 C#通过 P/Invoke或 Python通过 ctypes/win32api。系统要求Windows 操作系统。对 Windows 7/8/10/11 均适用。硬件门槛极低。纯 CPU 绘图不占用独立显存几乎不消耗 GPU 资源。性能影响单次绘制开销可忽略不计。高频刷新如模拟光标移动时需注意 GDI 绘图效率避免画面闪烁。输出目标桌面窗口最顶层Top-most覆盖在所有常规窗口之上类似于一个屏幕水印。与系统光标关系独立绘制不影响真实的系统鼠标指针。可以并存实现“双光标”效果。适合场景1. 软件操作演示录制高亮指示位置。2. 自动化测试脚本可视化操作路径。3. 远程协助/教育软件同步讲师光标。4. 特殊 UI 效果如游戏内辅助瞄准点。5. 解决某些远程桌面环境如无头系统、Kali Linux 远程桌面中光标不可见的问题。不适合场景1. 需要获取真实光标输入消息点击、移动的场景。2. 需要极高帧率、平滑动画的光标渲染应考虑 DirectX。3. 跨平台应用macOS/Linux 需使用各自原生 API。2. 适用场景与使用边界这个技术适合谁软件测试工程师在运行自动化测试时在录屏中清晰展示鼠标的点击和移动轨迹便于回溯和报告。教育/培训内容创作者制作软件教程视频时需要一个醒目、自定义的光标来引导观众视线。远程支持工具开发者在远程控制会话中除了传输屏幕还需要独立绘制一个指示光标提升体验。特定问题的解决者在某些虚拟机、远程桌面如通过 VNC 连接 Kali Linux或特殊显示配置下系统光标可能丢失或无法捕获此时自主绘制光标是有效的补救方案。它能解决什么问题可视化控制让不可见的自动化操作“可见”。自定义外观不受系统主题限制可以使用放大、高亮、彩色或动画光标。位置解耦绘制光标的位置可以与物理鼠标设备完全无关。录制兼容性确保屏幕录制软件能稳定捕获到光标避免某些录制工具无法捕获硬件光标的问题。使用边界与注意事项仅为视觉反馈绘制的光标只是一个“贴图”它不会产生真实的鼠标事件如WM_LBUTTONDOWN。交互逻辑仍需通过其他方式如SendInput实现。窗口层级限制虽然可以置顶但某些全屏独占应用如游戏或安全桌面UAC 提示可能会覆盖我们的绘制窗口。多显示器需要正确处理多显示器环境下的坐标转换。本文示例以主显示器为例。资源管理GDI 对象如位图、设备上下文必须及时释放否则会导致资源泄漏。合规使用用于演示、测试、辅助功能是正当的。请勿用于制作恶意软件、伪装系统提示进行钓鱼等非法用途。3. 环境准备与前置条件开始编码前请确保你的开发环境已就绪。1. 操作系统Windows 7 SP1 或更高版本推荐 Windows 10/11 以获得最佳开发体验。2. 开发工具与 SDK编译器支持 C11 或更高版本的编译器。我们使用 Visual Studio 2022 Community Edition免费进行演示。Windows SDK安装 Visual Studio 时请勾选“使用 C 的桌面开发”工作负载它会包含所需版本的 Windows SDK。项目类型我们将创建一个Windows 桌面应用程序项目而不是控制台应用。3. 项目基础配置项目字符集建议设置为“使用多字节字符集”或“Unicode 字符集”。本文示例使用 Unicode。需要链接user32.lib和gdi32.lib但 Visual Studio 的桌面应用模板默认已链接。4. 关键头文件我们的实现将主要依赖以下 Windows 头文件#include windows.h // 核心 Win32 API #include windowsx.h // 方便的消息处理宏可选 #include tchar.h // 用于 _T 宏处理 Unicode/ANSI 字符串5. 测试准备准备一个光标文件.cur或.ani备用或者我们直接使用系统内置光标。思考一个简单的触发机制例如按空格键在随机位置绘制光标。4. 核心原理与实现步骤我们将创建一个无边框、透明背景、始终置顶的窗口作为我们绘制光标的“画布”。然后在这个窗口的绘图事件中将光标图像绘制到指定坐标。4.1 创建透明置顶窗口这是所有 GDI 屏幕绘图的基础。窗口样式是关键。// 全局变量窗口句柄和光标句柄 HWND g_hwnd NULL; HCURSOR g_hCursor NULL; POINT g_cursorPos { 100, 100 }; // 初始绘制位置 // 窗口过程函数 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: // 窗口创建时加载一个光标资源。这里使用系统标准箭头光标。 g_hCursor LoadCursor(NULL, IDC_ARROW); // 你也可以从文件加载 // g_hCursor LoadCursorFromFile(_T(C:\\path\\to\\your.cur)); break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hwnd, ps); // 关键步骤在窗口的客户区绘制光标 // 我们假设光标热点在 (0,0)。复杂光标需要计算热点偏移。 DrawIcon(hdc, g_cursorPos.x, g_cursorPos.y, g_hCursor); EndPaint(hwnd, ps); } break; case WM_KEYDOWN: if (wParam VK_SPACE) // 按下空格键 { // 随机生成一个新位置 RECT screenRect; GetClientRect(GetDesktopWindow(), screenRect); // 获取屏幕尺寸 g_cursorPos.x rand() % (screenRect.right - 32); // 假设光标宽32像素 g_cursorPos.y rand() % (screenRect.bottom - 32); // 强制窗口重绘在新位置画出光标 InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } // 注册窗口类和创建窗口 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 1. 注册窗口类 WNDCLASSEX wc { 0 }; wc.cbSize sizeof(WNDCLASSEX); wc.style CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc WndProc; wc.hInstance hInstance; wc.hCursor LoadCursor(NULL, IDC_ARROW); // 窗口自己的光标不影响绘制 wc.hbrBackground (HBRUSH)GetStockObject(NULL_BRUSH); // 透明背景 wc.lpszClassName _T(CursorOverlayWindow); if (!RegisterClassEx(wc)) return -1; // 2. 获取屏幕尺寸创建覆盖全屏的窗口 int screenWidth GetSystemMetrics(SM_CXSCREEN); int screenHeight GetSystemMetrics(SM_CYSCREEN); // 3. 创建窗口 - 关键样式设置 g_hwnd CreateWindowEx( WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW, // 关键置顶、透明、分层、不显示在任务栏 _T(CursorOverlayWindow), _T(Cursor Overlay), WS_POPUP, // 无边框弹出窗口 0, 0, // 位置在左上角 screenWidth, screenHeight, // 覆盖整个屏幕 NULL, NULL, hInstance, NULL); if (!g_hwnd) return -1; // 4. 设置窗口分层属性实现透明 // 将窗口颜色键设置为黑色RGB(0,0,0)并将黑色部分设为完全透明 SetLayeredWindowAttributes(g_hwnd, RGB(0, 0, 0), 0, LWA_COLORKEY); // 5. 显示窗口 ShowWindow(g_hwnd, nCmdShow); UpdateWindow(g_hwnd); // 6. 消息循环 MSG msg; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return (int)msg.wParam; }代码关键点解析WS_EX_TOPMOST确保窗口在所有常规窗口之上。WS_EX_TRANSPARENT使窗口对鼠标点击“穿透”不会干扰你操作其他软件。WS_EX_LAYERED配合SetLayeredWindowAttributes实现颜色键透明。我们将窗口背景设为黑色并指定黑色透明这样只有我们绘制的光标是可见的。WS_POPUP和WS_EX_TOOLWINDOW创建一个无边框、不在任务栏显示的窗口。DrawIconGDI 函数用于在指定的设备上下文HDC和坐标绘制光标或图标。InvalidateRect和UpdateWindow当光标位置改变后调用它们触发WM_PAINT消息进行重绘。编译并运行此程序你会看到一个全屏的透明窗口。按下空格键一个标准的白色箭头光标就会在屏幕的随机位置出现。4.2 绘制自定义光标与处理热点上面的例子使用了系统光标。如果你想使用自定义光标文件或者需要更精细的控制如绘制彩色位图光标可以使用DrawIconEx或BitBlt函数。使用DrawIconEx绘制光标支持热点case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hwnd, ps); // 假设 g_hCursor 是已加载的自定义光标 if (g_hCursor) { ICONINFO iconInfo; if (GetIconInfo(g_hCursor, iconInfo)) { // iconInfo.xHotspot 和 iconInfo.yHotspot 是光标的热点坐标相对于图像左上角 // 绘制时需要从目标坐标减去热点偏移才能使热点对准指定位置。 DrawIconEx(hdc, g_cursorPos.x - iconInfo.xHotspot, g_cursorPos.y - iconInfo.yHotspot, g_hCursor, 0, 0, // 宽度和高度为0表示使用原始尺寸 0, NULL, DI_NORMAL); // 清理 GDI 对象 DeleteObject(iconInfo.hbmMask); DeleteObject(iconInfo.hbmColor); } else { // 获取图标信息失败回退到简单绘制 DrawIcon(hdc, g_cursorPos.x, g_cursorPos.y, g_hCursor); } } EndPaint(hwnd, ps); } break;从文件加载自定义光标在WM_CREATE或初始化函数中g_hCursor (HCURSOR)LoadImage( NULL, // 实例句柄NULL 表示从文件或系统资源加载 _T(C:\\MyCursors\\pointer.cur), // 光标文件路径 IMAGE_CURSOR, // 类型为光标 0, 0, // 宽度和高度为0表示使用原始尺寸 LR_LOADFROMFILE | LR_DEFAULTSIZE // 从文件加载使用默认大小 ); if (g_hCursor NULL) { // 加载失败使用系统默认光标作为后备 g_hCursor LoadCursor(NULL, IDC_ARROW); MessageBox(hwnd, _T(Failed to load custom cursor. Using default.), _T(Info), MB_OK); }5. 功能测试与效果验证现在我们构建一个更完整的测试程序来验证核心功能。5.1 测试一基础绘制与随机位置更新目的验证程序能创建透明窗口并在接收到指令如按键时在随机位置绘制光标。操作编译并运行 4.1 节的完整程序。观察屏幕应该看不到任何窗口因为是透明的。多次按下键盘上的空格键。观察一个白色箭头光标是否在屏幕各处随机出现。成功标准每次按键光标立即出现在一个新的、随机的屏幕位置旧位置的光标消失。常见问题光标不出现检查WM_PAINT中的DrawIcon调用是否执行。可以在其前后加OutputDebugString调试。窗口有黑色背景检查WNDCLASSEX的hbrBackground是否设置为(HBRUSH)GetStockObject(NULL_BRUSH)以及SetLayeredWindowAttributes是否被正确调用。按键无反应确认消息循环在运行并且WM_KEYDOWN分支被触发。5.2 测试二光标热点对齐目的验证绘制的光标其“热点”如箭头的尖端是否精确对准我们指定的坐标。操作修改代码将光标的初始位置g_cursorPos设置为屏幕中心(screenWidth/2, screenHeight/2)。使用DrawIconEx并传入热点坐标进行绘制如 4.2 节代码。运行程序观察光标是否以热点为中心点出现在屏幕正中央。可以同时打开一个画图软件将真实鼠标移动到屏幕中心对比两个光标的位置。成功标准绘制的光标热点与指定的坐标点对齐。例如箭头光标的尖端应该落在(screenWidth/2, screenHeight/2)这个像素上。排查方法如果对不齐打印或调试输出iconInfo.xHotspot和iconInfo.yHotspot的值检查计算g_cursorPos.x - iconInfo.xHotspot是否正确。5.3 测试三多光标与系统光标共存目的验证我们绘制的光标与真实的系统硬件光标互不干扰。操作运行程序使其绘制一个静态光标例如固定在(200,200)。自由移动你的物理鼠标。成功标准物理鼠标的系统光标可以自由移动并且可以穿过我们绘制的静态光标图像两者完全独立。我们的绘制操作不会引起系统光标卡顿或消失。扩展测试尝试在代码中同时绘制多个不同样式的光标在不同位置。5.4 测试四性能与闪烁测试目的验证高频更新光标位置时的性能表现和视觉流畅度。操作修改代码在WM_TIMER消息或一个独立线程中每隔 16 毫秒约 60 FPS更新一次g_cursorPos例如让光标沿圆形轨迹运动并调用InvalidateRect。运行程序观察光标动画是否平滑。打开任务管理器观察程序 CPU 占用率。成功标准光标移动基本平滑CPU 占用率在可接受范围内通常 5%。优化提示如果发现闪烁可以尝试双缓冲技术先在内存位图中绘制再一次性BitBlt到窗口。对于简单的光标移动DrawIconEx通常足够快。6. 封装为 API 与批量任务集成对于自动化测试或集成到其他项目我们需要一个更干净的接口而不是一个带消息循环的独立窗口程序。我们可以将核心功能封装到一个 DLL 或静态库中。6.1 设计简易 API创建一个头文件CursorOverlay.h// CursorOverlay.h #pragma once #ifdef CURSOROVERLAY_EXPORTS #define CURSOROVERLAY_API __declspec(dllexport) #else #define CURSOROVERLAY_API __declspec(dllimport) #endif #ifdef __cplusplus extern C { #endif // 初始化覆盖层窗口。成功返回非零句柄内部使用失败返回0。 CURSOROVERLAY_API int InitializeOverlay(); // 设置要绘制的光标。hCursor为光标句柄如果为NULL则使用默认箭头。 CURSOROVERLAY_API void SetOverlayCursor(HCURSOR hCursor); // 更新绘制光标的位置 (x, y) 为屏幕坐标。 CURSOROVERLAY_API void UpdateOverlayPosition(int x, int y); // 显示或隐藏覆盖层。 CURSOROVERLAY_API void ShowOverlay(BOOL bShow); // 清理资源销毁窗口。 CURSOROVERLAY_API void ShutdownOverlay(); #ifdef __cplusplus } #endif对应的实现文件CursorOverlay.cpp将包含之前提到的窗口创建、消息循环运行在独立线程和绘图逻辑。UpdateOverlayPosition函数内部会更新全局坐标并触发重绘。6.2 批量任务调用示例假设你有一个自动化测试脚本记录了一系列鼠标坐标(x1, y1), (x2, y2), ...。你可以这样集成// TestScript.cpp - 模拟测试脚本 #include CursorOverlay.h #include windows.h #include vector int main() { // 1. 初始化光标覆盖层 if (!InitializeOverlay()) { printf(Failed to initialize overlay.\n); return -1; } // 2. 可选加载一个自定义光标 HCURSOR hCustomCursor LoadCursorFromFile(_T(highlight.cur)); if (hCustomCursor) { SetOverlayCursor(hCustomCursor); } // 3. 模拟测试路径 std::vectorPOINT testPoints { {100, 100}, {300, 150}, {500, 500}, {800, 300} }; for (const auto pt : testPoints) { // 更新覆盖层光标位置 UpdateOverlayPosition(pt.x, pt.y); // 模拟在此点执行操作如点击 // SetCursorPos(pt.x, pt.y); // 移动真实鼠标可选 // mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); // 模拟点击 // 停留一段时间便于观察或录制 Sleep(1000); // 暂停1秒 } // 4. 测试结束清理 ShutdownOverlay(); return 0; }编译与运行将CursorOverlay编译为 DLL测试脚本链接此 DLL。运行测试脚本你将看到自定义光标沿着预设路径移动清晰地指示出自动化测试的“视线焦点”。7. 资源占用与性能观察GDI 绘图本身非常轻量但实现方式会影响性能。1. 资源占用观察CPU在空闲状态下光标静止消息循环处于等待状态CPU 占用接近 0%。当高频刷新如 60 FPS 动画时CPU 占用会上升取决于WM_PAINT中绘图操作的复杂度。对于简单的DrawIconEx即使在 60 FPS 下现代 CPU 的占用率也通常低于 2%。内存一个基本的 Win32 窗口程序内存占用很小通常在几 MB 到十几 MB。GDI 对象务必注意 GDI 对象泄漏。每次调用GetIconInfo都会返回两个位图句柄hbmMask和hbmColor必须用DeleteObject释放。可以使用GDIView等工具检查进程的 GDI 对象数是否稳定。2. 性能优化建议避免无效重绘只在光标位置改变时调用InvalidateRect。不要在每个消息循环中都重绘。使用双缓冲如果绘制内容复杂例如光标带阴影、特效在内存 DC 中完成所有绘制再一次性传输到屏幕 DC可以显著减少闪烁。限制刷新率对于指示用途不需要极高的帧率。使用SetTimer控制刷新间隔例如 30 FPS33ms足够流畅。精简窗口区域如果不需要全屏覆盖可以创建一个小窗口只在不透明区域绘制。这能减少 GDI 需要处理的像素数量。3. 多显示器坐标处理如果你的系统有多个显示器GetSystemMetrics(SM_CXSCREEN)获取的是主显示器的尺寸。要获取虚拟屏幕的总范围使用int virtualWidth GetSystemMetrics(SM_CXVIRTUALSCREEN); int virtualHeight GetSystemMetrics(SM_CYVIRTUALSCREEN); int virtualLeft GetSystemMetrics(SM_XVIRTUALSCREEN); int virtualTop GetSystemMetrics(SM_YVIRTUALSCREEN);创建窗口时位置和尺寸应使用这些虚拟屏幕值。绘制光标时传入的坐标也应是相对于虚拟屏幕左上角的坐标。8. 常见问题与排查方法在实现和使用过程中你可能会遇到以下问题问题现象可能原因排查方式解决方案窗口完全看不见也没有光标1. 窗口创建失败。2. 窗口样式导致被其他全屏窗口覆盖。3. 透明色设置错误整个窗口都透明了。1. 检查CreateWindowEx返回值是否为NULL。2. 临时移除WS_EX_TOPMOST或WS_EX_TRANSPARENT看窗口是否出现。3. 将SetLayeredWindowAttributes的透明度参数改为 255不透明测试。1. 获取GetLastError()查看错误码。2. 确保窗口创建后调用了ShowWindow和UpdateWindow。3. 确认透明色键RGB(0,0,0)与窗口背景画刷颜色一致。光标绘制不出来1.WM_PAINT消息未处理或DrawIcon未执行。2. 光标句柄g_hCursor为NULL。3. 绘制坐标在窗口客户区外。1. 在WM_PAINT开始处加调试输出。2. 检查LoadCursor或LoadImage的返回值。3. 绘制前用GetClientRect获取窗口大小确保坐标在范围内。1. 确保消息循环正常运行。2. 使用系统内置光标IDC_ARROW进行测试排除文件加载问题。3. 将初始坐标设为(0,0)进行测试。光标有黑色方块背景光标资源本身带有非透明背景或者DrawIcon不支持某些光标格式的透明度。尝试使用DrawIconEx并指定DI_NORMAL标志。检查光标文件是否为标准的 32 位带 Alpha 通道的.cur或.ico文件。1. 使用DrawIconEx。2. 使用图像处理软件重新制作光标确保背景透明。3. 对于复杂图像考虑使用AlphaBlend函数进行绘制。程序运行时无法点击背后的窗口WS_EX_TRANSPARENT扩展样式未生效或设置不正确。检查CreateWindowEx的参数是否包含了WS_EX_TRANSPARENT。确保创建窗口时设置了WS_EX_TRANSPARENT样式。注意分层窗口 (WS_EX_LAYERED) 的穿透行为可能更复杂WS_EX_TRANSPARENT是关键。高频更新时光标闪烁严重GDI 直接绘制到屏幕导致擦除-重绘间隙。观察WM_PAINT中是否先有擦除背景的操作由BeginPaint触发。1. 在WM_PAINT中使用双缓冲技术。2. 在注册窗口类时设置wc.hbrBackground NULL_BRUSH并处理WM_ERASEBKGND消息直接返回1禁止擦除背景。在多显示器上光标位置错乱使用了主显示器坐标未考虑多显示器虚拟桌面坐标系。打印出传入的坐标值和通过GetSystemMetrics获取的屏幕范围进行对比。统一使用虚拟屏幕坐标系SM_XVIRTUALSCREEN,SM_YVIRTUALSCREEN,SM_CXVIRTUALSCREEN,SM_CYVIRTUALSCREEN进行计算和绘制。编译时链接错误未正确链接user32.lib和gdi32.lib。查看错误信息是否关于DrawIcon,CreateWindowEx等函数未定义。在 Visual Studio 项目属性 - 链接器 - 输入 - 附加依赖项中添加user32.lib;gdi32.lib。9. 最佳实践与使用建议从简单开始首先使用系统内置光标 (IDC_ARROW) 和固定坐标实现基础绘制功能。确保窗口创建、透明、绘图流程正确后再引入自定义光标、动态坐标和热点计算。分离逻辑与显示将光标位置更新逻辑如跟随算法、路径规划与 GDI 绘制逻辑解耦。通过线程安全的队列或消息来传递位置信息。做好错误处理所有 Win32 API 调用如LoadImage,CreateWindowEx,DrawIconEx都应检查返回值。使用GetLastError()获取详细错误信息并记录日志。管理 GDI 对象生命周期遵循“谁创建谁删除”的原则。对于通过GetIconInfo,CreateCompatibleBitmap等函数获得的 GDI 对象句柄在使用完毕后务必调用DeleteObject。考虑封装与复用如第 6 节所示将功能封装成清晰的 APIDLL/静态库便于集成到不同的测试框架、演示工具或辅助软件中。性能与效果平衡对于演示视频30 FPS 的刷新率足够清晰流畅。对于需要精确指示高速操作的场景可以提高到 60 FPS。务必在实际硬件上测试性能。解决 Kali Linux 等远程桌面无光标问题这个技术的典型应用场景。在远程桌面客户端如 VNC Viewer连接的 Kali Linux 中如果服务器端光标未正确传输你可以在本地观看端运行此程序根据接收到的服务器端光标坐标在本地屏幕上同步绘制一个光标图像从而“弥补”缺失的视觉反馈。这需要你建立一套坐标传输机制例如通过 VNC 协议扩展或自定义网络通信。掌握在屏幕上自主绘制光标的能力为你打开了一扇窗让你能更灵活地控制软件的视觉反馈。无论是为了提升自动化测试的可观测性制作更专业的教学视频还是解决特定环境下的显示问题这项基于 Windows GDI 的技术都提供了可靠且高效的解决方案。建议从本文提供的完整示例代码开始将其集成到你的项目中并根据实际需求调整光标的样式、行为和控制逻辑。