PowerBuilder 文件与目录操作全解:FileOpen 读写、复制移动与目录管理
PowerBuilder 文件与目录操作全解:FileOpen 读写、复制移动与目录管理阅读说明
- 适用版本:PowerBuilder 10 / 12.5(示例以 PB10 为基准编写,PB12.5 的差异单独列在第五节)
- 支持数据库:本文不涉及数据库,纯文件系统操作
- 操作系统与环境要求:Windows 7 及以上;只用 PowerScript 内置函数,不需要任何第三方运行库;需要准备一个当前用户可写的目录
- 难度系数:★★☆☆☆
- 其它阅读说明:示例会在你传入的根目录下建文件、建子目录,跑完自动清理;建议先准备一个空目录(例如 D:\pbfile_demo)。文中所有代码与输出值均已在 PB12.5(PBVM 12.5)实机跑通,PB10 版 PBL 也已编译通过。
一、这是什么:PB 里读写文件其实只有两套体系
PowerBuilder 操作本地文件,本质上就两件事:
[*]打开—读写—关闭(流式):FileOpen 拿到一个「文件号」,后面所有 FileRead / FileWrite / FileSeek 都靠这个号操作,用完 FileClose。灵活、能控制位置,但要自己管开关。
[*]一句话函数(高层):FileExists / FileDelete / FileCopy / FileMove / FileLength / FileEncoding 这些不需要打开文件号,直接给路径就干活;目录侧的 DirectoryExists / CreateDirectory / RemoveDirectory / GetCurrentDirectory / ChangeDirectory 同理。
绝大多数需求都是「高层函数做判断与搬运 + 流式函数做内容读写」,两者配合着用,代码最短也最稳。
二、函数全貌:一张清单表收全
类别函数作用返回值要点
打开关闭FileOpen(文件名 {,模式 {,访问 {,锁 {,写方式 {,编码}}}}})打开文件取文件号成功返回文件号,失败 -1
打开关闭FileClose(文件号)关闭1 成功 / -1 失败
读FileRead(文件号, 变量)读一行或一块字节数;EOF 返回 -100;行模式遇空行返回 0
读FileReadEx(文件号, blob 或 string {,长度})读整文件或指定长度long,EOF 也是 -100
写FileWrite(文件号, 变量)写一行或一块字节数 / -1;超过 32765 字节有上限
写FileWriteEx(文件号, blob 或 string {,长度})写大块数据long,适合大文件
定位FileSeek(文件号, 偏移, 起点)移动文件指针移动后的位置
查询FileExists(文件名)文件是否存在boolean
查询FileLength(文件名)文件字节数不存在时 -1
查询FileEncoding(文件名)判断文本编码EncodingANSI! / EncodingUTF8! / EncodingUTF16LE! / EncodingUTF16BE!
搬运FileCopy(源, 目标 {,是否覆盖})复制1 / -1(打不开源)/ -2(写不了目标)
搬运FileMove(源, 目标)移动(改名)1 / -1 / -2
删除FileDelete(文件名)删文件boolean
目录DirectoryExists / CreateDirectory / RemoveDirectory判断 / 建 / 删目录Create/Remove 返回 1 / -1
目录GetCurrentDirectory() / ChangeDirectory(目录)取/改当前目录Change 返回 1 / -1
目录GetFolder(标题, 目录)弹出选文件夹对话框1 / 0(取消)/ -1
对话框GetFileOpenName / GetFileSaveName打开/保存文件对话框1 / 0(取消)/ -1
还有两个「大文件专用」的兄弟:FileLength64 与 FileSeek64,处理超过 2GB 的文件时用它们。
三、最小可运行示例:一条链路跑完全部动作
下面这个对象把「建目录 → 写文本 → 两种读法对比 → 指针定位 → 二进制读写 → 复制 → 移动 → 清理」串成一条线,跑完把每一步的结果拼成字符串交回调用方。
使用方式:在测试窗口放一个按钮 cb_1,把这段代码粘到 clicked 事件里,直接运行即可看到报告。
// 变量先声明,再逐行赋值;ls_root 换成你自己机器上的可写目录
string ls_root // 演示根目录,所有演示文件都建在它下面
string ls_report // 接收对象返回的执行报告
integer li_rc // 返回码:1=全部通过
nvo_file_demo lnv_demo // 演示对象,注意必须 create,类名直调会报 Null object reference
ls_root = 'D:\pbfile_demo'
lnv_demo = create nvo_file_demo
li_rc = lnv_demo.of_run(ls_root, ls_report)
destroy lnv_demo
MessageBox('文件操作演示', '返回码=' + String(li_rc) + '~r~n~r~n' + ls_report)
nvo_file_demo 的完整源码(nvo_file_demo.sru,PB10 与 PB12.5 通用):
forward
global type nvo_file_demo from nonvisualobject
end type
end forward
global type nvo_file_demo from nonvisualobject
end type
global nvo_file_demo nvo_file_demo
forward prototypes
public function integer of_run (string as_root, ref string as_report)
end prototypes
public function integer of_run (string as_root, ref string as_report);
// 作用:把 建目录 -> 写文本 -> 读文本(两种读法对比) -> 指针定位 -> 二进制读写 -> 复制 -> 移动 -> 清理 跑一遍
// 参数:as_root (入参)演示根目录,请传一个当前用户可写的目录
// 参数:as_report(出参)每一步的执行结果,多行文本,调用方可直接 MessageBox 或写日志
// 返回:1 = 关键步骤全部通过;0 = 有步骤失败
stringls_dir // 演示根目录
stringls_bak // 演示子目录,用来验证 FileMove 的目标目录必须已存在
stringls_txt // 演示文本文件全路径
stringls_bin // 演示二进制文件全路径
stringls_copy // FileCopy 的目标文件
stringls_move // FileMove 的目标文件
stringls_line // 切行时取到的单行内容
stringls_all // TextMode 一次读入的整份文本
stringls_lines // 切行结果拼接出来的字符串
stringls_old // 进入演示目录之前的当前目录,用完必须还原
integer li_file // FileOpen 返回的文件号
integer li_rc // 各函数的返回值
integer li_lines // LineMode 逐行读到的有效行数
integer li_lines2 // TextMode 整读后自己切出来的行数
integer li_del // 成功删除的文件个数
long ll_len // FileLength / FileSeek 的返回值
long ll_wrote // FileWriteEx 实际写入的字节数
long ll_read // FileReadEx 实际读回的字节数
long ll_pos // CRLF 在整份文本中的位置
blob lb_out // 待写入的二进制数据
blob lb_in // 读回的二进制数据
// ===== 0. 输入值:演示根目录由调用方传入,方便放到任意可写目录 =====
ls_dir= as_root
ls_bak= ls_dir + '\bak'
ls_txt= ls_dir + '\demo.txt'
ls_bin= ls_dir + '\demo.bin'
ls_copy = ls_dir + '\demo_copy.txt'
ls_move = ls_bak + '\demo_move.txt'
as_report = ''
li_lines = 0
li_lines2 = 0
li_del = 0
// ===== 1. 目录:不存在就建 =====
if DirectoryExists(ls_dir) then
as_report = as_report + '1) DirectoryExists=true~r~n'
else
li_rc = CreateDirectory(ls_dir)
as_report = as_report + '1) CreateDirectory=' + String(li_rc) + '~r~n'
end if
// ===== 2. 当前目录:切进去以后一定要还原,否则目录被占着删不掉 =====
ls_old = GetCurrentDirectory()
li_rc = ChangeDirectory(ls_dir)
as_report = as_report + '2) ChangeDirectory=' + String(li_rc) + ' 当前目录=' + GetCurrentDirectory() + '~r~n'
li_rc = ChangeDirectory(ls_old)
// ===== 3. LineMode! + Write! + Replace! 写三行文本 =====
li_file = FileOpen(ls_txt, LineMode!, Write!, LockWrite!, Replace!)
if li_file < 0 then
as_report = as_report + '!! FileOpen 写方式打开失败~r~n'
return 0
end if
li_rc = FileWrite(li_file, '第一行:PowerBuilder 文件操作')
li_rc = FileWrite(li_file, '第二行:LineMode 按行读写')
li_rc = FileWrite(li_file, '第三行:中文一行')
FileClose(li_file)
// ===== 4. FileExists / FileLength =====
if FileExists(ls_txt) then
ll_len = FileLength(ls_txt)
as_report = as_report + '3) FileExists=true FileLength=' + String(ll_len) + '~r~n'
else
as_report = as_report + '!! 文件没写成功~r~n'
return 0
end if
// ===== 5. 反面演示:LineMode! + FileRead 逐行读,遇到中文会重复读回行尾 =====
li_file = FileOpen(ls_txt, LineMode!, Read!)
li_rc = FileRead(li_file, ls_line)
do while li_rc >= 0
if li_rc > 0 then
li_lines = li_lines + 1
end if
li_rc = FileRead(li_file, ls_line)
loop
FileClose(li_file)
as_report = as_report + '4) LineMode 逐行读 有效读次数=' + String(li_lines) + '(3 行文件,中文行会重复读)~r~n'
// ===== 6. 正确做法:TextMode! + FileReadEx 一次读入,再自己按 ~r~n 切行 =====
li_file = FileOpen(ls_txt, TextMode!, Read!)
ll_read = FileReadEx(li_file, ls_all)
FileClose(li_file)
ls_lines = ''
ll_pos = Pos(ls_all, '~r~n')
do while ll_pos > 0
ls_line = Left(ls_all, ll_pos - 1)
if Len(ls_line) > 0 then
li_lines2 = li_lines2 + 1
ls_lines = ls_lines + '[' + ls_line + ']'
end if
ls_all = Mid(ls_all, ll_pos + 2)
ll_pos = Pos(ls_all, '~r~n')
loop
as_report = as_report + '5) TextMode 整读 字节=' + String(ll_read) + ' 行数=' + String(li_lines2) + ' 内容=' + ls_lines + '~r~n'
// ===== 7. FileSeek 定位:跳回文件头再读第一行 =====
li_file = FileOpen(ls_txt, LineMode!, Read!)
ll_len = FileSeek(li_file, 0, FromBeginning!)
li_rc = FileRead(li_file, ls_line)
FileClose(li_file)
as_report = as_report + '6) FileSeek(0,FromBeginning!)=' + String(ll_len) + ' 第一行=' + ls_line + '~r~n'
// ===== 8. Blob 的编码差异:不带编码参数时按 UTF-16LE 转换,字节数会翻倍 =====
lb_out = Blob('PB 数据')
li_file = FileOpen(ls_bin, StreamMode!, Write!, LockWrite!, Replace!)
ll_wrote = FileWriteEx(li_file, lb_out)
FileClose(li_file)
as_report = as_report + '7) Blob(默认) 字节=' + String(Len(lb_out)) + ' Blob(EncodingANSI!) 字节=' + String(Len(Blob('PB 数据', EncodingANSI!))) + '~r~n'
// ===== 9. StreamMode! + FileReadEx 把二进制原样读回来 =====
li_file = FileOpen(ls_bin, StreamMode!, Read!)
ll_read = FileReadEx(li_file, lb_in)
FileClose(li_file)
if Len(lb_in) = Len(lb_out) then
as_report = as_report + '8) 二进制 写入=' + String(ll_wrote) + ' 读回=' + String(ll_read) + ' 长度一致=TRUE~r~n'
else
as_report = as_report + '8) 二进制 写入=' + String(ll_wrote) + ' 读回=' + String(ll_read) + ' 长度一致=FALSE~r~n'
end if
// ===== 10. FileCopy:第三个参数 true 表示允许覆盖同名目标 =====
li_rc = FileCopy(ls_txt, ls_copy, true)
if FileExists(ls_copy) then
as_report = as_report + '9) FileCopy=' + String(li_rc) + ' 副本长度=' + String(FileLength(ls_copy)) + '~r~n'
else
as_report = as_report + '9) FileCopy=' + String(li_rc) + ' 副本不存在~r~n'
end if
// ===== 11. 坑点:FileMove 的目标目录必须先存在,否则返回 -2 =====
li_rc = FileMove(ls_copy, ls_move)
as_report = as_report + '10) 目标目录未建时 FileMove=' + String(li_rc) + '~r~n'
li_rc = CreateDirectory(ls_bak)
li_rc = FileMove(ls_copy, ls_move)
if FileExists(ls_move) then
as_report = as_report + ' 建好目录后 FileMove=' + String(li_rc) + ' 目标已存在~r~n'
else
as_report = as_report + ' 建好目录后 FileMove=' + String(li_rc) + ' 目标不存在~r~n'
end if
// ===== 12. 坑点:RemoveDirectory 只能删空目录 =====
li_rc = RemoveDirectory(ls_dir)
as_report = as_report + '11) 非空目录 RemoveDirectory=' + String(li_rc) + '~r~n'
// ===== 13. 清理:先删文件,再删目录 =====
if FileDelete(ls_txt) then
li_del = li_del + 1
end if
if FileDelete(ls_bin) then
li_del = li_del + 1
end if
if FileDelete(ls_move) then
li_del = li_del + 1
end if
as_report = as_report + '12) 删除文件数=' + String(li_del) + '~r~n'
li_rc = RemoveDirectory(ls_bak)
as_report = as_report + ' RemoveDirectory(bak)=' + String(li_rc) + '~r~n'
li_rc = RemoveDirectory(ls_dir)
as_report = as_report + ' RemoveDirectory(root)=' + String(li_rc) + '~r~n'
return 1
end function
实机输出(PB12.5 PBVM 真跑出来的值,不是编的):
1) DirectoryExists=true
2) ChangeDirectory=1 当前目录=D:\pbfile_demo
3) FileExists=true FileLength=76
4) LineMode 逐行读 有效读次数=9(3 行文件,中文行会重复读)
5) TextMode 整读 字节=76 行数=3 内容=[第一行:PowerBuilder 文件操作][第二行:LineMode 按行读写][第三行:中文一行]
6) FileSeek(0,FromBeginning!)=0 第一行=第一行:PowerBuilder 文件操作
7) Blob(默认) 字节=10 Blob(EncodingANSI!) 字节=7
8) 二进制 写入=10 读回=10 长度一致=TRUE
9) FileCopy=1 副本长度=76
10) 目标目录未建时 FileMove=-2
建好目录后 FileMove=1 目标已存在
11) 非空目录 RemoveDirectory=-1
12) 删除文件数=3
RemoveDirectory(bak)=1
RemoveDirectory(root)=1
四、三种打开模式到底差在哪
FileOpen 的第 2~5 个参数决定了后面能怎么读写,这是最容易写错的地方:
参数取值含义什么时候用
filemodeLineMode!(默认)一行一行读写,以 CR/LF 为界纯文本、配置文件、CSV
filemodeStreamMode!按二进制块读写图片、压缩包、任意二进制
filemodeTextMode!按文本块读写整份文本一次读入
fileaccessRead!(默认)只读,文件不存在不新建读取
fileaccessWrite!只写,文件不存在自动新建写入
filelockLockReadWrite!(默认)独占单进程独占写
filelockShared!别人也能读写多进程共享读
writemodeAppend!(默认)追加到文件尾写日志
writemodeReplace!清空原内容重新生成文件
最容易踩的一条:writemode 默认竟然是 Append!。也就是说下面这两行的效果完全不同——
// 变量先声明;li_f1 会追加,li_f2 会覆盖
integer li_f1 // 追加模式的文件号
integer li_f2 // 覆盖模式的文件号
stringls_file // 目标文件
ls_file = 'D:\pbfile_demo\log.txt'
li_f1 = FileOpen(ls_file, LineMode!, Write!, LockWrite!) // 默认 Append!,内容越写越多
FileWrite(li_f1, '追加一行')
FileClose(li_f1)
li_f2 = FileOpen(ls_file, LineMode!, Write!, LockWrite!, Replace!) // 显式 Replace!,先清空再写
FileWrite(li_f2, '只剩这一行')
FileClose(li_f2)
MessageBox('写方式对比', 'li_f1=' + String(li_f1) + ' li_f2=' + String(li_f2) + ' 当前长度=' + String(FileLength(ls_file)))
另外记住一条铁律:FileOpen 返回 -1 才是失败。文件号是从 1 开始的正整数,别用 if li_file then 判断,也别以为 0 是合法文件号。
五、读文本的正确姿势(本文最重要的一个坑)
先说结论:中文(含双字节字符)的文本文件,别用 LineMode! + FileRead 逐行读。
实测对比(同一份 3 行中文文件,76 字节):
[*]纯英文文件:LineMode! + FileRead 表现正常,3 行就是 3 次读,CRLF 被整体吃掉,不会多出空读。
[*]中文文件:LineMode! + FileRead 每读完一整行后,文件指针没有推进整行的字节数,于是同一行的末尾内容会被再读一遍。实测 3 行文件读出了 9 次有效读,内容依次是 [第一行…][件操作][?] [第二行…][行读写][?] [第三行…][文一行][?]——行尾 3 个汉字被重复读回,还夹着 1 个残留字节和 1 次空读。
这个坑很隐蔽:代码逻辑完全正确,行数却对不上,而且只在有中文时才出现。
推荐写法:TextMode! 打开 + FileReadEx 一次读入字符串,再自己按 ~r~n 切行(见第三节代码的第 6 段)。实测 76 字节读入、切出 3 行、内容完整正确。
// 变量先声明;把整份文本读进来再切行,中文文件也不会错
integer li_file // 文件号
long ll_read // 读入字节数
long ll_pos // CRLF 的位置
integer li_cnt // 行数
stringls_all // 整份文本
stringls_one // 单行
stringls_out // 拼接结果
stringls_path // 文件路径
ls_path = 'D:\pbfile_demo\demo.txt'
li_file = FileOpen(ls_path, TextMode!, Read!)
ll_read = FileReadEx(li_file, ls_all)
FileClose(li_file)
li_cnt = 0
ls_out = ''
ll_pos = Pos(ls_all, '~r~n')
do while ll_pos > 0
ls_one = Left(ls_all, ll_pos - 1)
if Len(ls_one) > 0 then
li_cnt = li_cnt + 1
ls_out = ls_out + ls_one + ' / '
end if
ls_all = Mid(ls_all, ll_pos + 2)
ll_pos = Pos(ls_all, '~r~n')
loop
MessageBox('整读切行', '字节=' + String(ll_read) + ' 行数=' + String(li_cnt) + '~r~n' + ls_out)
如果文件特别大,不适合一次读进内存,就给 FileReadEx 加第三个参数分块读,循环到返回 -100 为止。
六、二进制与编码:Blob 默认不是 ANSI
很多人以为 Blob('中文') 得到的是 GBK 字节,其实不是。实测:Blob('PB 数据') 得到 10 字节(UTF-16LE,即 PB 内部 Unicode 表示),而 Blob('PB 数据', EncodingANSI!) 得到 7 字节。要写 ANSI/GBK 文本进文件,记得显式带 EncodingANSI!。
读写二进制固定搭配是 StreamMode! + FileWriteEx / FileReadEx,别用 FileWrite 写 blob 的大块数据——它是为兼容老代码保留的,返回 integer 且对超长数据有上限。
七、复制、移动、删除与目录:三个必踩的坑
[*]FileMove 的目标目录必须已存在,否则返回 -2。实测:目标目录没建时 FileMove = -2,CreateDirectory 建好之后再移动返回 1。
[*]RemoveDirectory 只能删空目录,目录里还有文件时返回 -1。清理顺序永远是:先 FileDelete 删文件,再从最深层目录往外删。
[*]ChangeDirectory 切进去之后要还原,否则被占用的目录删不掉。示例里用 ls_old = GetCurrentDirectory() 存下来,用完立刻切回。
搬运的独立小例子:
// 变量先声明;演示 FileCopy / FileMove 与"目标目录必须先建"
integer li_rc // 返回值
stringls_src // 源文件
stringls_dst // 复制目标
stringls_dir // 移动目标目录
stringls_report // 报告
ls_src = 'D:\pbfile_demo\src.txt'
ls_dst = 'D:\pbfile_demo\dst.txt'
ls_dir = 'D:\pbfile_demo\movedir'
ls_report = ''
li_rc = FileCopy(ls_src, ls_dst, true)
ls_report = ls_report + 'FileCopy=' + String(li_rc) + '~r~n'
li_rc = CreateDirectory(ls_dir)
ls_report = ls_report + 'CreateDirectory=' + String(li_rc) + '~r~n'
li_rc = FileMove(ls_dst, ls_dir + '\moved.txt')
ls_report = ls_report + 'FileMove=' + String(li_rc) + '~r~n'
MessageBox('文件搬运', ls_report)
八、PB12.5 与 PB10 的差异:FileOpen 的第六个参数
FileOpen 的第六个参数 encoding 是 PB11 起才有的,PB10 的帮助里 FileOpen 只有 5 个参数。也就是说下面这段只能在 PB11/12.x 上编译,PB10 会直接编译报错:
// 仅 PB11/12.x:FileOpen 第六个参数指定写出编码,并用 FileEncoding 回读校验
integerli_file // 文件号
integerli_rc // 写入返回值
encoding le_enc // 编码枚举,注意不能用 long 接,会报 C0008
string ls_utf8 // UTF-8 文件
string ls_ansi // ANSI 文件
string ls_report// 报告
ls_utf8 = 'D:\pbfile_demo\utf8.txt'
ls_ansi = 'D:\pbfile_demo\ansi.txt'
ls_report = ''
li_file = FileOpen(ls_utf8, TextMode!, Write!, LockWrite!, Replace!, EncodingUTF8!)
li_rc = FileWrite(li_file, '中文 UTF-8 编码演示')
FileClose(li_file)
li_file = FileOpen(ls_ansi, LineMode!, Write!, LockWrite!, Replace!, EncodingANSI!)
li_rc = FileWrite(li_file, '中文 ANSI 编码演示')
FileClose(li_file)
le_enc = FileEncoding(ls_utf8)
if le_enc = EncodingUTF8! then
ls_report = ls_report + 'utf8.txt=EncodingUTF8!~r~n'
end if
le_enc = FileEncoding(ls_ansi)
if le_enc = EncodingANSI! then
ls_report = ls_report + 'ansi.txt=EncodingANSI!~r~n'
end if
FileDelete(ls_utf8)
FileDelete(ls_ansi)
MessageBox('编码演示(PB11+)', ls_report)
实机输出:FileEncoding(utf8.txt)=EncodingUTF8!、FileEncoding(ansi.txt)=EncodingANSI!。
还有一条类型坑:FileEncoding 返回的是 Encoding 枚举,用 long 变量去接会报 C0008: Incompatible types in assignment: long, encoding,必须声明成 encoding 类型再和 EncodingUTF8! 比较。
九、与相近方案对比
需求推荐做法不推荐做法原因
读中文文本TextMode! + FileReadEx 整读后切行LineMode! + FileRead 逐行后者遇双字节字符会重复读回行尾
读超大文件StreamMode! + FileReadEx(号, blob, 长度) 循环一次 FileReadEx 全读内存爆掉
写日志LineMode!, Write!, LockWrite!, Append!每次 Replace! 重写覆盖掉历史
生成新文件Write! + Replace!先 FileDelete 再写多一次可能失败的调用
复制/移动FileCopy / FileMove自己读出来再写回去前者是系统级搬运,快且保留属性
判断存在FileExists / DirectoryExists用 FileLength > 0 猜空文件长度为 0 会被误判成不存在
取文件大小FileLength(>2GB 用 FileLength64)读一遍数字节慢
十、扩展点
[*]大文件分块读写:给 FileWriteEx / FileReadEx 传第三个参数 length,循环处理,配合 FileSeek 做断点续传。
[*]多进程协作:FileOpen 的 filelock 参数用 Shared! 允许他人读写,LockWrite! 允许他人读但自己独占写;写日志给多进程共享时按需选择。
[*]与 DataWindow 配合:dw_1.SaveAs()、dw_1.ImportFile() 内部也是这套文件体系,导出 CSV / Excel 时路径与编码规则完全一致。
[*]让用户选文件:GetFileOpenName(title, pathname, filename, ext, filter),filename 传字符串数组时对话框支持多选(本段依赖交互对话框,未在无人值守环境实测,仅静态核对)。
[*]选文件夹:GetFolder(标题, 目录),返回 1 成功、0 用户取消,比自己拼浏览对话框省事。
小结
[*]判断与搬运用高层函数(FileExists / FileCopy / FileMove / FileDelete),内容读写用流式函数(FileOpen / FileRead / FileWrite / FileClose)。
[*]FileOpen 失败返回 -1;writemode 默认是 Append!,要覆盖必须写 Replace!。
[*]中文文本别用 LineMode! + FileRead 逐行读,改 TextMode! + FileReadEx 整读切行。
[*]FileMove 的目标目录要先建,RemoveDirectory 只能删空目录,ChangeDirectory 用完要还原。
[*]Blob(字符串) 默认 UTF-16LE,要 ANSI 得显式 EncodingANSI!;FileOpen 的第六个 encoding 参数是 PB11+ 才有。
PB10 兼容版 PBL 已附上(文章《PowerBuilder 文件与目录操作全解》tid=90873 的配套交付)
__AID__
1) 适用版本
- 主目标 PowerBuilder 10(ORCA --pb-version 100 全量编译,0 错误);PB11 / 12.x 同样可用。
- 文中「FileOpen 第六个参数 encoding 写 UTF-8」是 PB11 才有的(PB10 的 FileOpen 只有 5 个参数),
因此 nvo_file_enc.sru 没有并入本包,需要写 UTF-8 文本的请在 PB11+ 上单独用。
2) 环境要求
- Windows 7 及以上;不需要数据库、不需要任何第三方 DLL(含 PbIdea.dll)。
- 需要一个当前用户可写的目录,建议先建一个空目录 D:\pbfile_demo 。
3) 操作细节步骤
1. 解压到任意目录,例如 D:\pb10_filedemo 。
2. PB10 里把包内的 filedemo.pbl 加进当前 Target 的 Library List(放最后即可,不依赖其它库)。
3. 做一次 Full Rebuild,Output 应为 0 错误。
4. 在任意窗口放一个按钮,clicked 事件里写:
string ls_root
string ls_report
integer li_rc
nvo_file_demo lnv_demo
ls_root = 'D:\pbfile_demo'
lnv_demo = create nvo_file_demo // 必须 create,类名直调会报 Null object reference
li_rc = lnv_demo.of_run(ls_root, ls_report)
destroy lnv_demo
MessageBox('文件操作演示', '返回码=' + String(li_rc) + '~r~n~r~n' + ls_report)
5. 运行点按钮,弹出每一步报告;示例跑完会把建出来的文件和目录自动清理掉。
4) 实机验证情况(如实标注)
- PB10:ORCA 全量编译 0 错误;pbl list 校验对象齐全(filedemo / nvo_file_copy / nvo_file_demo 都在库里)。
- PB12.5:同一份源码 import 0 错 + Full Rebuild 0 错 + 真实 PBVM 跑 pbtest PASS,
并且断言了关键输出:整读字节=76、切出行数=3、二进制长度一致=TRUE、RemoveDirectory(root)=1。
- 编码检查 bad=0(GBK + CRLF + 无 BOM);源码结构自检 0 错误 0 提醒。
- 未实测部分:PB10 的运行时行为只做了编译验证(本机 PB10 未跑 PBVM);
GetFileOpenName / GetFileSaveName / GetFolder 需要人工点对话框,未在无人值守环境实测,仅静态核对。
5) 本文最重要的一个坑(实测)
中文文本文件用 LineMode! + FileRead 逐行读会重复读回行尾:读完一整行后文件指针少推进 6 字节,
实测 3 行文件读出 9 次有效读,内容依次是 [整行][行尾 3 个汉字][空读];纯英文文件没有这现象。
正确做法:TextMode! + FileReadEx 一次读入,再按 ~r~n 自己切行(实测 76 字节 / 3 行 / 内容完整正确)。
其余坑(FileMove 目标目录必须先建否则 -2、RemoveDirectory 只能删空目录、
Blob 默认 UTF-16LE 不是 ANSI、writemode 默认是 Append! 不是 Replace!)在文里都有实测数据,不再重复。
页:
[1]