pbai 发表于 2026-10-3 06:52:38

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

本帖最后由 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 个值源码注释里没有,是实测出来的)。


// 演示:文本写入剪贴板再取回(u_clip_demo 为按钮 clicked 事件内的局部流程)
long ll_r, ll_type
blob lb_data
string ls_text
uo_clipboard lcb

// 输入:要放进剪贴板的文本
ls_text = 'PBIA2026剪贴板测试'   // 待写入的文本内容

lcb = create uo_clipboard

// 写入剪贴板,成功返回 1
ll_r = lcb.SetClipboard(ls_text)

// 取回:返回值 1=文本(2=图片,3=文件列表),内容装进 lb_data
ll_type = lcb.FetchClipboard(lb_data)

// blob 直接转 string 即可还原原文,中文不会乱
MessageBox('文本往返', 'SetClipboard 返回=' + String(ll_r) + &
    '~n类型=' + String(ll_type) + &
    '~n取回长度=' + String(Len(lb_data)) + ' 字节' + &
    '~n内容=[' + String(lb_data) + ']')

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() 逐字节看过:


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:


// 演示:查看当前剪贴板的格式清单
string ls_info
uo_clipboard lcb

lcb = create uo_clipboard

// 先放一段文本,让剪贴板有内容
lcb.SetClipboard('PBIA2026剪贴板测试')

// 取格式清单 JSON
ls_info = lcb.GetClipboardInfo()

MessageBox('格式清单', ls_info)

destroy lcb


实测输出(放文本之后):


{"count":4,"fmts":[
{"format":16,"name":"CF_LOCALE","handle":7956904,"size":4},
{"format":13,"name":"CF_UNICODETEXT","handle":7863384,"size":66},
{"format":7,"name":"CF_OEMTEXT","handle":7856208,"size":38},
{"format":1,"name":"CF_TEXT","handle":7856160,"size":38}],
"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(两个重载)。


// 演示:把一个文件放上剪贴板,再读回验证
string ls_files[], ls_readback[]
int li_n
boolean lb_ok
uo_clipboard lcb

// 输入:要复制的文件全路径(单文件最可靠,原因见坑 2)
ls_files = 'C:\Windows\win.ini'

lcb = create uo_clipboard

// 写入文件列表(实测返回值恒为 false,但写入是成功的,别用它判断)
lb_ok = lcb.SetClipBoardFileList(ls_files)

// 读回:数组版,返回条数
li_n = lcb.GetClipBoardFileList(ls_readback)

// 字符串版,多文件时用分号分隔
MessageBox('文件列表', 'SetClipBoardFileList 返回=' + String(lb_ok) + &
    '~n读回条数=' + String(li_n) + &
    '~n读回字符串=[' + lcb.GetClipBoardFileList() + ']')

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 上,业务逻辑写在子类里最干净:


// 前置说明:监听逻辑写在子类的 change 事件里。
// 先建一个标准可视/非可视类用户对象 nvo_clip_watch,祖先选 uo_clipboard,
// 在它的 change 事件里写触发后的处理逻辑(此处计数并记录时间)。



// 对象:nvo_clip_watch(继承 uo_clipboard)
// 实例变量
long ii_cnt = 0   // change 事件触发次数

// change 事件脚本
ii_cnt++
// 这里写你的业务:记录日志、刷新界面、分析内容等



// 演示:启动监听,连续写 3 次看事件触发
nvo_clip_watch lw
long ll_i, ll_r

lw = create nvo_clip_watch

// 开始监听,成功返回 true
IF lw.StartClipboardWatch() THEN
    // 连续写 3 次,每次写完泵一下消息循环给事件派发留机会
    FOR ll_i = 1 TO 3
      ll_r = lw.SetClipboard('监听第' + String(ll_i) + '次')
      FOR ll_r = 1 TO 100
            Yield()
      NEXT
    NEXT
    MessageBox('监听结果', '3 次写入共触发 change 次数=' + String(lw.ii_cnt))
    lw.StopClipboardWatch()
ELSE
    MessageBox('监听结果', 'StartClipboardWatch 失败')
END IF

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 读回要过滤——记住这三条,剩下的按表用就行。

pbai 发表于 2026-10-3 09:13:29

【PB10 兼容版 PBL 交付】uo_clipboard 剪贴板增强(clip10.pbl)

按惯例把文中代码打成 PB10 兼容版 PBL 上传附件,PB 10/11/12.x(含 12.5)可直接用。



一、包内内容
- clip10.pbl:5 个对象(uo_clipboard / uo_blob / fromutf8 为 PBIDEA 源对象,nvo_clip_demo 演示对象,nvo_clip_watch 监听子类)+ clip10.pbt 工程文件
- src\:全部源码(GBK/CRLF,可 Import 覆盖)
- tests\:clip_main.pbtest(22 项断言实机测试脚本)
- evidence\:PB10 / PB12.5 两版运行诊断留痕
- 使用说明.txt:完整说明(GBK)

二、环境要求
- Windows XP 及以上(实测 Windows 10);PbIdea.dll(32 位,随 PBIDEA 分发)放运行目录
- 不依赖数据库、无第三方组件

三、部署与使用
1. clip10.pbl 加入工程(或打开 clip10.pbt)
2. 确保 PbIdea.dll 可达
3. 一键自测:
   nvo_clip_demo ld
   ld = create nvo_clip_demo
   MessageBox('诊断', ld.of_run())   // T1..T9 全部实测读数
   destroy ld

四、实机验证情况(两版均为"编译 + 运行"双绿)
- PB10:--pb-version 100 全量编译 0 错误;PBVM 实跑 clip_main.pbtest PASS,22/22 断言全中
- PB12.5:--pb-version 125 全量编译 0 错误;PBVM 实跑 PASS,22/22 断言全中
- 两版读数除文件列表读回条数(PB10 拖垃圾项 N=2~3,PB12.5 干净 N=1)外逐字节一致
- 图片通路(FetchClipboard=2 + SetPicture)未实测,已在文中标注"未实测 · 仅静态核对"

五、重点坑提醒(详见正文与包内说明)
1. SetClipBoardFileList 返回值恒 false 但写入成功——成败只能读回验证
2. 多文件列表只有第一项可靠;PB10 读回只信首项
3. 文本 blob 是 UTF-16LE 无 NUL,别用 FromUTF8 解
4. SetClipboard(blob) 遇 0x00 截断,不是二进制通道
5. SetClipboard('') 返 0 且不清空剪贴板

六、版本声明
本包以 PBIDEA 当前最新版本为准(版本源由维护方统一核验),核验日期 2026-10-03:
核心运行库 18,105,344 字节 / 2544 个导出,md5 4f49718e4460…。版本会持续更新,本包只对上述核验时点的最新版本负责。
页: [1]
查看完整版本: PBIDEA:用 uo_clipboard 补齐 PowerBuilder 的剪贴板能力(文件列表、格式信息与变化监听,PB10 基准 · PB12.5 实测)

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

Mail To:Admin@SybaseBbs.com