找回密码
 注册
搜索
[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
查看: 759|回复: 7

[转载代码] 在办公软件WPS的JSA中操作剪贴板文本

[复制链接]
发表于 2026-3-24 17:38:18 | 显示全部楼层 |阅读模式
本帖最后由 cutebe 于 2026-3-30 20:50 编辑

使用方法:        //JS代码,在WPS的JSA宏中使用        //适用于还没有ffi的老版本WPS
  1. var 剪贴板文本=获取剪贴板文本();
  2. var 修改文本="新文本:\n"+ 剪贴板文本 +"\n字符串结束!";
  3. 设置剪贴板文本(修改文本);        //新文本放入剪贴板,可粘贴使用
复制代码


办公软件WPS的JSA宏无法直接使用系统剪贴板,但可以通过ExecuteExcel4Macro的CALL来调用系统功能以实现剪贴板操作。
最新版本的WPS可以用FFI/ffi方便实现,但没有这些的中间版本就需要ExecuteExcel4Macro了。

使用ExecuteExcel4Macro调用CALL时有所限制。CALL的函数其 参数/返回值 如果是字符串,则最大只到255字节(缓冲区256字节,包含有一字节结束符)。所以只要调用的函数(系统API)有其中一个参数是字符串,或是返回值是字符串,都需要切成小段逐段处理。

设置剪贴板文本("文本/字符串");
  1. function 设置剪贴板文本(文本内容){    //参数为字符串
  2.     let 执行宏调用=ExecuteExcel4Macro;    //调用系统应用接口。简化书写
  3.     let 分段文本=[],段长度=127,分段字节=[],总字节=0;    //去掉结束符\0,双字节文字最长127
  4.     for(let 起始=i=0;起始<文本内容.length;起始+=段长度,i++){
  5.         分段文本.push(文本内容.slice(起始,起始+段长度));
  6.         分段字节.push(分段文本[i].replace(/[^\x00-\xff]/g,'xx').length);    //计算字节数
  7.         总字节+=分段字节[i];
  8.     }    //console.clear();    //清空立即窗口(控制台)信息
  9.     if (执行宏调用(`CALL("User32","OpenClipboard","AJ",0)`)){        //打开剪贴板
  10.         if(执行宏调用(`CALL("user32.dll","EmptyClipboard","A")`)){    //清空剪切板
  11.             const 内存句柄=执行宏调用(`CALL("Kernel32","LocalAlloc","JJJ",${0x42},${总字节+1})`);    //分配内存
  12.             //console.log("内存块大小:"+执行宏调用(`CALL("Kernel32","LocalSize","JJ",${内存句柄})`));
  13.             if(内存句柄){    //内存分配成功。然后锁、写、解内存。最后设置剪贴板。
  14.                 const 内存地址=执行宏调用(`CALL("kernel32.dll","LocalLock","JJ",${内存句柄})`);    //锁定对象,返回地址
  15.                 for(let 偏移字节=i=0;i<分段文本.length;i++){    //字符串(JS变量)分批复制到内存中。分批是因为有限长
  16.                     执行宏调用(`CALL("Kernel32","lstrcpynW","JJFJ",${内存地址+偏移字节},"${分段文本[i].replace(/"/g,'""')}",${分段字节[i]})`);
  17.                     偏移字节+=分段字节[i];    //返回地址J。
  18.                 }
  19.                 执行宏调用(`CALL("kernel32.dll","LocalUnlock","JJ",${内存句柄})`);    //解锁内存对象
  20.                 let 剪贴板文本=执行宏调用(`CALL("User32","SetClipboardData","JJJ",1,${内存句柄})`);
  21.                 执行宏调用(`CALL("Kernel32","LocalFree","JJ",${内存句柄})`);    //释放内存
  22.             }else{console.log("未能分配内存!");}    //数据放入剪切板,参数为1(CF_TEXT)则返回文本
  23.         }else{console.log('未能清空剪贴板!');}
  24.         执行宏调用(`CALL("User32.dll","CloseClipboard","A")`);    //关闭剪贴板,以释放控制权。
  25.     }else{alert('无法打开剪贴板!');}
  26. }
复制代码


var 字符串=获取剪贴板文本();
  1. function 获取剪贴板文本(){    //返回字符串
  2.     let 内存地址,剪贴板文本="",执行宏调用=ExecuteExcel4Macro;
  3.     if(执行宏调用(`CALL("user32.dll","OpenClipboard","JJ",0)`)){    //打开剪贴板
  4.         const 内存句柄=执行宏调用(`CALL("user32","GetClipboardData","JJ",1)`);    //获取剪贴板文本,返回句柄。
  5.         if(内存句柄){    //锁定内存对象,获取内存地址
  6.             内存地址=执行宏调用(`CALL("kernel32","LocalLock","JJ",${内存句柄})`);
  7.             let 总字节,偏移字节=0,段长度=127,分段文本='';
  8.             总字节=执行宏调用(`CALL("Kernel32","lstrlenA","JJ",${内存地址})`);    //剪贴板文本字节数
  9.             while(偏移字节<总字节){    //返回字符串F。返回值超255字节时,也需要分段操作
  10.                 分段文本=执行宏调用(`CALL("Kernel32","lstrcpynW","FFJJ","",${内存地址+偏移字节},${段长度})`);
  11.                 偏移字节+=分段文本.replace(/[^\x00-\xff]/g,'xx').length;    //计算分段文本字节数
  12.                 剪贴板文本+=分段文本;
  13.             }
  14.             执行宏调用(`CALL("kernel32.dll","LocalUnlock","JJ",${内存句柄})`);    //解锁
  15.         }else{console.log("未能获取剪贴板数据的内存句柄!");}
  16.         执行宏调用(`CALL("User32","CloseClipboard","J")`);    //关闭剪贴板,让其它应用可以使用剪贴板
  17.         return 剪贴板文本;    //返回字符串
  18.     }else{alert('无法打开剪贴板!');}
  19. }
复制代码

//在WPS中使用ExecuteExcel4Macro宏调用CALL,限制字符串类型的参数限长255字节。
//一个汉字占2个字节        //参数为字符串时需要引号包起,且类型必须用F。
let 文本字节长度=执行宏调用(`CALL("kernel32","lstrlenA","JF","${文本内容}")`);
//使可以处理双引号"而不提示错误
执行宏调用(`CALL("Kernel32","lstrcpynW","JJFJ",${内存地址},"${字符串.replace(/"/g,'""')}",${字长度})`);
let 文本字节长度=执行宏调用(`CALL("kernel32","lstrlenA","JF","${文本内容.replace(/["]/g,'x')}")`);
let 文本字节长度=执行宏调用(`CALL("kernel32","lstrlenA","JF","${文本内容.replace(/\"/g,'y')}")`);
let 文本字节长度=执行宏调用(`CALL("kernel32","lstrlenA","JF","${文本内容.replace(/"/g,'z')}")`);
//参数为(字符串所在的)内存地址时不限制
let 文本字节长度=执行宏调用(`CALL("Kernel32","lstrlenA","JJ",${内存地址})`);
 楼主| 发表于 2026-3-29 18:19:53 | 显示全部楼层
wanghan519 发表于 2026-3-29 16:24
最新版用ffi更加方便

是的,我的WPS比较老。方法适用于还没有ffi的版本。

评分

参与人数 1技术 +1 收起 理由
wanghan519 + 1 赞,通用性最强

查看全部评分

 楼主| 发表于 2026-3-29 15:13:05 | 显示全部楼层
本帖最后由 cutebe 于 2026-3-30 15:35 编辑

执行ExecuteExcel4Macro宏调用时,字符串参数要加双引号包起:"${字符串变量}"。这样当遇到字符串中有双引号时就会出错。
宏调用时按VBA类的语法将两个双引号解释成一个,所以写入时反过来替换即可:"${字符串.replace(/["]/g,'\"\"')}"

字符串写入内存时,单个双引号替换成两个。
  1. function 字符串内存测试二(){
  2.         let 字符串="内存中汉字占双字节\",\n数字1'字母a占一字节。";
  3.         console.log("原字符串:\n"+字符串);
  4.         const 执行宏调用=ExecuteExcel4Macro;
  5.         let 字符串字节数一=执行宏调用(`CALL("kernel32","lstrlenA","JF","${字符串.replace(/\"/g,'y')}")`);
  6.         const 内存句柄=执行宏调用(`CALL("Kernel32","GlobalAlloc","JJJ",${0x42},256)`);
  7.         if(内存句柄){        //申请内存,获取分配的地址
  8.                 var 内存地址=执行宏调用(`CALL("Kernel32","GlobalLock","JJ",${内存句柄})`);
  9.                 //字符串写入内存。宏调用ExecuteExcel4Macro中字符串参数按VBA方式处理,即两个双引号解释成一个。
  10.                 执行宏调用(`CALL("Kernel32","lstrcpynW","JJFJ",${内存地址},"${字符串.replace(/\"/g,'\"\"')}",${字符串字节数一})`);
  11.                 执行宏调用(`CALL("Kernel32","GlobalUnlock","JJ",${内存句柄})`);        //解锁
  12.         }
  13.         读取字符串=执行宏调用(`CALL("Kernel32","lstrcpynW","FFJJ","",${内存地址},256)`);
  14.         字符串字节数二=执行宏调用(`CALL("Kernel32","lstrlenA","JC","${读取字符串.replace(/["]/g,'x')}")`);
  15.         console.log("原字符串字节长度:"+字符串字节数一+" 字节\n读回字符串字节数:"+字符串字节数二+" 字节");
  16.         console.log("取出字符串:\n"+读取字符串);
  17. }
复制代码

读取内存时使用的是返回值(引用),无需要特别处理。

计算字符串字节数:一、宏调用法时把双引号替换成普通字符;二、直接用JS方案计算。
  1. function 字符串字节数测试(){
  2.         let 字符串="内存中汉字占双字节\",\n数字'字母占一字节。";
  3.         let 字符串字节数一=ExecuteExcel4Macro(`CALL("kernel32","lstrlenA","JF","${字符串.replace(/\"/g,'y')}")`);
  4.         let 字符串字节数二=字符串.replace(/[^\x00-\xff]/g,'xx').length;
  5.         console.log("计算结果:\n功能调用:"+字符串字节数一+" 字节\n直接计算:"+字符串字节数二+" 字节");
  6. }
复制代码
 楼主| 发表于 2026-3-29 15:21:38 | 显示全部楼层
本帖最后由 cutebe 于 2026-3-30 15:34 编辑

字符串到内存,内存到字符串。能处理引号,不限于255字节。
  1. function 字符串内存测试(){
  2.         let 字符串="字符串内存测试16\"字符串内存\"测试32\"字符串内存测试48字符串内存测试64";
  3.         let 新字符串='';
  4.         for(let i=0;i<4;i++){新字符串+=字符串+"\n";}
  5.         新字符串+=字符串;
  6.         const 内存地址=字符串到内存(新字符串);
  7.         let 取得字符串=内存取字符串(内存地址)+"\n结束!";
  8.         console.clear();console.log(取得字符串);
  9. }

  10. function 字符串到内存(字符串){        //返回内存地址
  11.         const 执行宏调用=ExecuteExcel4Macro;
  12.         let 分段文本=[],分段字节=[],总字节=0,分段长=127;        //可申请256字节内存,结束符1字节,双字节汉字最长127
  13.         for(let 起始=i=0;起始<字符串.length;起始+=分段长,i++){
  14.                 分段文本.push(字符串.slice(起始,起始+分段长));
  15.                 分段字节.push(分段文本[i].replace(/[^\x00-\xff]/g,'xx').length);        //一个汉字占两个字节
  16.                 总字节+=分段字节[i];
  17.         }        //分配内存        //分配GlobalAlloc对应销毁GlobalFree        //分配LocalAlloc对应销毁LocalFree
  18.         const 内存句柄=执行宏调用(`CALL("Kernel32","GlobalAlloc","JJJ",${0x42},${总字节+1})`);
  19.         if(内存句柄){        //内存分配成功,然后锁写解内存。        //锁定全局内存对象,会返回其内存地址
  20.                 const 内存地址=执行宏调用(`CALL("kernel32.dll","GlobalLock","JJ",${内存句柄})`);
  21.                 for(let 偏移字节=i=0;i<分段文本.length;i++){        //字符串(JS变量)分批复制到刚申请的内存中。分批是因为有限长
  22.                         执行宏调用(`CALL("Kernel32","lstrcpynW","JJFJ",${内存地址+偏移字节},"${分段文本[i].replace(/\"/g,'\"\"')}",${分段字节[i]})`);
  23.                         偏移字节+=分段字节[i];        //返回地址J。        //字符串中含双引号出错,因此替换成单引号
  24.                 }
  25.                 执行宏调用(`CALL("kernel32.dll","GlobalUnlock","JJ",${内存句柄})`);        //解锁全局内存对象,以释放资源
  26.                 return 内存地址;
  27.         }else{console.log("未能分配内存!");}
  28. }

  29. function 内存取字符串(内存地址){        //返回字符串
  30.         const 执行宏调用=ExecuteExcel4Macro,段长度=127;
  31.         if(内存地址){
  32.                 const 总字节=执行宏调用(`CALL("Kernel32","lstrlenA","JJ",${内存地址})`);        //内存文本字节数
  33.                 let 偏移字节=0,文本字符串=分段文本='';
  34.                 while(偏移字节<总字节){        //返回字符串F。        //返回超255字节时,也需要分段操作
  35.                         分段文本=执行宏调用(`CALL("Kernel32","lstrcpynW","FFJJ","",${内存地址+偏移字节},${段长度})`);
  36.                         //偏移字节+=执行宏调用(`CALL("Kernel32","lstrlenA","JC","${分段文本.replace(/["\n]/g,'x')}")`);
  37.                         偏移字节+=分段文本.replace(/[^\x00-\xff]/g,'xx').length;        //计算字符串字节数
  38.                         文本字符串+=分段文本;
  39.                 }        //解锁全局内存对象,使内存可以回收
  40.                 return 文本字符串;
  41.         }else{console.log("未找到内存地址!");}
  42. }
复制代码
发表于 2026-3-29 16:24:36 | 显示全部楼层
最新版用ffi更加方便
 楼主| 发表于 2026-3-29 19:49:46 | 显示全部楼层
本帖最后由 cutebe 于 2026-3-30 20:57 编辑

在WPS的JSA中,设置/获取剪贴板文本。分离出字符串与内存交换的函数:
  1. function 设置剪贴板文本(文本内容){        //参数为字符串
  2.         let 执行宏调用=ExecuteExcel4Macro;        //调用系统应用接口。简化书写
  3.         if (执行宏调用(`CALL("User32","OpenClipboard","AJ",0)`)){        //打开剪贴板
  4.                 if(执行宏调用(`CALL("User32","EmptyClipboard","A")`)){        //清空剪切板
  5.                         const 内存句柄=字符串到内存(文本内容);        //*****调用函数*****/
  6.                         if(内存句柄){        //console.log("内存块大小:"+执行宏调用(`CALL("Kernel32","LocalSize","JJ",${内存句柄})`));
  7.                                 let 剪贴板文本=执行宏调用(`CALL("User32","SetClipboardData","JJJ",1,${内存句柄})`);
  8.                                 执行宏调用(`CALL("Kernel32","LocalFree","JJ",${内存句柄})`);    //释放内存
  9.                         }else{console.log("未能分配内存!");}        //数据放入剪切板,参数为1(CF_TEXT)则返回文本
  10.                 }else{console.log('未能清空剪贴板!');}
  11.                 执行宏调用(`CALL("User32","CloseClipboard","A")`);        //关闭剪贴板,以释放控制权。
  12.         }else{alert('无法打开剪贴板!');}
  13. }

  14. function 获取剪贴板文本(){        //返回字符串
  15.         const 执行宏调用=ExecuteExcel4Macro;
  16.         if(执行宏调用(`CALL("User32","OpenClipboard","JJ",0)`)){        //打开剪贴板
  17.                 const 内存句柄=执行宏调用(`CALL("User32","GetClipboardData","JJ",1)`);        //获取剪贴板文本,返回句柄。
  18.                 if(内存句柄){
  19.                         var 剪贴板文本=内存取字符串(内存句柄);        //*****调用函数*****/
  20.                 }else{console.log("未能获取剪贴板数据的内存句柄!");}
  21.                 执行宏调用(`CALL("User32","CloseClipboard","J")`);        //关闭剪贴板,让其它应用可以使用剪贴板
  22.                 return 剪贴板文本;        //返回字符串
  23.         }else{alert('无法打开剪贴板!');}
  24. }

  25. function 字符串到内存(字符串){        //返回内存句柄
  26.         const 执行宏调用=ExecuteExcel4Macro;
  27.         let 分段文本=[],分段字节=[],总字节=0,段长度=127;        //可申请256字节内存,结束符1字节,汉字最长127
  28.         for(let 起始=i=0;起始<字符串.length;起始+=段长度,i++){
  29.                 分段文本.push(字符串.slice(起始,起始+段长度));
  30.                 分段字节.push(分段文本[i].replace(/[^\x00-\xff]/g,'xx').length);        //一个汉字占两个字节
  31.                 总字节+=分段字节[i];
  32.         }        //分配内存        //分配LocalAlloc对应销毁LocalFree        //分配GlobalAlloc对应销毁GlobalFree
  33.         const 内存句柄=执行宏调用(`CALL("Kernel32","LocalAlloc","JJJ",${0x42},${总字节+1})`);
  34.         if(内存句柄){        //内存分配成功,然后锁写解内存。        //锁定内存对象,会返回其内存地址
  35.                 const 内存地址=执行宏调用(`CALL("Kernel32","LocalLock","JJ",${内存句柄})`);
  36.                 for(let 偏移字节=i=0;i<分段文本.length;i++){        //字符串(JS变量)分批复制到刚申请的内存中。分批是因为有限长
  37.                         执行宏调用(`CALL("Kernel32","lstrcpynW","JJFJ",${内存地址+偏移字节},"${分段文本[i].replace(/\"/g,'\"\"')}",${分段字节[i]})`);
  38.                         偏移字节+=分段字节[i];        //返回地址J。        //字符串中含双引号出错,因此替换成单引号
  39.                 }
  40.                 执行宏调用(`CALL("Kernel32","LocalUnlock","JJ",${内存句柄})`);        //解锁内存对象,以释放资源
  41.                 return 内存句柄;
  42.         }else{console.log("未能分配内存!");}
  43. }

  44. function 内存取字符串(内存句柄){        //返回字符串
  45.         const 执行宏调用=ExecuteExcel4Macro,段长度=127;
  46.         const 内存地址=执行宏调用(`CALL("Kernel32","LocalLock","JJ",${内存句柄})`);        //锁定,获取内存地址
  47.         if(内存地址){
  48.                 const 总字节=执行宏调用(`CALL("Kernel32","lstrlenA","JJ",${内存地址})`);        //内存文本字节数
  49.                 let 偏移字节=0,文本字符串=分段文本='';
  50.                 while(偏移字节<总字节){        //返回字符串F。        //返回超255字节时,也需要分段操作
  51.                         分段文本=执行宏调用(`CALL("Kernel32","lstrcpynW","FFJJ","",${内存地址+偏移字节},${段长度})`);
  52.                         //偏移字节+=执行宏调用(`CALL("Kernel32","lstrlenA","JC","${分段文本.replace(/["\n]/g,'x')}")`);
  53.                         偏移字节+=分段文本.replace(/[^\x00-\xff]/g,'xx').length;        //计算字符串字节数
  54.                         文本字符串+=分段文本;
  55.                 }        //解锁内存对象,以释放资源
  56.                 执行宏调用(`CALL("Kernel32","LocalUnlock","JJ",${内存句柄})`);
  57.                 return 文本字符串;
  58.         }else{console.log("未找到内存地址!");}
  59. }
复制代码
 楼主| 发表于 7 天前 | 显示全部楼层
wanghan519 发表于 2026-3-29 16:24
最新版用ffi更加方便

学习了在EH的代码,非常感谢!
 楼主| 发表于 7 天前 | 显示全部楼层
计算字符串字节数的新方法,调用表格函数LENB。

原方法:
  1. 偏移字节+=分段文本.replace(/[^\x00-\xff]/g,'xx').length;    //计算字符串“分段文本”的字节数
复制代码

新方法:
  1. function 测试计算字符中字节数新方法(){
  2.         let 字符串="你好阿小明。\nABC";        //应该是16字节
  3.         let 字节数=ExecuteExcel4Macro(`LENB("${字符串}")`);        //表格JSA中计算字符串字节数
  4.         console.log("字符串的字节数:"+字节数);
  5. }
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|批处理之家 ( 渝ICP备10000708号 )

GMT+8, 2026-4-19 20:54

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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