简介:这是一份基于Visual C++开发的网吧锁屏程序,目标是与万象网管软件形成同等的锁屏管控效果,适合网吧管理员、网维技术人员以及学习Windows桌面应用开发的初学者参考。程序聚焦锁屏策略、禁止操作与安全解锁等核心流程,可用于休息时段或维护期间的机器接管,也可作为自定义网吧管理工具的基础原型。压缩包共15个文件,体积仅12KB,以.cpp、.h等源码文件为主,辅以.rc界面资源、工程配置与图标文件,结构精简,便于直接阅读和二次编译。内容涵盖对话框界面、锁屏逻辑、资源脚本等要点,适合快速分析锁屏实现路径。该资源已有506人学习下载,具备一定的参考热度。通过源码可了解全屏置顶、输入密码解锁、系统消息屏蔽等常见锁屏机制的写法,也能借鉴其工程组织方式,为后续扩展计费联动或远程控制功能打下基础。
1. 一份能复刻「万象锁屏」效果的 VC 源码包:它到底能帮你解决什么
网吧老板想把万象网管那套「空闲锁屏 + 输密码解锁」的界面搬到自己的机器上,又不想继续为客户端授权费买单;做网维的想研究锁屏逻辑,手头却只有一份「vc网吧锁屏_和万象网管软件效果一样.rar」这种资源包。这份资源的核心价值很简单:用 C/C++ 实现一套和万象网管视觉、交互接近的全屏锁屏程序,包含置顶窗口、键盘拦截、密码校验、开机自启等模块。它适合两类人:一是能编译 VC 项目、想快速部署一套自用锁屏的从业者,二是想拆解锁屏实现原理、学习 Win32 API 和键盘钩子的开发者。如果你完全看不懂 C 代码,这份资源对你来说只是「能运行的黑匣子」,后面的章节会告诉你黑匣子里每一层是怎么工作的。
2. 万象式锁屏的原理:界面、拦截、校验三层各司其职
2.1 先从用户体验拆解:万象锁屏到底做了什么
用万象网管的老机器,锁屏后的界面有固定套路:全屏深色背景,中间一个输入框,下面写着「请输入卡号和密码」,顶部可能带网吧名称或 logo。你按任何键,焦点始终停留在那个输入框里;Ctrl+Alt+Del、Alt+Tab、Win 键全部失效;鼠标移动但图标被隐藏,点任何位置都没有反应。输入正确的卡号和密码后回车,界面消失,桌面恢复。
这套体验拆成技术点,对应关系如下:
| 体验表现 | 底层实现 |
|---|---|
| 全屏不可关闭的窗口 | 置顶窗口 + 无边框 + 隐藏鼠标 |
| 按键全部进输入框 | 低级键盘钩子拦截系统热键 |
| 输入密码后解锁 | 对话框消息循环 + 密码比对 |
| 开机自动进入锁屏 | 注册表 Run 键或服务方式启动 |
很多人拿到这类源码包第一反应是「直接编译运行」,结果锁屏窗口弹出来一按 Win 键就跑了,这就是没理解第一层和第三层的配合。锁屏不是画个全屏窗口那么简单,拦截层没做好,任何锁屏都是摆设。
2.2 客户端锁屏的三层结构:界面、拦截、校验
锁屏程序在应用层其实就是三层:界面层负责「遮住桌面」,拦截层负责「阻止用户绕过界面」,校验层负责「判断该不该放行」。三者是独立模块,可以分别替换。
界面层用 Win32 API 创建一个顶级窗口,关键参数是WS_EX_TOPMOST | WS_EX_TOOLWINDOW。WS_EX_TOPMOST保证窗口始终在 Z 序最前面,WS_EX_TOOLWINDOW让窗口不出现在任务栏和 Alt+Tab 切换列表里。窗口大小直接取GetSystemMetrics(SM_CXSCREEN)和SM_CYSCREEN,覆盖整个主屏。这一步是基础,但不构成锁屏。
拦截层的关键是键盘钩子。万象的做法是从内核层拦截,普通应用做不到,所以这份资源通常会退而求其次,用SetWindowsHookEx挂WH_KEYBOARD_LL低级键盘钩子,在回调里屏蔽 Win、Alt+Tab、Alt+F4 这类组合键。低级键盘钩子的优势是不需要注入到别的进程,直接在全局生效,缺点是它跑在调用线程的消息循环上,如果锁屏窗口的主循环卡死,钩子也跟着失效。
校验层就是密码框的逻辑。用户输入的内容取出来,和预置密码比对,匹配就销毁锁屏窗口并恢复任务栏。需要注意的是密码比对不能写在窗口过程里等着WM_COMMAND触发,否则会出现焦点丢失、回车不响应的问题。正确做法是把编辑框的WS_TABSTOP置位,让 Tab 键时焦点固定,然后拦截IDOK按钮或者用IsDialogMessage处理回车。
2.3 万象能做到而普通应用做不到的部分:驱动级拦截的边界
这里必须说清楚一道坎:万象网管对 Ctrl+Alt+Del 的处理,实质上是在驱动层挂了键盘过滤设备对象,或者配合系统 SAS 窗口的替代方案,这不是应用层 API 能做到的。WH_KEYBOARD_LL钩子接不到 Ctrl+Alt+Del 的按键消息——Windows 在 winlogon 阶段就把这个组合键留给系统了。
所以,如果你拿这份资源在 Win10 上测试,按 Ctrl+Alt+Del 依然会弹出系统安全桌面,这属于正常边界,不是源码写错了。实战里常见的补救手法是:检测到安全桌面弹出后,用SetWinEventHook监听窗口事件,找到那个安全桌面窗口后尝试关闭或把它置底。这条路在 Win7 上能用,在 Win10/11 上经常失效。如果你要部署在营业场所,建议在说明文件里标注「仅拦截常规热键,不保证拦截 Ctrl+Alt+Del」,避免上线后翻车。
3. 核心代码拆解:窗口创建、键盘钩子、密码校验的实现骨架
3.1 全屏置顶锁屏窗口:CreateWindowEx 的参数怎么设
这一段依赖纯 Win32,建议用 C 语言写入口,避免 MFC 的运行时依赖。资源包里如果带的是.dsp工程,多半是 VC6 时代创建的;如果你用 VS2017 以上版本打开,转换向导会把它升级成新的工程格式,但源码逻辑基本不变。
先看窗口创建的核心代码:
HWND hTray = ::FindWindow(_T("Shell_TrayWnd"), NULL); if (hTray) ::ShowWindow(hTray, SW_HIDE); // 隐藏任务栏 WNDCLASS wc = {0}; wc.lpfnWndProc = LockScreenWndProc; wc.hInstance = hInst; wc.hCursor = NULL; // 不加载鼠标光标 wc.lpszClassName = _T("LockScreenClass"); RegisterClass(&wc); HWND hLockWnd = CreateWindowEx( WS_EX_TOPMOST | WS_EX_TOOLWINDOW, // 置顶且不出现在任务栏 _T("LockScreenClass"), _T("Lock"), WS_POPUP, // 无边框无标题栏 0, 0, GetSystemMetrics(SM_CXSCREEN), // 主屏宽度 GetSystemMetrics(SM_CYSCREEN), // 主屏高度 NULL, NULL, hInst, NULL ); ::ShowWindow(hLockWnd, SW_SHOW); SetForegroundWindow(hLockWnd);FindWindow找到系统任务栏句柄后先隐藏,防止用户在锁屏间隙点任务栏。hCursor = NULL导致窗口内鼠标光标不可见,这也是万象锁屏的视觉特征之一。WS_POPUP不能省——如果你用了WS_OVERLAPPEDWINDOW,窗口会带边框和标题栏,用户可能通过窗口控制菜单把它最小化。
创建完成后必须调用SetForegroundWindow,因为CreateWindowEx创建的窗口不一定是前台窗口,不抢前台的话键盘输入会落到下面的桌面进程上,用户直接就能打字,锁屏形同虚设。
3.2 低级键盘钩子:屏蔽 Win 键和 Alt+Tab 的完整回调
键盘钩子的安装代码通常放在WinMain里,和窗口消息循环在同一线程。注意WH_KEYBOARD_LL钩子的回调是在安装钩子的线程里执行的,所以这个线程必须持续运行消息循环,否则钩子不生效。
HHOOK hKeyHook = SetWindowsHookEx( WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0 ); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } UnhookWindowsHookEx(hKeyHook);关键在回调函数本身:
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { KBDLLHOOKSTRUCT *pkb = (KBDLLHOOKSTRUCT*)lParam; DWORD vk = pkb->vkCode; // 屏蔽 Win 键、Alt+Tab、Alt+F4、Ctrl+Esc if (vk == VK_LWIN || vk == VK_RWIN) return 1; if (vk == VK_TAB && (GetAsyncKeyState(VK_MENU) & 0x8000)) return 1; if (vk == VK_F4 && (GetAsyncKeyState(VK_MENU) & 0x8000)) return 1; if (vk == VK_ESCAPE && (GetAsyncKeyState(VK_CONTROL) & 0x8000)) return 1; } return CallNextHookEx(NULL, nCode, wParam, lParam); }返回值1表示该按键消息被吞掉,不再向下传递。GetAsyncKeyState(VK_MENU)用来检测 Alt 键是否处于按下状态,因为KBDLLHOOKSTRUCT里一次只包含一个键的信息,组合键需要单独查状态。CallNextHookEx是必须的,不调用的话其他挂钩子的程序会受影响。
这套逻辑能挡住 90% 的常见热键,但挡不住 Ctrl+Alt+Del,原因前面说了。
3.3 密码校验与解锁流程:Edit 控件的焦点陷阱
锁屏窗口上放一个EDIT控件,密码输入框设置为ES_PASSWORD样式。解锁逻辑按「回车触发 → 取文本 → 比对 → 销毁窗口」的顺序走,代码结构如下:
case WM_COMMAND: if (LOWORD(wParam) == ID_BTN_UNLOCK || LOWORD(wParam) == IDOK) { TCHAR szPwd[64] = {0}; GetDlgItemText(hLockWnd, IDC_EDIT_PWD, szPwd, 64); if (_tcscmp(szPwd, _T("888888")) == 0) { // 默认密码 HWND hTray = ::FindWindow(_T("Shell_TrayWnd"), NULL); if (hTray) ::ShowWindow(hTray, SW_SHOW); // 恢复任务栏 DestroyWindow(hLockWnd); PostQuitMessage(0); } else { SetDlgItemText(hLockWnd, IDC_EDIT_PWD, _T("")); // 可在这里闪烁窗口提示密码错误 } } break;IDOK的处理很关键。对话框里按下回车时,默认会触发WM_COMMAND且LOWORD(wParam) == IDOK,但如果你的窗口不是对话框而是普通CreateWindow创建的子窗口,回车键不会自动映射到IDOK。这时要在窗口过程中手动拦截WM_KEYDOWN,检测VK_RETURN,再走同一段校验逻辑。常见翻车点就在这里:编译出来的锁屏输入密码后按回车没反应,光标在编辑框里换行了,就是因为没有处理WM_KEYDOWN的VK_RETURN。
4. 编译与部署:从 cl.exe 报错到开机自启的一条完整路径
4.1 编译环境选择和 cl.exe 报错的根源
老网吧环境里的机器五花八门,有的还是 Win7 + VC6 时代的残留,有的已经升到 Win10 + VS2015+。这份资源如果标注是 VC6 工程,你先不要急着用 VS2019 直接打开点编译,大概率会撞上error: command ‘...VC for Python...cl.exe’ failed with exit status 2这类报错——其实就是编译器工具链没配对。
我的做法是:先在项目属性里把「平台工具集」选对。用 VS2015/2017/2019 打开 VC6 旧工程时,默认会做一次升级,但这不代表配置没坑。你需要手动检查三处:
| 配置项 | 推荐值 | 原因 |
|---|---|---|
| 字符集 | 使用多字节字符集 | 老代码大量char*和TCHAR混用,Unicode 下报 C2664 |
| MFC 使用 | 在静态库中使用 MFC | 目标机器可能没有 MFC DLL |
| 运行库 | 多线程 (/MT) | 避免依赖 msvcp140.dll、vcruntime140.dll |
编译报错集中在两类:一是字符集问题,_T("")和char[]混用导致类型不匹配;二是运行库缺失,程序在本机能跑,拷到别的机器提示缺少VCRUNTIME140.dll。静态链接/MT是唯一干净的解决方案,代价是 exe 体积大 200KB 左右,但对锁屏这种常驻程序无所谓。
4.2 注册表自启动的两种写法与权限差异
锁屏程序必须开机自启,最常见的做法是写HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run。HKLM下的 Run 键对所有用户生效,需要管理员权限写入;HKCU下的 Run 键只对当前用户生效,不需要提权。考虑到锁屏要拦住所有账户的访问,建议优先写HKLM。
HKEY hKey = NULL; RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_SET_VALUE, &hKey); TCHAR szPath[MAX_PATH] = {0}; GetModuleFileName(NULL, szPath, MAX_PATH); RegSetValueEx(hKey, _T("NetBarLock"), 0, REG_SZ, (BYTE*)szPath, (_tcslen(szPath) + 1) * sizeof(TCHAR)); RegCloseKey(hKey);GetModuleFileName拿的是当前 exe 的完整路径,这样即使程序放在非系统盘也能启动。RegSetValueEx的第五个参数是数据缓冲区,第六个参数必须包含结尾的\0,漏掉的话注册表值虽然能写入,但读取时可能带尾巴字符,启动时会报路径不存在。这是注册表写入的经典低级坑。
部署时还要注意杀毒软件。锁屏程序因为涉及全局键盘钩子和隐藏任务栏,很多安全软件会把它当风险程序处理。实战中要提前在部署机上加入白名单,或者用SetWindowsHookEx改为调用系统提供的旧版钩子(WH_KEYBOARD而非WH_KEYBOARD_LL)来降低查杀概率,但老式钩子需要 DLL 注入,锁定程度反而更差,自己权衡。
4.3 vc 运行库依赖:为什么目标机上还要装运行库
静态链接/MT能解决一部分运行库问题,但如果你在 VS2015+ 下编译且代码里用了 STL、std::string之类的东西,链接时仍可能带出vcruntime140_1.dll等新版本运行库。网吧客户机系统版本杂,Win7 SP1 之前的老系统甚至不支持 VC2015 运行库。
稳妥做法是把vc_redist.x86.exe一并放进资源包的部署目录,安装锁屏前先跑一遍。这个 exe 是微软官方发行包,不需要自己写安装逻辑。在部分机器上如果出现dota2正在运行安装脚本 microsoft vc那种报错,通常就是游戏平台在触发同一个运行库安装流程,和锁屏部署不冲突,两个程序用同一个运行库实例,不会互相干扰。
4.4 部署验证的四个检查项
部署完成后,重启机器做四步验证:第一,锁屏窗口是否在桌面出现前就弹出来;第二,Win 键和 Alt+Tab 是否失效;第三,输入错误密码是否有提示;第四,重启后用另一个管理员账户能否绕过锁屏。如果第二个管理员能通过任务管理器结束锁屏进程,说明程序没有做进程保护,需要进一步加保护手段。
5. 常见问题避坑:锁屏失效、闪退、解不开锁的排查记录
5.1 现象:锁屏窗口一闪而过,无法驻留
原因:窗口过程没有正确返回WM_NCDESTROY处理,消息循环提前退出;或者CreateWindowEx返回的句柄为 NULL,窗口创建失败后代码直接调用了PostQuitMessage。
解决:在WinMain里加句柄判空,创建失败时用GetLastError()输出错误码定位。窗口过程里DefWindowProc分支必须覆盖所有未处理的WM_*消息,特别是WM_CLOSE,要直接拦截并返回 0,否则用户按系统菜单的关闭命令会把锁屏窗口关掉:
case WM_CLOSE: return 0; // 禁止关闭5.2 现象:编译报错 C2146、C2664,源码一个字没改还是过不了
原因:字符集从多字节切到 Unicode 后,老代码里的char*字符串和TCHAR混用类型不匹配。最典型的就是_stricmp参数从char*变成wchar_t*,直接报 C2664。
解决:项目属性里统一设置为「使用多字节字符集」,不要再切回 Unicode。如果资源包源码里大量出现TEXT()宏和L""字面量混用,那说明作者是在 VC6 时代以 MBCS 为基础写的,你强行切 Unicode 就是给自己找麻烦。
5.3 现象:Win10 下 Ctrl+Alt+Del 能弹出安全桌面,锁屏失效
原因:应用层低级键盘钩子收不到 Ctrl+Alt+Del,这是系统设计决定的,万象是靠内核驱动才做到拦截。
解决:有两种变通方案。第一,用SetWinEventHook监听EVENT_SYSTEM_FOREGROUND,当检测到安全桌面窗口出现时,用PostMessage尝试关闭它,但 Win10 上成功率低。第二,把锁屏做成「请求工作站锁定」模式:调用LockWorkStation(),再配合定时器检测解锁状态,用户输入密码通过后系统自动解锁。第二种做法等于放弃仿万象界面,改用系统锁屏,但稳定。我一般会在发布说明里明确标注这两种方式的差异,让使用者自己选。
5.4 现象:输入正确密码回车没反应,焦点在编辑框但键盘无输入
原因:锁屏窗口没有设置WS_EX_NOACTIVATE导致焦点丢失,或者窗口过程里没有处理WM_KEYDOWN的VK_RETURN。还有一种情况是编辑框的父窗口不是你的锁屏窗口,GetDlgItemText拿到的是空字符串。
解决:在窗口过程里加WM_KEYDOWN分支,判断wParam == VK_RETURN后走校验逻辑。
5.5 现象:开机不自动启动,手动双击运行正常
原因:注册表 Run 键写到了HKCU,但当前启动用户是 Administrator,锁屏进程权限不够;或者杀毒软件拦截了 Run 键写入。
解决:改用HKLM写入,并在部署说明里提醒用户关闭杀毒软件实时防护后再写注册表。RegSetValueEx返回值要用ERROR_SUCCESS校验,失败时输出GetLastError()辅助排查。
6. 给锁屏加一个「登录记录」:用最朴素的文件追加实现审计功能
网吧环境下,锁屏不只是为了防蹭机,还要留下登录记录。万象的做法是把记录写到数据库服务端,我们自己用这份资源的话没必要引数据库,直接在解锁成功的分支里追加一行文本文件即可。
void WriteLoginLog(const TCHAR* szCardNo) { SYSTEMTIME st; GetLocalTime(&st); TCHAR szLine[128] = {0}; wsprintf(szLine, _T("%04d-%02d-%02d %02d:%02d:%02d - %s\r\n"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, szCardNo); HANDLE hFile = CreateFile(_T("C:\\NetBarLock\\login.log"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { SetFilePointer(hFile, 0, NULL, FILE_END); DWORD dwWritten = 0; WriteFile(hFile, szLine, lstrlen(szLine) * sizeof(TCHAR), &dwWritten, NULL); CloseHandle(hFile); } }OPEN_ALWAYS保证文件不存在时自动创建;FILE_SHARE_READ允许其他程序在锁屏运行时读取日志,方便远程网管查看;SetFilePointer到文件末尾让每次追加都接在后面。注意lstrlen统计的是字符数,要和sizeof(TCHAR)相乘才是真实字节数,写 Unicode 串时漏掉这一步会导致日志内容被截断。
调用时机放在密码比对成功的分支里,解锁动作发生之前。记录完再恢复任务栏、销毁窗口。如果密码输错,不写日志。这套逻辑上线后,网吧老板想看某台机器的使用记录,直接打开C:\NetBarLock\login.log就能看到完整的登录时间线,比反查万象数据库直观得多。
如果日志目录不存在,CreateFile会失败。部署时要先建好这个目录,或者改成在WinMain初始化时用CreateDirectory主动创建。从那以后我每次部署锁屏资源,都会强制走一遍「编译 → 静态链接 → 写注册表 → 重启验证 → 查看登录日志」的流程,五个步骤缺一个都可能留下隐患。希望这篇拆解能帮你在部署自己的锁屏程序时少踩几个坑。
本文还有配套的精品资源,点击获取