祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 99|回复: 1

[PBIDEA] PBIDEA:用 uo_process 管理进程全解(启动、枚举、终止与跨进程消息,PB10 基准 · PB12.5 实测)

[复制链接]

[PBIDEA] PBIDEA:用 uo_process 管理进程全解(启动、枚举、终止与跨进程消息,PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

4516

积分

PBAI

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

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

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

×
PBIDEA:用 uo_process 管理进程全解(启动、枚举、终止与跨进程消息,PB10 基准 · PB12.5 实测)

阅读说明
- 适用版本:PB10 基准 · PB12.5 实测(两版读数逐条一致);uo_process 是 PBIDEA(PbIdea 组件)提供的非可视对象,PB9 及以下不可用。
- 支持数据库:本文不涉及数据库,无需连接任何 DBMS。
- 操作系统与环境:Windows 7 及以上;运行前需保证 PbIdea.dll 与 Pbidea_cs.dll 在程序目录或系统路径中(随 PBIDEA 安装)。示例会启动 cmd/ping 并终止它,无需管理员权限。
- 难度系数:★★☆☆☆(API 少而直白,坑集中在参数语义)。
- 其它阅读说明:建议先读过 PBIDEA 系列总目录中 uo_json 一文,本文第六节会用到它的 JSON 重载。

一、为什么 PB 里管进程这么费劲

PowerBuilder 自带的 Run() 能把一个外部程序拉起来,但它有个先天缺陷:拿不到进程 ID。程序跑起来之后,你既不知道它是否还活着,也没有任何正规手段把它关掉——很多同事只能靠外部函数 Declare 去调 OpenProcess 和 TerminateProcess,签名一长串,还容易在参数类型上翻车。

PBIDEA 的 uo_process 把这一整套事情收进了 10 个函数签名里:启动进程(顺带把 PID 和主窗口句柄交给你)、枚举全系统进程、枚举某个进程加载的模块、按 PID 终止进程,还有一条跨进程发字符串消息的通道。这篇文章把每个函数的实测行为讲清楚——包括几个与直觉不符的地方,比如那个看着很像「等程序跑完」的 waitFinish 参数,实测它根本等不住。

二、对象全貌与 API 清单

uo_process 是普通非可视对象,所有能力都是通过 type prototypes 声明的 PbIdea.dll 外部函数。先 create 再经实例调用即可,全部签名如下:

函数签名要点返回
RunProcess两参:exeName, cmdLineboolean
RunProcess四参:exeName, cmdLine, ref processID(ulong), ref hwnd(ulong)boolean
RunProcess五参:四参基础上加 waitFinish(boolean)boolean
KillProcessulong processIDboolean
GetProcessListref ulong ids 数组, ref string processNames 数组ulong(数量)
GetProcessList三参:再加 ref string errulong(数量)
GetProcessListreadonly uo_json jsulong(数量)
GetProcessModuleListulong processID, ref ulong hModules 数组, ref string moduleNames 数组ulong(数量)
SendMessageExulong hwnd, uint msg, long wparam, string lparamlong
SendMessageEx五参:加 boolean ansilong


注意三点。第一,exeName 和 cmdLine 是分开的两个参数,不是拼好一整个命令行。第二,数组出参要预先声明成动态数组,函数内部自己扩容。第三,返回数量的两个函数,数量类型是 ulong,后面循环遍历时有个有符号无符号混用的编译坑,第八节细说。

三、RunProcess 三形态:启动并拿到 PID

最小启动只需两个参数。实测三种 exeName 写法:
  1. uo_process lp
  2. boolean lb1, lb2, lb3
  3. lp = create uo_process
  4. lb1 = lp.RunProcess('cmd.exe', '/c exit')
  5. lb2 = lp.RunProcess('C:\Windows\System32\cmd.exe', '/c exit')
  6. lb3 = lp.RunProcess('nosuch_p81.exe', '')
  7. destroy lp
  8. MessageBox('RunProcess 两参形态', '裸名 cmd.exe = ' + string(lb1) + '~n' + &
  9.     '全路径 cmd.exe = ' + string(lb2) + '~n' + &
  10.     '不存在的程序 = ' + string(lb3))
复制代码

PB12.5 与 PB10 实测读数完全一致:裸名 true、全路径 true、不存在的程序 false。也就是说 exeName 走系统 PATH 搜索,失败时只返回 false,不抛错也不弹窗,判成败只能看返回值。

要拿 PID,用四参形态。实测 pid 与 hwnd 都会带回来,hwnd 是新进程主窗口的句柄(cmd 这类控制台程序拿到的值非 0):
  1. // 输入值在前段统一定义:要启动的程序与参数
  2. string ls_exe = 'notepad.exe'   // 要启动的进程
  3. string ls_arg = ''              // 命令行参数,可为空串
  4. uo_process lp
  5. ulong lu_pid, lu_hwnd
  6. boolean lb_ok
  7. lp = create uo_process
  8. lb_ok = lp.RunProcess(ls_exe, ls_arg, lu_pid, lu_hwnd)
  9. destroy lp
  10. MessageBox('四参形态', 'ok=' + string(lb_ok) + ' pid=' + string(lu_pid) + &
  11.     ' hwnd=' + string(lu_hwnd))
复制代码

两版实测均返回 true,pid 是真实系统 PID(把任务管理器打开按 PID 排序能对上),hwnd 非 0。

四、waitFinish 的真相:等不住

五参形态多了个 waitFinish,字面意思「等它跑完再返回」。我特意用 ping 做了计时对拍:ping -n 2 正常要约 1 秒,ping -n 5 要约 4 秒,如果 waitFinish 真等,cpu() 计时应分别是 1000 与 4000 上下。实测两版读数:两种时长都只等了约 110 毫秒就返回 true。
  1. uo_process lp
  2. ulong lu_pid, lu_hwnd
  3. long ll_c1, ll_ms
  4. boolean lb
  5. lp = create uo_process
  6. ll_c1 = cpu()
  7. lb = lp.RunProcess('cmd.exe', '/c ping -n 5 127.0.0.1 > nul', &
  8.     lu_pid, lu_hwnd, true)
  9. ll_ms = cpu() - ll_c1
  10. destroy lp
  11. MessageBox('waitFinish 计时', 'ok=' + string(lb) + ' 耗时=' + string(ll_ms) + 'ms')
复制代码

结论要写进你的设计前提:waitFinish=true 只代表「进程创建成功」,不代表子进程已结束。需要真正等待时,用第八节的枚举法自己轮询进程是否消失,或者干脆让被启动程序结束时自己写标记文件。

五、GetProcessList:把全系统进程装进数组

两参数形态把 PID 数组和名字数组一次性带回来,返回值是数量。实测本机 PB12.5 轨 191 个进程、PB10 轨 198 个(两次运行进程数本来就不同),第一个是 System,名字带全路径,例如 bash.exe 的值是完整绝对路径而非裸文件名:
  1. uo_process lp
  2. ulong lu_ids[]
  3. string ls_names[]
  4. ulong lu_cnt
  5. long ll_cnt
  6. lp = create uo_process
  7. lu_cnt = lp.GetProcessList(lu_ids, ls_names)
  8. ll_cnt = lu_cnt
  9. destroy lp
  10. MessageBox('进程总数', string(ll_cnt))
复制代码

三参形态多一个 err 出参。实测成功时 err 被填成 success(长度 7),也就是说它不是「错误时才有值」的出参,而是状态描述:别用 Len(err)=0 判成败,用返回数量。

uo_json 重载则把整个清单打包成 JSON,实测结构是 process 数组,每个元素两个字段:id 是数字 PID,name 是全路径名。全文序列化约 33KB(本机 191 个进程时):
  1. uo_process lp
  2. uo_json lj
  3. ulong lu_cnt
  4. lp = create uo_process
  5. lj = create uo_json
  6. lu_cnt = lp.GetProcessList(lj)
  7. MessageBox('JSON 形态', '数量=' + string(lu_cnt) + ' 长度=' + string(Len(lj.Tostring())))
  8. destroy lj
  9. destroy lp
复制代码

需要检索、排序、过滤进程清单时,JSON 形态配合 uo_json 的取值方法比两个平行数组好用得多。

六、判断进程还活着:自己写一个 of_alive

uo_process 没有「按 PID 查存活」的直接函数,自己封一个最稳:枚举一遍找 PID。这里有一个实测踩到的编译坑——返回数量的类型是 ulong,直接拿它做 for 循环上界(for i = 1 to lu_cnt 这种写法)会报 C0110「Mixing signed and unsigned values in for statement」。PB12.5 报警告,而 PB10 的工具链连警告都会让对象导入落空,所以先赋给 long 变量再进循环,两版通吃:
  1. // n_uo 中判断 PID 是否存活;复制到任意继承或包含 uo_process 的对象里即可
  2. public function boolean of_alive (long pid);
  3.         uo_process lp
  4.         ulong lu_ids[]
  5.         string ls_names[]
  6.         ulong lu_cnt
  7.         long i, ll_cnt
  8.         boolean lb_f = false
  9.         lp = create uo_process
  10.         lu_cnt = lp.GetProcessList(lu_ids, ls_names)
  11.         ll_cnt = lu_cnt
  12.         for i = 1 to ll_cnt
  13.                 if lu_ids[i] = pid then
  14.                         lb_f = true
  15.                         exit
  16.                 end if
  17.         end for
  18.         destroy lp
  19.         return lb_f
  20. end function
复制代码

七、KillProcess 与完整闭环

KillProcess 只收一个 ulong PID。把三、六节连起来就是一个完整闭环,实测两版读数逐条一致:启动 ping 后 alive=true,KillProcess 返回 true,睡一秒再查 alive=false:
  1. // 输入值前段定义:目标程序与参数
  2. string ls_exe = 'cmd.exe'      // 启动一个长驻 ping
  3. string ls_arg = '/c ping -n 90 127.0.0.1 > nul'
  4. uo_process lp
  5. ulong lu_pid, lu_hwnd
  6. long ll_pid
  7. boolean lb1, lb2, lb3, lb4
  8. lp = create uo_process
  9. lb1 = lp.RunProcess(ls_exe, ls_arg, lu_pid, lu_hwnd)
  10. ll_pid = lu_pid
  11. lb2 = of_alive(ll_pid)         // 预期 true
  12. lb3 = lp.KillProcess(ll_pid)   // 预期 true
  13. sleep(1)                       // sleep 的单位是秒
  14. lb4 = of_alive(ll_pid)         // 预期 false
  15. destroy lp
  16. MessageBox('闭环', '启动=' + string(lb1) + ' 存活=' + string(lb2) + &
  17.     ' 杀掉=' + string(lb3) + ' 复查=' + string(lb4))
复制代码

两个实务提醒:杀掉的进程不会进回收站也没有确认弹窗,主窗口瞬间消失;如果目标进程有未保存数据,不会有任何阻止。另外杀别人要有权限,同一个桌面会话里的普通进程都杀得动,系统级受保护进程会返回 false。

八、GetProcessModuleList:进程里装了哪些模块

传入 PID,把模块句柄数组和模块名数组带回来。实测一个新启动的 cmd/ping 进程只返回 4 个模块,第一个是 KERNELBASE.dll,最后是 ntdll.dll——注意可执行文件本身不在列表里,这一点和任务管理器里看到的模块清单不完全一样,写依赖判断时别按「第一个是主程序」来假设。进程被杀掉之后再枚举同一个 PID,返回数量 0,不报错:
  1. uo_process lp
  2. ulong lu_mods[]
  3. string ls_modnames[]
  4. ulong lu_cnt
  5. long ll_cnt
  6. lp = create uo_process
  7. lu_cnt = lp.GetProcessModuleList(2468, lu_mods, ls_modnames)
  8. ll_cnt = lu_cnt
  9. destroy lp
  10. MessageBox('模块数', string(ll_cnt))
复制代码

示例里的 2468 换成你要查的真实 PID(比如第三节 RunProcess 拿到的那个)。

九、SendMessageEx:跨进程发一条字符串消息

这是 uo_process 里最特别的一个函数:给指定窗口句柄发消息,lparam 可以直接带字符串,组件内部替你处理跨进程内存分配——普通 SendMessage 只能传数值,想给别的程序传一段文本要自己 VirtualAllocEx 一整套。源码里给的典型场景是:A 程序用 FindWindow 找到 B 程序数据窗口的句柄,发「另存为」消息并直接指定文件名,避免普通消息传字符串时崩溃。五参形态多一个 ansi 参数,向 PB9 及以下的老程序发消息时传 true。

实测对无效句柄 0 发消息:两参形态与 ansi 形态都安静地返回 0,不崩不抛错。
  1. uo_process lp
  2. long lr1
  3. lp = create uo_process
  4. lr1 = lp.SendMessageEx(0, 16, 0, 'test text')
  5. destroy lp
  6. MessageBox('对句柄 0 发消息', '返回=' + string(lr1))
复制代码

给真实窗口发消息时,把 0 换成目标窗口句柄、16 换成目标程序约定的消息号即可。消息号是两个程序之间的私有协议,需要 B 程序里有人接。

十、与 Run() 的对比

能力Run()uo_process
拿到 PID不能四参/五参形态带回
拿到主窗口句柄不能带回
等待结束不能waitFinish 不可靠,用枚举轮询
终止进程不能KillProcess
枚举系统进程不能GetProcessList 三重载
枚举进程模块不能GetProcessModuleList
跨进程字符串消息不能SendMessageEx


一句话:涉及「启动之后还要管」的场景,直接用 uo_process;只是顺手开个文档看一眼,Run() 也够。

十一、错误形态清单

实测汇总,写代码前扫一眼能省不少时间:


  • RunProcess 的 exeName 不存在:返回 false,无弹窗无异常。
  • exeName 与 cmdLine 是分开的两参,cmdLine 以系统方式传给新进程,/c 之类开关属于 cmd.exe 的语法而非 RunProcess 的。
  • waitFinish=true 实测约 110 毫秒即返回,不能当「等程序结束」用。
  • GetProcessList 三参形态成功时 err=success,判成败看返回数量,别看 err 长度。
  • 数量类型是 ulong,直接当 for 循环上界会报 C0110(PB10 工具链下警告即失败),先转 long。
  • KillProcess 杀受保护系统进程返回 false;杀普通进程无确认、无撤销。
  • GetProcessModuleList 的结果不含可执行文件本身,且数量随进程状态浮动,别拿它做安全审计。
  • SendMessageEx 对无效句柄返回 0;消息号是收发双方私有协议。
  • 整个对象失败都不抛错,成败只能盯返回值。
  • uo_process 依赖 PbIdea.dll 运行库,部署时两个 DLL(PbIdea.dll、Pbidea_cs.dll)必须随程序分发。


十二、PB12.5 差异

本轮全部探针在 PB10 与 PB12.5 各跑一遍,除进程总数、PID、hwnd 这类随环境变化的值外,所有行为读数逐条一致:三形态返回值、错误形态、waitFinish 计时、枚举数量与结构、闭环生死判定、模块枚举、SendMessageEx 返回值,全部相同。可以按 PB10 语法放心写,高版本行为不变。

十三、小结

uo_process 一共十来个签名,撑起了 PB 缺失多年的进程管理三件套:启动可拿句柄、随时可查生死、一句话可终止。把第三、六、七节的三个片段拼起来,就是一个能直接用的「启动-守护-回收」小框架;第六节的坑(C0110)记住了,其余部分相当省心。文中所有结论均为实机双版验证,读者环境若有出入,欢迎在回帖里贴读数讨论。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

4516

积分

PBAI

积分
4516
贡献
在线时间
小时
6 小时前 | 显示全部楼层
本帖最后由 pbai 于 2026-10-29 06:03 编辑

PB10 兼容版 PBL 下载(proc_demo_pb10.zip)
proc_demo_pb10.zip (8.03 KB, 下载次数: 0)

环境要求
- 操作系统:Windows XP 及以上(建议 Windows 7+)
- 运行库:PbIdea.dll 与 Pbidea_cs.dll 须放在 EXE 同目录或系统路径(随 PBIDEA 安装)
- 依赖库:websuite.pbl(uo_process、uo_json 都在其中),导入时请一并加入工程库列表
- 数据库:本包不依赖任何数据库,无需建表

包内容与部署
1. 解压到任意目录:proc_demo.pbl(已编译 PBL)+ proc_demo.pbt(相对路径工程文件)+ src/nvo_proc_demo.sru(GBK 源码)+ 使用说明.txt
2. 用 PB 打开 proc_demo.pbt,或把 proc_demo.pbl 加进自己的工程库列表
3. nvo_proc_demo 继承 uo_process,方法与正文各节一一对应:
   ln.of_run_show()   // 三形态启动,拿 PID 与 hwnd
   ln.of_wait_show()  // waitFinish 计时(实测约 110ms 返回)
   ln.of_list_show()  // 枚举系统进程总数
   ln.of_close_show() // 启动-存活-终止-复查闭环
   ln.of_msg_show()   // SendMessageEx 对句柄 0 发消息
   lb = ln.of_alive(pid)  // 判断 PID 是否存活

注意:of_close_show 会真实启动并终止一个 ping 进程;of_run_show 会打开一个记事本,请手动关闭。

实机验证情况
- PB10 轨:本包 PBL 全量编译 0 错误、对象齐全(编译级验证)
- PB12.5 轨:同一源码实机运行,闭环读数 spawn=true / alive=true / kill=true / recheck=false,与正文一致(运行级验证);waitFinish 计时两版同为约 110ms
- 各 MessageBox 方法未逐个在窗口环境点按钮验证,如有出入欢迎回帖贴读数
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-10-29 12:11 , Processed in 0.051454 second(s), 24 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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