找回密码
 注册
搜索
系统gho:最纯净好用系统下载站投放广告、加入VIP会员,请联系 微信:wuyouceo
查看: 486|回复: 12

办公软件WPS的JSA中模拟键盘输入

[复制链接]
发表于 7 天前 | 显示全部楼层 |阅读模式
办公软件WPS的表格:通过ExecuteExcel4Macro调用WinAPI实现

函数:
  1. function 操作键盘(操作数组){
  2.         let 序号=0,键盘数组=[];
  3.         if(Array.isArray(操作数组)){
  4.                 for(let i=0;i<操作数组.length;i++){
  5.                         if(Array.isArray(操作数组[i])){
  6.                                 for(let j=0;j<操作数组[i].length;j++){
  7.                                         键盘数组[序号]=操作数组[i][j];序号++;
  8.                                 }
  9.                         }else{键盘数组[序号]=操作数组[i];序号++;}
  10.                 }
  11.         }else{键盘数组[序号]=操作数组;}
  12.         let 操作组数=键盘数组.length;
  13.         let 结构字节数=对象字节数(键盘数组[0]);        /*****计算结构对象字节数*****/
  14.         let 总字节=结构字节数*操作组数;
  15.         const 数组缓冲=new ArrayBuffer(总字节);        //操作对象数组写入缓冲
  16.         for(let i=0;i<操作组数;i++){
  17.                 结构对象填入数组缓冲(数组缓冲,i*结构字节数,键盘数组[i]);        /*****对象转数组缓冲*****/
  18.         }
  19.         const 执行宏调用=ExecuteExcel4Macro;        //申请内存
  20.         const 内存地址=执行宏调用(`CALL("Kernel32","VirtualAlloc","JJJJJ",0,${总字节},${0x3000},4)`);
  21.         数据写入内存(内存地址,数组缓冲,总字节);        /*****写入键盘操作结构*****/        //执行键盘操作
  22.         键盘操作结果=执行宏调用(`CALL("User32","SendInput","JJJJ",${操作组数},${内存地址},${结构字节数})`);
  23.         if(!键盘操作结果){console.log("错误代码:"+执行宏调用(`CALL("Kernel32","GetLastError","I")`));}
  24.         执行宏调用(`CALL("Kernel32", "VirtualFree", "JJJJ", ${内存地址}, 0, ${0x8000})`);        //释放内存
  25. }
复制代码

测试:键盘输入测试();  //执行函数前,请先打开一个记事本,将鼠标指针停在记事本窗口上方

测试及调用函数:
  1. function 键盘输入测试(){
  2.         //执行函数前,请先打开一个记事本,将鼠标指针停在记事本窗口上方
  3.         let 点击=鼠标操作对象(2|4);
  4.         操作鼠标([点击]);        /*****鼠标单击操作*****/

  5.         let 字符串="WuYou.Net";        //无忧论坛,将网址输入到记事本
  6.         let 字符数组=字符串.split(''),按键数组=[];
  7.         for(let i=0;i<字符串.length;i++){
  8.                 var 字符=字符数组[i];
  9.                 if(字符>='a' && 字符<='z'){字符=字符.toUpperCase();}
  10.                 按键数组[i]=键盘操作对象(字符);
  11.         }
  12.         操作键盘(按键数组);        /*****键盘操作*****/
  13. }

  14. function 键盘操作对象(键码){
  15.         class 键盘操作 {        //类
  16.                 constructor(按键,动作){        //类的构造方法,用于构造属性
  17.                         this.输入类型1 = [4,1],        //键盘1        //字节数,数据
  18.                         this.虚拟键码2 = [4,按键],        //同ASCII码
  19.                         this.键扫描码3 = [4,0],
  20.                         this.输入操作4 = [4,动作],        //按键弹起2        启用扫描码8
  21.                         this.时间戳值5 = [4,0],        //系统确定
  22.                         this.扩展信息6 = [4,0],
  23.                         this.数据填充x = [4,0]        //都是4*7=28字节,不足部分补足
  24.                 }
  25.         };
  26.         if(键码>='A'&&键码<='Z'){键码=键码.charCodeAt(0);}
  27.         if(键码=='.'){键码=0xBE;}
  28.         let 键盘对象 = new 键盘操作(键码,4|2);        //统一码0x4|2键弹起
  29.         return 键盘对象;
  30. }
  31. function 鼠标操作对象(操作){
  32.         class 鼠标操作 {        //类
  33.                 constructor(动作){        //类的构造方法,用于构造属性
  34.                         this.输入类型1 = [4,0],        //鼠标0        //字节数,数据
  35.                         this.水平坐标2 = [4,0],        //相对坐标,即移动量
  36.                         this.垂直坐标3 = [4,0],
  37.                         this.滚轮转量4 = [4,0],
  38.                         this.输入操作5 = [4,动作],        //右键按下8        绝对坐标0x8000
  39.                         this.时间戳值6 = [4,0],        //系统确定
  40.                         this.扩展数据7 = [4,0]
  41.                 }
  42.         };
  43.         let 鼠标对象 = new 鼠标操作(操作);
  44.         return 鼠标对象;
  45. }
  46. function 对象字节数(结构对象){
  47.         let 对象数据字节数=0;
  48.         for (let 属性名 in 结构对象) {
  49.                 if (结构对象.hasOwnProperty(属性名)) {
  50.                         对象数据字节数+=结构对象[属性名][0];        //累加字节数
  51.                 }
  52.         }
  53.         return 对象数据字节数;
  54. }
  55. function 结构对象填入数组缓冲(数组缓冲,偏移,结构对象){
  56.         const 数据操作=new DataView(数组缓冲);
  57.         let 字节数,待填数据;
  58.         for (let 属性名 in 结构对象) {
  59.                 if (结构对象.hasOwnProperty(属性名)) {
  60.                         字节数=结构对象[属性名][0],待填数据=结构对象[属性名][1];
  61.                         if(字节数==4){数据操作.setInt32(偏移,待填数据,true);}        //小端序
  62.                         else{if(字节数==2){数据操作.setInt32(偏移,待填数据,true);}
  63.                         else{if(字节数==1){数据操作.setInt32(偏移,待填数据,true);}}}
  64.                         偏移+=字节数;
  65.                 }
  66.         }
  67. }
  68. function 操作鼠标(操作数组){
  69.         let 操作组数=操作数组.length;
  70.         let 结构字节数=对象字节数(操作数组[0]);        /*****计算结构对象字节数*****/
  71.         let 总字节=结构字节数*操作组数;
  72.         const 数组缓冲=new ArrayBuffer(总字节);        //操作对象数组写入缓冲
  73.         for(let i=0;i<操作组数;i++){
  74.                 结构对象填入数组缓冲(数组缓冲,i*结构字节数,操作数组[i]);        /*****对象转数组缓冲*****/
  75.         }
  76.         const 执行宏调用=ExecuteExcel4Macro;        //申请内存
  77.         const 内存地址=执行宏调用(`CALL("Kernel32","VirtualAlloc","JJJJJ",0,${总字节},${0x3000},4)`);
  78.         数据写入内存(内存地址,数组缓冲,总字节);        /*****写入鼠标操作结构*****/        //执行鼠标操作
  79.         鼠标操作结果=执行宏调用(`CALL("User32","SendInput","JJJJ",${操作组数},${内存地址},${结构字节数})`);
  80.         if(!鼠标操作结果){console.log("错误代码:"+执行宏调用(`CALL("Kernel32","GetLastError","I")`));}
  81.         执行宏调用(`CALL("Kernel32", "VirtualFree", "JJJJ", ${内存地址}, 0, ${0x8000})`);        //释放内存
  82. }
  83. function 数据写入内存(内存地址,数组缓冲,字节数){
  84.         const 数据操作=new DataView(数组缓冲);
  85.         let 偏移=i=0,待写数据=[],写入字节=[];
  86.         while(字节数>偏移){
  87.                 if(字节数-偏移>=4){待写数据[i]=数据操作.getInt32(偏移,true);写入字节[i]=4;i++;偏移+=4;}
  88.                 else{if(字节数-偏移>=2){待写数据[i]=数据操作.getInt16(偏移,true);写入字节[i]=2;i++;偏移+=2;}
  89.                 else{if(字节数-偏移==1){待写数据[i]=数据操作.getInt8(偏移,true);写入字节[i]=1;i++;偏移++;}}}
  90.         }
  91.         const 执行宏调用=ExecuteExcel4Macro;
  92.         for(偏移=i=0;i<写入字节.length;i++){        //写入
  93.                 执行宏调用(`CALL("Kernel32","RtlMoveMemory","2JNJ",${内存地址+偏移},${待写数据[i]},${写入字节[i]})`);
  94.                 偏移+=写入字节[i];
  95.         }
  96. }
复制代码

数据结构类似鼠标操作。

发表于 7 天前 | 显示全部楼层
谢谢分享坐个沙发
回复

使用道具 举报

发表于 7 天前 | 显示全部楼层
很复杂的代码,表示看不懂,惭愧

评分

参与人数 1无忧币 +5 收起 理由
yyz2191958 + 5

查看全部评分

回复

使用道具 举报

 楼主| 发表于 7 天前 | 显示全部楼层

用WinAPI寻找窗口,并模拟键盘输入

本帖最后由 cutebe 于 2026-4-26 19:02 编辑

测试及调用函数:
  1. function 找到窗口键盘输入测试(){        //记事本编辑框中输入内容
  2.         console.clear();
  3.         const 执行宏调用=ExecuteExcel4Macro;
  4.        
  5.         let 记事本类="Notepad";        //找到那个打开的记事本窗口
  6.         var 记事本句柄=执行宏调用(`CALL("User32","FindWindowA","JFJ","${记事本类}",0)`);
  7.         console.log("记事本句柄:"+记事本句柄);
  8.         执行宏调用(`CALL("User32","ShowWindow","JJJ",${记事本句柄},9)`);        //激活并显示SW_RESTORE=9
  9.     执行宏调用(`CALL("User32","SetForegroundWindow","JJ",${记事本句柄})`);        //前置
  10.        
  11.         //'A'=0x41, 'B'=0x42, 'C'=0x43, 'N'=0x4E, 'Z'=0x5A
  12.         let 字符串="WuYou.Net";        //无忧论坛网址,用来填写
  13.         let 字符数组=字符串.split(''),按键数组=[];
  14.         for(let i=0;i<字符串.length;i++){
  15.                 var 字符=字符数组[i];
  16.                 if(字符>='a' && 字符<='z'){字符=字符.toUpperCase();}
  17.                 按键数组[i]=键盘操作对象(字符);
  18.         }
  19.         操作键盘(按键数组);        /*****键盘操作*****/
  20. }

  21. function 操作键盘(操作数组){
  22.         let 序号=0,键盘数组=[];
  23.         if(Array.isArray(操作数组)){
  24.                 for(let i=0;i<操作数组.length;i++){
  25.                         if(Array.isArray(操作数组[i])){
  26.                                 for(let j=0;j<操作数组[i].length;j++){
  27.                                         键盘数组[序号]=操作数组[i][j];序号++;
  28.                                 }
  29.                         }else{键盘数组[序号]=操作数组[i];序号++;}
  30.                 }
  31.         }else{键盘数组[序号]=操作数组;}
  32.         let 操作组数=键盘数组.length;
  33.         let 结构字节数=对象字节数(键盘数组[0]);        /*****计算结构对象字节数*****/
  34.         let 总字节=结构字节数*操作组数;
  35.        
  36.         const 数组缓冲=new ArrayBuffer(总字节);        //操作对象数组写入缓冲
  37.         for(let i=0;i<操作组数;i++){
  38.                 结构对象填入数组缓冲(数组缓冲,i*结构字节数,键盘数组[i]);        /*****对象转数组缓冲*****/
  39.         }
  40.         const 执行宏调用=ExecuteExcel4Macro;        //申请内存
  41.         const 内存地址=执行宏调用(`CALL("Kernel32","VirtualAlloc","JJJJJ",0,${总字节},${0x3000},4)`);
  42.         数据写入内存(内存地址,数组缓冲,总字节);        /*****写入键盘操作结构*****/        //执行键盘操作
  43.         键盘操作结果=执行宏调用(`CALL("User32","SendInput","JJJJ",${操作组数},${内存地址},${结构字节数})`);
  44.         if(!键盘操作结果){console.log("错误代码:"+执行宏调用(`CALL("Kernel32","GetLastError","I")`));}
  45.         执行宏调用(`CALL("Kernel32", "VirtualFree", "JJJJ", ${内存地址}, 0, ${0x8000})`);        //释放内存
  46. }
  47. function 键盘操作对象(键码){
  48.         class 键盘操作 {        //类
  49.                 constructor(按键,动作){        //类的构造方法,用于构造属性
  50.                         this.输入类型1 = [4,1],        //键盘1        //字节数,数据
  51.                         this.虚拟键码2 = [4,按键],        //同ASCII码
  52.                         this.键扫描码3 = [4,0],
  53.                         this.输入操作4 = [4,动作],        //按键弹起2        启用扫描码8
  54.                         this.时间戳值5 = [4,0],        //系统确定
  55.                         this.扩展信息6 = [4,0],
  56.                         this.数据填充0 = [4,0]        //都是4*7=28字节,不足部分补足
  57.                 }
  58.         };
  59.         if(键码>='A'&&键码<='Z'){键码=键码.charCodeAt(0);}
  60.         if(键码=='.'){键码=0xBE;}
  61.         let 键盘对象 = new 键盘操作(键码,4|2);        //统一码0x4|2键弹起
  62.         return 键盘对象;
  63. }
  64. function 对象字节数(结构对象){
  65.         let 对象数据字节数=0;
  66.         for (let 属性名 in 结构对象) {
  67.                 if (结构对象.hasOwnProperty(属性名)) {
  68.                         对象数据字节数+=结构对象[属性名][0];        //累加字节数
  69.                 }
  70.         }
  71.         return 对象数据字节数;
  72. }
  73. function 结构对象填入数组缓冲(数组缓冲,偏移,结构对象){
  74.         const 数据操作=new DataView(数组缓冲);
  75.         let 字节数,待填数据;
  76.         for (let 属性名 in 结构对象) {
  77.                 if (结构对象.hasOwnProperty(属性名)) {
  78.                         字节数=结构对象[属性名][0],待填数据=结构对象[属性名][1];
  79.                         if(字节数==4){数据操作.setInt32(偏移,待填数据,true);}        //小端序
  80.                         else{if(字节数==2){数据操作.setInt32(偏移,待填数据,true);}
  81.                         else{if(字节数==1){数据操作.setInt32(偏移,待填数据,true);}}}
  82.                         偏移+=字节数;
  83.                 }
  84.         }
  85. }
  86. function 数据写入内存(内存地址,数组缓冲,字节数){
  87.         const 数据操作=new DataView(数组缓冲);
  88.         let 偏移=i=0,待写数据=[],写入字节=[];
  89.         while(字节数>偏移){
  90.                 if(字节数-偏移>=4){待写数据[i]=数据操作.getInt32(偏移,true);写入字节[i]=4;i++;偏移+=4;}
  91.                 else{if(字节数-偏移>=2){待写数据[i]=数据操作.getInt16(偏移,true);写入字节[i]=2;i++;偏移+=2;}
  92.                 else{if(字节数-偏移==1){待写数据[i]=数据操作.getInt8(偏移,true);写入字节[i]=1;i++;偏移++;}}}
  93.         }
  94.         const 执行宏调用=ExecuteExcel4Macro;
  95.         for(偏移=i=0;i<写入字节.length;i++){        //写入
  96.                 执行宏调用(`CALL("Kernel32","RtlMoveMemory","2JNJ",${内存地址+偏移},${待写数据[i]},${写入字节[i]})`);
  97.                 偏移+=写入字节[i];
  98.         }
  99. }
复制代码


回复

使用道具 举报

发表于 7 天前 | 显示全部楼层
回复

使用道具 举报

发表于 7 天前 | 显示全部楼层
表示看不懂,惭愧
回复

使用道具 举报

 楼主| 发表于 6 天前 | 显示全部楼层
本帖最后由 cutebe 于 2026-4-27 11:30 编辑

通过发送消息,来填写编辑区文本。WinAPI: SendMessage
  1. function 记事本窗口发送文本测试(){        //记事本编辑框中内容
  2.         console.clear();
  3.         const 执行宏调用=ExecuteExcel4Macro;
  4.         
  5.         //找到打开的记事本窗口,没有则打开一个新记事本
  6.         let 记事本类="Notepad";
  7.         var 记事本句柄=执行宏调用(`CALL("User32","FindWindowA","JFJ","${记事本类}",0)`);
  8.         console.log("记事本句柄:"+记事本句柄);
  9.         if(!记事本句柄){
  10.                 let 记事本进程句柄=执行宏调用(`CALL("Shell32","ShellExecuteA","JJFFJJJ",0,"open","notepad",0,0,1)`);
  11.                 console.log("记事本进程句柄:"+记事本进程句柄);
  12.                 alert("等待,确定后向记事本写入信息");        //弹窗提示
  13.                 记事本句柄=执行宏调用(`CALL("User32","FindWindowA","JFJ","${记事本类}",0)`);
  14.                 console.log("记事本句柄:"+记事本句柄);
  15.         }
  16.             执行宏调用(`CALL("User32","SetForegroundWindow","JJ",${记事本句柄})`);        //前置窗口
  17.         
  18.         //定位到记事本的编辑输入区
  19.         let 编辑框类="Edit";
  20.         let 编辑框句柄=执行宏调用(`CALL("User32","FindWindowExA","JJJFJ",${记事本句柄},0,"${编辑框类}",0)`);
  21.         
  22.         //通过发送消息,向记事本写入文本
  23.         let 设置文本=0xC,字符串="无忧启动 WuYou.Net";
  24.         var 设置文本框内容=执行宏调用(`CALL("User32", "SendMessageA", "JJJJF", ${编辑框句柄},${设置文本},0,"${字符串}")`);
  25. }
复制代码
回复

使用道具 举报

发表于 6 天前 | 显示全部楼层
进来了解了解
回复

使用道具 举报

发表于 6 天前 | 显示全部楼层
支持,LZ辛苦了
回复

使用道具 举报

 楼主| 发表于 11 小时前 | 显示全部楼层

模拟按键 Win + R 打开运行对话框

本帖最后由 cutebe 于 2026-5-3 10:29 编辑
  1. function 打开运行对话框键盘测试(){        //快捷键 Win + R
  2.         let 按下=0,扩展=1,松开=2;
  3.         //键盘事件(虚拟键码,扫描键码,键标志);
  4.         let 左窗口=0x5B,运行=0x52;
  5.         键盘事件(左窗口,左窗口,按下|扩展);        //按下LWin
  6.         键盘事件(运行,0,按下);        //按下R
  7.         键盘事件(运行,0,松开);        //松开R
  8.         键盘事件(左窗口,左窗口,松开|扩展);        //松开LWin        //合成 LWin + R 运行
  9. }
  10. function 键盘事件(虚拟键码,扫描键码,键标志){
  11.         const 执行宏调用=ExecuteExcel4Macro;        //扫描码只需要低位,高位0xE0省略        //扩展键才用扫描码
  12.         执行宏调用(`CALL("User32","keybd_event","JJJJJ",${虚拟键码},${扫描键码},${键标志},0)`);
  13. }
复制代码



扩展键与对应扫描码:
扩展按键扫描码说明
----- ----- ---------------
系统关机0xE05E
系统睡眠0xE05F
系统唤醒0xE063
键盘 PrintScreen 键0xE037
键盘 Pause 键0xE046
键盘 Insert 键0xE052
键盘 Home 键0xE047
键盘 PageUp 键0xE049
键盘 Delete 键0xE053
键盘 End 键0xE04F
键盘 PageDown 键0xE051
键盘向右→键0xE04D
键盘向左←键0xE04B
键盘向下↓键0xE050
键盘向上↑键0xE048
小键盘 Num Lock 键0xE045
小键盘 /0xE035
小键盘 ENTER 键0xE01C
键盘应用程序0xE05D//菜单键,在右Win和右Ctrl之间
键盘电源0xE05E
键盘 左Win 键0xE05B
键盘 右Ctrl 键0xE01D
键盘 右Alt 键0xE038
键盘 右Win 键0xE05C
扫描下一个曲目0xE019
扫描上一个曲目0xE010
停止0xE024
播放/暂停0xE022
静音0xE020
音量增大0xE030
音量减小0xE02E
AL 使用者控制配置0xE06D
AL 电子邮件阅读器0xE06C
AL 计算器0xE021
AL 本地计算机浏览器0xE06B
AC 搜索0xE065
AC 主页0xE032
AC 返回0xE06A
AC 向前0xE069
AC 停止0xE068
AC 刷新0xE067
AC 书签0xE066


回复

使用道具 举报

 楼主| 发表于 11 小时前 | 显示全部楼层

模拟键盘输入应用:打开关闭小键盘数字指示灯

  1. function 关闭数字灯(){
  2.         let 数字键码=0x90;
  3.         let 数字灯状态=获取按键状态(数字键码);        //关闭0
  4.         if(数字灯状态){
  5.                 let 数字扫描码=0x45,按下=0,扩展=1,松开=2;
  6.                 键盘事件(数字键码,数字扫描码,按下|扩展);        //按下NumLock
  7.                 键盘事件(数字键码,数字扫描码,松开|扩展);        //松开NumLock
  8.         }
  9. }
  10. function 打开数字灯(){
  11.         let 数字键码=0x90;
  12.         let 数字灯状态=获取按键状态(数字键码);        //打开1
  13.         if(!数字灯状态){
  14.                 let 数字扫描码=0x45,按下=0,扩展=1,松开=2;
  15.                 键盘事件(数字键码,数字扫描码,按下|扩展);        //按下NumLock
  16.                 键盘事件(数字键码,数字扫描码,松开|扩展);        //松开NumLock
  17.         }
  18. }
  19. function 获取按键状态(键盘码){
  20.         const 执行宏调用=ExecuteExcel4Macro;
  21.         let 状态=执行宏调用(`CALL("User32","GetKeyState","IJ",${键盘码})`);
  22.         return 状态;
  23. }
  24. function 键盘事件(虚拟键码,扫描键码,键标志){
  25.         const 执行宏调用=ExecuteExcel4Macro;        //扫描码只需要低位,高位0xE0省略        //扩展键才用扫描码
  26.         执行宏调用(`CALL("User32","keybd_event","JJJJJ",${虚拟键码},${扫描键码},${键标志},0)`);
  27. }
复制代码


回复

使用道具 举报

 楼主| 发表于 10 小时前 | 显示全部楼层

模拟键盘输入应用:截屏

  1. function 截取全屏测试(){        //截屏键PrintScreen
  2.         let 按下=0,扩展=1,松开=2;
  3.         let 截屏键码=0x2C,截屏扫描码=0x37;
  4.         键盘事件(截屏键码,截屏扫描码,按下|扩展);        //按下PrintScreen
  5.         键盘事件(截屏键码,截屏扫描码,松开|扩展);        //松开PrintScreen
  6. }
  7. function 截屏当前窗口测试(){        //当前窗口 Alt + PrintScreen
  8.         let 按下=0,扩展=1,松开=2;        //Alt0x12,左Alt0xA4
  9.         let 修改键码=0x12,截屏键码=0x2C,截屏扫描码=0x37;
  10.         键盘事件(修改键码,0,按下);        //按下Alt
  11.         键盘事件(截屏键码,截屏扫描码,按下|扩展);        //按下PrintScreen
  12.         键盘事件(截屏键码,截屏扫描码,松开|扩展);        //松开PrintScreen
  13.         键盘事件(修改键码,0,松开);        //松开Alt
  14. }
  15. function 键盘事件(虚拟键码,扫描键码,键标志){
  16.         const 执行宏调用=ExecuteExcel4Macro;        //扫描码只需要低位,高位0xE0省略        //扩展键才用扫描码
  17.         执行宏调用(`CALL("User32","keybd_event","JJJJJ",${虚拟键码},${扫描键码},${键标志},0)`);
  18. }
复制代码


回复

使用道具 举报

 楼主| 发表于 5 小时前 | 显示全部楼层

发现 WinAPI 中的 SendInput() 在模拟键盘操作时有个问题:

模拟 LWin 或 RWin 时按下了后无法松开!需要再手工去按下相应键才能松开。


而用相对简单的 keybd_event() 模拟按键输入没有这个问题。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|捐助支持|无忧启动 ( 闽ICP备05002490号-1|闽公网安备35020302032614号 )

GMT+8, 2026-5-3 21:52

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表