MFC屏幕软键盘源码拆解:字符注入原理与触屏输入实战
简介这是一份面向Windows平台开发者的屏幕软键盘虚拟键盘源码工程适合需要为触摸屏设备、公共终端或无障碍场景实现字符输入功能的开发者参考与二次开发。资源以MFC对话框程序为基础通过图形界面模拟QWERTY等键盘布局将鼠标或触摸点击事件转换为字符并发送至当前活动窗口可用于替代实体键盘完成安全输入。压缩包共20个文件约32KB包含5个C头文件、4个cpp实现文件、3个txt说明文档以及dsw/dsp工程文件、ico图标、rc资源脚本和url快捷方式等覆盖界面设计、事件处理、字符编码与系统托盘集成等关键模块。目前已有163人学习下载。借助完整工程结构与使用说明读者可快速理解软键盘的按键映射、消息发送与应用程序集成思路并在此基础上扩展自定义布局或辅助输入方案。1. 屏幕软键盘.zip 拆包一个能往记事本、浏览器、登录框里塞字符的 MFC 老工程上周有个做触屏一体机的朋友甩过来一个压缩包说现场没有实体键盘客户又要在自己写的采集软件里录工单号问我能不能找个不依赖系统自带 osk.exe 的方案。我解压看了一眼目录里躺着 OnscreenKeyboard.dsw、OnscreenKeyboardDlg.cpp、SystemTray.cpp 这一套典型的 VC6 时代 MFC 对话框工程。它干的事很直接自己画一个键盘窗口点哪个键就把对应字符送到当前获得焦点的那个程序里去记事本、浏览器地址栏、登录框都能收。这跟 Windows 自带的屏幕键盘不是一回事自带那个是系统组件改不了布局也嵌不进你自己的程序这份源码是给你一个能编译、能改键位、能挂托盘的基础壳子。适合谁做工业触屏 HMI 的、维护老 MFC 项目的、需要给无键盘设备补一个输入入口的嵌入式 Windows 开发者。下面我按拆包、编译、字符注入原理、避坑、进阶改造的顺序把这份资源从头到尾过一遍。2. 拆包看结构MFC 对话框工程里每个文件管什么2.1 从 .dsw 到 .cpp先认清这套 VC6 工程骨架解压之后文件不少但真正决定行为的就那么几个。我先把关键文件列出来你对照自己解压出来的目录看文件作用要不要动OnscreenKeyboard.dsw / .dspVC6 工作区与工程文件用新版 VS 打开时需转换OnscreenKeyboardDlg.cpp / .h主对话框逻辑键盘按钮事件都在这核心改动区OnscreenKeyboard.cpp / .h应用入口InitInstance 在这一般不动SystemTray.cpp / .h托盘图标封装最小化到托盘靠它按需改OnscreenKeyboard.rc / resource.h资源脚本与控件 ID 定义加按钮要动StdAfx.cpp / .h预编译头不动res / .rc2 / .ico图标等资源换图标时动使用说明.txt / ReadMe.txt作者留的说明先读这套结构是标准 MFC 对话框程序OnscreenKeyboard.cpp里的InitInstance创建主对话框OnscreenKeyboardDlg.cpp里用OnInitDialog初始化界面、用消息映射处理按钮点击。SystemTray.cpp是额外挂的托盘支持让键盘窗口能缩到右下角不占触屏可视区域。你要做的第一件事不是急着编译而是先打开OnscreenKeyboardDlg.h看它声明了哪些按钮成员变量和消息处理函数这决定了后面字符注入的入口在哪。2.2 用新版 Visual Studio 打开老工程转换与字符集两个必调项VC6 的 .dsw 现在的 VS 打不开得走转换。我一般这么干# 1. 备份原始压缩包别在原目录直接转 # 2. 用 VS2019/2022 打开 OnscreenKeyboard.dsw # 弹出升级向导时选是生成 .sln # 3. 转换后先别编译进项目属性改两处转换完成后右键项目 → 属性重点看两个地方。第一是字符集老工程默认多字节字符集MBCS新版 VS 默认 Unicode直接编会报一堆C2664无法转换参数。要么把字符集改回使用多字节字符集要么把代码里的char相关调用改成宽字符版本。第二是平台工具集如果目标机器是老系统选对应的 v141_xp 之类纯本机测试用默认即可。改完先编译一次把报错清干净再谈功能否则后面字符注入的问题会和编译问题混在一起排查起来就是玄学。提示转换前把整个目录复制一份.dsw 转换是不可逆的原工程结构被覆盖后想退回 VC6 就麻烦了。3. 字符注入原理软键盘怎么把按键送进别的程序3.1 焦点窗口与输入目标为什么点按钮能进记事本很多人第一次看这类源码会疑惑我点的是自己程序里的按钮字符怎么就跑到记事本里去了关键在于 Windows 的焦点机制。当你的软键盘窗口不是当前活动窗口、或者它刻意不抢焦点时用户点击键盘按钮按钮的点击事件在你进程里被处理但系统当前的前台窗口仍然是记事本。这时候只要把字符以键盘输入的形式投递到系统输入队列接收方就是那个前台窗口。实现上有两条主流路线。第一条是SendInput构造INPUT结构体类型设为INPUT_KEYBOARD填好虚拟键码调一次系统 API效果等同于真键盘按下。第二条是keybd_event老是老但在一些老系统上兼容性反而稳。这份 MFC 工程常见做法是在按钮的BN_CLICKED处理函数里调SendInput。核心代码大概长这样// 在 OnscreenKeyboardDlg.cpp 的按钮处理函数里 void COnscreenKeyboardDlg::OnButtonKeyA() { // 构造键盘输入事件按下 抬起 INPUT input[2] {0}; input[0].type INPUT_KEYBOARD; input[0].ki.wVk A; // 虚拟键码字母直接用 ASCII input[0].ki.dwFlags 0; // 0 表示按下 input[1].type INPUT_KEYBOARD; input[1].ki.wVk A; input[1].ki.dwFlags KEYEVENTF_KEYUP; // 抬起 SendInput(2, input, sizeof(INPUT)); }逻辑说明SendInput第一个参数是事件数组长度按下和抬起必须成对只发按下不发抬起目标程序会以为键一直按着出现连发或卡键。参数说明ki.wVk填虚拟键码字母数字直接用字符本身功能键要用VK_SHIFT、VK_BACK这类常量dwFlags为 0 是按下KEYEVENTF_KEYUP是抬起要发扩展键还得加KEYEVENTF_EXTENDEDKEY。这段是整个软键盘的心脏改键位、加组合键都从这里下手。3.2 不抢焦点软键盘窗口自己的样式怎么设如果软键盘窗口一被点击就变成前台窗口那字符就全进它自己了记事本一个字收不到。所以窗口创建时通常要加WS_EX_NOACTIVATE扩展样式让它被点击时不激活// 在 OnInitDialog 或 PreCreateWindow 里 ModifyStyleEx(0, WS_EX_NOACTIVATE); // 或者创建时指定 // CreateEx(WS_EX_NOACTIVATE | WS_EX_TOPMOST, ...)WS_EX_NOACTIVATE让窗口能接收鼠标点击但不抢焦点这是软键盘能隔空打字的前提。WS_EX_TOPMOST是让它浮在其他窗口上面触屏场景下不被遮挡。这两个样式配合才是完整的可用形态。少了WS_EX_NOACTIVATE你会看到字符全打在软键盘自己的某个隐藏编辑框里这就是最常见的翻车现场。3.3 特殊键与组合键Shift、退格、回车怎么发字母数字好办特殊键要单独处理。退格是VK_BACK回车是VK_RETURN空格是VK_SPACETab 是VK_TAB。组合键比如大写字母或 Shift数字出符号得先发 Shift 按下再发目标键最后发 Shift 抬起// Shift 某键的组合输入 INPUT input[4] {0}; input[0].type INPUT_KEYBOARD; input[0].ki.wVk VK_SHIFT; input[1].type INPUT_KEYBOARD; input[1].ki.wVk A; // 目标键 input[2].type INPUT_KEYBOARD; input[2].ki.wVk A; input[2].ki.dwFlags KEYEVENTF_KEYUP; input[3].type INPUT_KEYBOARD; input[3].ki.wVk VK_SHIFT; input[3].ki.dwFlags KEYEVENTF_KEYUP; SendInput(4, input, sizeof(INPUT));顺序不能乱Shift 按下 → 目标键按下 → 目标键抬起 → Shift 抬起。中间任何一步顺序错了出来的字符就不对。触屏上做大小写切换本质就是维护一个 Shift 状态标志点字母时判断要不要包一层 Shift。4. 编译与运行排查从报错到能打字的完整链路4.1 编译期常见报错与对应改法老 MFC 工程在新环境编译报错集中在几类。第一类fatal error C1083: 无法打开包括文件 afxwin.h说明没装 MFC 组件去 VS Installer 里勾使用 C 的桌面开发下的 MFC。第二类error C2664: 无法将参数从 const char* 转换为 LPCWSTR就是前面说的字符集问题改项目属性或改代码。第三类LNK2019: 无法解析的外部符号多半是SystemTray.cpp没加进工程或者某个库没链接。第四类error RC1015: cannot open include file afxres.h资源编译器找不到 MFC 头检查包含路径。我一般按这个顺序清先解决 C1083 和 RC1015 这类找不到文件再解决 C2664 这类类型不匹配最后处理 LNK 链接错误。顺序反了会在类型错误里绕圈。4.2 运行期字符打不进去的三种典型情况编译过了不代表能用。字符打不进去按这三条查。第一目标程序是不是以管理员权限运行而你的软键盘不是Windows 的 UIPI 机制会拦截低完整性进程向高完整性进程发送输入两边权限要一致。第二软键盘窗口是不是抢了焦点回去检查WS_EX_NOACTIVATE有没有生效。第三目标程序是不是用了 DirectInput 或 Raw Input 这类绕过系统消息队列的输入方式游戏和部分专业软件会这样SendInput对它们无效这是原理层面的边界不是代码 bug。注意UIPI 拦截是静默的SendInput返回成功但字符没进去很容易误判成代码问题。先确认两边权限等级。4.3 托盘与最小化SystemTray 模块的接入要点SystemTray.cpp负责把键盘窗口缩到托盘。接入时注意两点一是托盘图标的消息回调要正确注册否则点了没反应二是窗口隐藏和显示的状态要同步别出现托盘图标没了但窗口还在或者反过来的情况。触屏设备上托盘图标本身不好点我一般会额外留一个常驻的小按钮来切换显示托盘只作为兜底。5. 避坑与常见问题软键盘落地时最容易翻车的五处现象一点按钮字符全进软键盘自己。原因窗口没有WS_EX_NOACTIVATE点击时抢了焦点。解决加扩展样式并确认创建窗口时样式生效必要时在OnInitDialog里ModifyStyleEx补一次。现象二字母能打退格和回车没反应。原因特殊键没映射到正确的虚拟键码或者只发了按下没发抬起。解决退格用VK_BACK、回车用VK_RETURN并保证按下抬起成对。现象三目标程序是管理员权限软键盘打字无效。原因UIPI 完整性级别拦截。解决让软键盘也以管理员权限运行或降低目标程序权限两边对齐。现象四连点同一个键出现重复字符或卡键。原因SendInput事件数组里按下抬起没配对或键盘状态没复位。解决每个键严格按下抬起两条事件组合键结束后把 Shift 等修饰键状态清干净。现象五换台机器编译报一堆字符集错误。原因不同 VS 版本默认字符集不同工程没锁定。解决在项目属性里显式指定字符集别依赖默认值团队协作时把 .vcxproj 一起纳入版本管理。6. 进阶改造把固定键盘变成可配置输入面板基础功能跑通后真正体现价值的是改造。我一般会做三件事。第一把键位从硬编码改成配置驱动用一张表描述每个按钮的显示文本、虚拟键码、是否需要 Shift加载时动态生成按钮这样换布局不用重编译。第二加输入法无关的数字键盘模式工业采集场景往往只需要 0-9 和小数点把无关键隐藏掉减少误触。第三加一个发送字符串的批量接口把一整串工单号一次性SendInput出去比逐键点快得多。验证改造是否成功我有个固定习惯开一个记事本把每个键都点一遍对照预期字符逐个核对再开一个浏览器登录框测一遍最后用管理员权限的程序测一遍。三处都过才算真的能用。从那以后我每次拿到这类输入注入的工程都强制走一遍记事本→浏览器→高权限程序的验证链路少一步都可能在上线后被打回来。希望帮到你。本文还有配套的精品资源点击获取