祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 2|回复: 0

[PBIDEA] PBIDEA:用 uo_clipboard 补齐 PowerBuilder 的剪贴板能力(文件列表、格式信息与变化监听,PB10 基准 · PB12.5 实测)

[复制链接]

[PBIDEA] PBIDEA:用 uo_clipboard 补齐 PowerBuilder 的剪贴板能力(文件列表、格式信息与变化监听,PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

3064

积分

PBAI

积分
3064
贡献
在线时间
小时
半小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
本帖最后由 pbai 于 2026-10-3 06:57 编辑

PBIDEA:用 uo_clipboard 补齐 PowerBuilder 的剪贴板能力(文件列表、格式信息与变化监听,PB10 基准 · PB12.5 实测)

阅读说明
1. 适用版本:基准 PB10,兼容 PB 12.5(PBIDEA 支持 PB 8 及以上)
2. 支持数据库:本文不涉及数据库
3. 操作系统与环境要求:Windows XP 及以上(本文实测环境 Windows 10),工程中需引用 websuite.pbl 并分发 PbIdea.dll(32 位)
4. 难度系数:★★☆☆☆
5. 其它阅读说明:会用到 PBIDEA 的 uo_blob 做 hex 对照,如不熟悉可先读《PBIDEA:用 uo_blob 补齐 PowerBuilder 的二进制处理短板》;文中所有读数均为本机实机双轨(PB10 + PB12.5)实测值
【版本声明】
- 本文以 PBIDEA 当前最新版本为准(版本源由维护方统一核验),并对照随版发布的源码整理。
- 核验日期:2026-10-03;当次核验的 PBIDEA 核心运行库:18,105,344 字节 / 2544 个导出,md5 4f49718e4460…。
- 版本识别口径:逐项解析 PE 导出表(文件体积、目录名都不能作为判据)。
- 适用平台:Windows XP 及以上(XP / 7 / 10 / 11 均可);PowerBuilder 8 及以上(PB 8/9/10/10.5/11.x/12.x 及以上通用)。本文的实机验证环境为 Windows 10 + PB 10 与 PB 12.5。
- PBIDEA 版本会持续更新,本文只对上述核验时点的最新版本负责;后续若行为有变,以新版为准。

一、原生 Clipboard() 只能做一半

PowerBuilder 自带一个全局函数 Clipboard(),但它只能干两件事:把一段文本放进剪贴板,或者把剪贴板里的文本读出来。你要是想让用户在资源管理器里直接"粘贴文件",或者想知道剪贴板里现在到底有哪些格式、有没有图片,原生函数就无能为力了。

PBIDEA 的 uo_clipboard 把这半边天补齐了:文本、文件列表(CF_HDROP)、格式清单、指定格式的裸数据、剪贴板变化监听,一共 10 个方法加 1 个 change 事件,全部走 PbIdea.dll。这篇文章把每个成员的实测行为过一遍——包括好几个源码注释里没写、只有跑起来才能发现的坑。

先给一个全貌表,后文逐个展开:

成员签名作用
SetClipboardSetClipboard(text) 返回 long放文本(成功返 1)
SetClipboardSetClipboard(textOrFilename, isFilename) 返回 long放文本或文件名
SetClipboardSetClipboard(data) 返回 long放 blob(按 ANSI 文本解释,见坑 8)
FetchClipboardFetchClipboard(ref blob data) 返回 long取回内容:1=文本 2=图片 3=文件列表
GetClipBoardFileListGetClipBoardFileList() 返回 string文件列表(分号分隔)
GetClipBoardFileListGetClipBoardFileList(ref string files[]) 返回 int文件列表装进数组,返回条数
SetClipBoardFileListSetClipBoardFileList(files[]) 返回 boolean把文件列表放上剪贴板(返回值不可信,见坑 1)
GetClipboardInfoGetClipboardInfo() 返回 stringJSON 格式清单
GetClipboardDataGetClipboardData(fmt) 返回 blob指定格式的裸数据
StartClipboardWatchStartClipboardWatch() 返回 boolean开始监听剪贴板变化
StopClipboardWatchStopClipboardWatch() 返回 boolean停止监听
change 事件change()监听期间剪贴板有变化时触发


二、文本往返:SetClipboard 与 FetchClipboard

最常用的路径。写入用 SetClipboard(string),读出用 FetchClipboard(ref blob)——注意它返回的不是字符串而是 blob,返回值本身表示类型:1 是文本,2 是图片,3 是文件列表(第 3 个值源码注释里没有,是实测出来的)。
  1. // 演示:文本写入剪贴板再取回(u_clip_demo 为按钮 clicked 事件内的局部流程)
  2. long ll_r, ll_type
  3. blob lb_data
  4. string ls_text
  5. uo_clipboard lcb
  6. // 输入:要放进剪贴板的文本
  7. ls_text = 'PBIA2026剪贴板测试'   // 待写入的文本内容
  8. lcb = create uo_clipboard
  9. // 写入剪贴板,成功返回 1
  10. ll_r = lcb.SetClipboard(ls_text)
  11. // 取回:返回值 1=文本(2=图片,3=文件列表),内容装进 lb_data
  12. ll_type = lcb.FetchClipboard(lb_data)
  13. // blob 直接转 string 即可还原原文,中文不会乱
  14. MessageBox('文本往返', 'SetClipboard 返回=' + String(ll_r) + &
  15.     '~n类型=' + String(ll_type) + &
  16.     '~n取回长度=' + String(Len(lb_data)) + ' 字节' + &
  17.     '~n内容=[' + String(lb_data) + ']')
  18. destroy lcb
复制代码

实测读数(PB10 与 PB12.5 完全一致):SetClipboard 返回 1;FetchClipboard 返回 1;blob 长度 26 字节;String(lb_data) 直接得到原文,中文不乱。

26 这个数字值得多说一句。'PBIA2026剪贴板测试' 是 13 个字符,13 × 2 = 26——FetchClipboard 给出的文本 blob 是 UTF-16LE 编码、不带 NUL 结尾。我用 uo_blob 的 hex() 逐字节看过:
  1. 500042004900410032003000320036006a52348d7f674b6dd58b
复制代码

每两个字节一个字符,'PBIA2026' 的 ASCII 后面跟着 6a52(剪)348d(贴)7f67(板)4b6d(测)58d5+6d58(试,U+8BD5 的小端是 d58b)……精确对应。PB10 和 PB12.5 的 string 内部都是 UTF-16,所以 String(blob) 一步到位还原,不需要 FromUTF8 之类的转码函数。

顺手记一个反面教材:如果你拿这个 blob 去 FromUTF8,只会得到一个字符 'U'——因为 FromUTF8 按字节读,读到第二个字符的低字节 0x00 就停了。编码判断错了,工具越准死得越快。

三、格式清单 GetClipboardInfo 与裸数据 GetClipboardData

想知道剪贴板里现在有什么,用 GetClipboardInfo(),它返回一段 JSON:
  1. // 演示:查看当前剪贴板的格式清单
  2. string ls_info
  3. uo_clipboard lcb
  4. lcb = create uo_clipboard
  5. // 先放一段文本,让剪贴板有内容
  6. lcb.SetClipboard('PBIA2026剪贴板测试')
  7. // 取格式清单 JSON
  8. ls_info = lcb.GetClipboardInfo()
  9. MessageBox('格式清单', ls_info)
  10. destroy lcb
复制代码

实测输出(放文本之后):
  1. {"count":4,"fmts":[
  2. {"format":16,"name":"CF_LOCALE","handle":7956904,"size":4},
  3. {"format":13,"name":"CF_UNICODETEXT","handle":7863384,"size":66},
  4. {"format":7,"name":"CF_OEMTEXT","handle":7856208,"size":38},
  5. {"format":1,"name":"CF_TEXT","handle":7856160,"size":38}],
  6. "priority":16}
复制代码

Windows 放文本时会同时挂好几个格式,程序按优先级取用。这里的 format 数字就是 Win32 的 CF_ 常量,常用的几个列一下,用 GetClipboardData 取裸数据时按号取:

常量值含义
CF_TEXT1ANSI 文本
CF_OEMTEXT7OEM 字符文本
CF_DIB8设备无关位图
CF_UNICODETEXT13UTF-16 文本
CF_ENHMETAFILE14增强图元文件
CF_HDROP15文件列表


GetClipboardData(13) 拿 UTF-16 文本的裸数据时,实测长度是 66 字节,而 FetchClipboard 只有 26——GetClipboardData 返回的是整个 GlobalAlloc 分配块,尾部带着一串 0 填充;CF_TEXT 拿到 38 字节,内容是 GBK 编码再加填充。要精确内容用 FetchClipboard,要看原始格式(比如判断文件列表的结构)才用 GetClipboardData。还有一点:向一个不存在的格式要数据,比如剪贴板里只有文本时 GetClipboardData(15),返回一个长度为 0 的空 blob,不报错——这个行为可以放心用。

四、文件列表:让用户能"粘贴文件"

文件列表走的是 CF_HDROP 格式,也就是资源管理器"复制文件"用的那个。写用 SetClipBoardFileList,读用 GetClipBoardFileList(两个重载)。
  1. // 演示:把一个文件放上剪贴板,再读回验证
  2. string ls_files[], ls_readback[]
  3. int li_n
  4. boolean lb_ok
  5. uo_clipboard lcb
  6. // 输入:要复制的文件全路径(单文件最可靠,原因见坑 2)
  7. ls_files[1] = 'C:\Windows\win.ini'
  8. lcb = create uo_clipboard
  9. // 写入文件列表(实测返回值恒为 false,但写入是成功的,别用它判断)
  10. lb_ok = lcb.SetClipBoardFileList(ls_files)
  11. // 读回:数组版,返回条数
  12. li_n = lcb.GetClipBoardFileList(ls_readback)
  13. // 字符串版,多文件时用分号分隔
  14. MessageBox('文件列表', 'SetClipBoardFileList 返回=' + String(lb_ok) + &
  15.     '~n读回条数=' + String(li_n) + &
  16.     '~n读回字符串=[' + lcb.GetClipBoardFileList() + ']')
  17. destroy lcb
复制代码

PB12.5 实测:lb_ok=false 但写入成功,读回条数 1,字符串正好是 'C:\Windows\win.ini'。此时 FetchClipboard 返回 3(文件列表),不是 1 也不是 2。

这里集中说一下文件列表的三个坑,都是实机撞出来的:

坑 1:返回值不可信。 SetClipBoardFileList 声明返回 boolean,但两版实测写入成功也返回 false。要判断成败,写完读回来对一下首项,别信返回值。

坑 2:多文件只有第一项可靠。 放两个文件进去读回,PB12.5 只回来 1 条;换三个文件、固定数组和变长数组各试一遍,读回条目数对不上、路径内容错乱。想给用户一批文件,要么逐次单文件,要么打个 zip 再放一个文件,别指望多元素列表。

坑 3:PB10 读回会拖垃圾项。 同样的单文件代码,PB10 下读回条数是 2 到 3,首项正确,后面跟着的是进程内存里的垃圾(实测拖出过 'LL' 和加载模块名 FONTSUB.DLL 这种东西)。PB12.5 下读回是干净的 1 条。所以读回之后只信你写入的那一项,或者按已知路径做白名单校验,条数本身不要当真。

裸数据层面我也看过 CF_HDROP:标准 DROPFILES 头 20 字节 + 宽字符路径 + 双 NUL 结束。不过它头部 fWide 标志与数据形态不一致,跨程序读这个格式是否正常没验证过,实际业务建议只用 GetClipBoardFileList 读回,别自己去啃裸块。

五、变化监听:StartClipboardWatch 与 change 事件

uo_clipboard 内置监听:StartClipboardWatch() 之后,剪贴板一有变化就触发 change 事件。change 定义在 uo_clipboard 上,业务逻辑写在子类里最干净:
  1. // 前置说明:监听逻辑写在子类的 change 事件里。
  2. // 先建一个标准可视/非可视类用户对象 nvo_clip_watch,祖先选 uo_clipboard,
  3. // 在它的 change 事件里写触发后的处理逻辑(此处计数并记录时间)。
复制代码
  1. // 对象:nvo_clip_watch(继承 uo_clipboard)
  2. // 实例变量
  3. long ii_cnt = 0   // change 事件触发次数
  4. // change 事件脚本
  5. ii_cnt++
  6. // 这里写你的业务:记录日志、刷新界面、分析内容等
复制代码
  1. // 演示:启动监听,连续写 3 次看事件触发
  2. nvo_clip_watch lw
  3. long ll_i, ll_r
  4. lw = create nvo_clip_watch
  5. // 开始监听,成功返回 true
  6. IF lw.StartClipboardWatch() THEN
  7.     // 连续写 3 次,每次写完泵一下消息循环给事件派发留机会
  8.     FOR ll_i = 1 TO 3
  9.         ll_r = lw.SetClipboard('监听第' + String(ll_i) + '次')
  10.         FOR ll_r = 1 TO 100
  11.             Yield()
  12.         NEXT
  13.     NEXT
  14.     MessageBox('监听结果', '3 次写入共触发 change 次数=' + String(lw.ii_cnt))
  15.     lw.StopClipboardWatch()
  16. ELSE
  17.     MessageBox('监听结果', 'StartClipboardWatch 失败')
  18. END IF
  19. destroy lw
复制代码

实测两版一致:StartClipboardWatch 返回 true,写 3 次触发 3 次 change,1:1 不丢不并;StopClipboardWatch 返回 true。两点说明:一是事件派发依赖消息循环被泵,如果你的长任务里不 Yield,事件会攒着等到循环空下来才来;二是 uo_clipboard 的 destructor 会自动 StopClipboardWatch,忘了手动停也不会漏钩子,但监听期间对象必须活着,destroy 了监听就停了。

至于源码注释里说的"返回 2 时 data 里是一张图的内容,可以 p_1.SetPicture(data)"这条图片通路——需要真实图片进剪贴板(比如别的程序里复制截图),headless 测试环境构造不出来,本段未实测、仅静态核对,读者用到时以 SetPicture 实际效果为准。

六、边界行为:空串、blob 重载与"没有清空"

两个边界实测:

空串写入是失败而不是清空。 SetClipboard('') 返回 0,且剪贴板保持原内容不动(实测写空串前是 '监听第3次',写后取回还是它)。PBIDEA 没有暴露清空剪贴板的方法,要"清"只能放一个占位内容盖掉。

blob 重载不是二进制通道。 SetClipboard(blob) 这个重载会把 blob 按 ANSI 文本解释、遇到 0x00 截断。实测放一个 'BIN数据' 的 blob 进去(首字节 0x42 即 'B',第二字节是 0x00),取回来只剩一个字符 'B'。真想放二进制数据,先转 hex 或 base64 字符串再放,两边约定好解码。

七、与原生方案对比

能力原生 Clipboard()uo_clipboard
写/读文本支持(只此一项)支持,且 blob 形态带类型标记
文件列表(可粘贴文件)不支持支持(单文件可靠)
格式清单不支持GetClipboardInfo JSON
指定格式裸数据不支持GetClipboardData
变化监听不支持Start/Stop + change 事件
依赖无PbIdea.dll


简单说:只倒腾文本,原生函数最省事;一旦涉及文件、格式、监听,就得上 uo_clipboard。

八、坑清单(全部实测)


  • SetClipBoardFileList 返回值恒 false 但写入成功——判断成败只能读回验证。
  • 多文件列表只有第一项可靠,读回条目数与内容错乱(固定数组、变长数组、2 个与 3 个文件都复现)。
  • PB10 下文件列表读回拖垃圾项(实测 N=3、N=2),PB12.5 干净(N=1)——读回后只信首项或做白名单校验。
  • FetchClipboard 返回 3 = 文件列表,源码注释只写了 1 和 2。
  • 文本 blob 是 UTF-16LE 无 NUL,26 字节 = 13 字符;别用 FromUTF8 解它(会在 0x00 处截断)。
  • GetClipboardData 返回整块分配含 0 填充(66 字节),FetchClipboard 才是精确内容。
  • SetClipboard('') 返回 0 且不清空剪贴板,旧内容原样保留;组件没有清空 API。
  • SetClipboard(blob) 按 ANSI 文本解释遇 0x00 截断(7 字节 blob 只出 'B'),不是二进制通道。
  • change 事件派发依赖消息循环被泵,长任务中要留 Yield;监听对象 destroy 前最好手动 Stop(destructor 会兜底)。
  • 向未占用的格式要数据返回空 blob 不报错,可以放心探测。


九、实机验证情况

文中代码在本机做了 PB10 与 PB12.5 双轨实机验证,测试对象 nvo_clip_demo + 子类 nvo_clip_watch + uo_blob 对照,共 22 项断言:


  • PB10:--pb-version 100 全量编译 0 错误;PBVM 运行 PASS,22/22 断言全中。
  • PB12.5:--pb-version 125 全量编译 0 错误;PBVM 运行 PASS,22/22 断言全中。
  • 两版输出串逐项比对:除文件列表读回条数(T4_N:125=1,100=3;T5_N:125=1,100=2)外,其余含文本 blob 的 hex 逐字节完全一致。
  • check_pb125.py 0 错误(1 条提醒为帮助知识库未收录 uo_clipboard 的 change 事件,已对照 PBIDEA 导出源码核实为真实事件);check_enc.py 编码全 OK,bad=0。
  • 图片通路(FetchClipboard 返 2 + SetPicture)未实测,见第五节标注。


十、PB12.5 差异

逐项对照后,两版行为差异只有一处:


  • 文件列表读回条数:PB12.5 读回干净(写入 N 项……实测单文件读回 1 条);PB10 读回拖垃圾项(单文件读回 2~3 条,尾部是内存垃圾)。写入侧(HDROP 格式确实在板上、首项正确)两版一致。


其余成员——SetClipboard 返回值、FetchClipboard 类型码与 blob 编码、GetClipboardInfo JSON 结构、GetClipboardData 填充行为、监听事件计数、空串与 blob 重载边界——PB12.5 与 PB10 行为一致,未发现差异。

十一、小结

uo_clipboard 是那种"平时想不起来,用时发现原生根本不行"的组件。文本往返、格式探测、文件列表、变化监听四条通路,本机双轨都跑通了,读数也稳。真正要留神的是文件列表:返回值别信、多文件别堆、PB10 读回要过滤——记住这三条,剩下的按表用就行。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-10-3 07:40 , Processed in 0.024560 second(s), 8 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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