如何修改Delphi的CaptureConsoleOutput过程以正确捕获Unicode控制台输出?
Delphi 11 修复控制台输出Unicode显示异常问题
问题根源
程序无法正确显示Unicode字符的核心原因:
- 默认调用
cmd.exe时使用ANSI编码输出,tree命令的分支符号属于OEM字符集,中文/俄文等非英文字符会因编码不匹配乱码 - 原
CaptureConsoleOutput过程使用ANSI版本WinAPI读取管道,未全程以宽字符处理数据
修改后的实现(线程安全版)
为实时且正确显示Unicode输出,我们使用Unicode版本WinAPI、添加cmd /U参数强制Unicode输出,并通过线程避免UI阻塞:
type TConsoleCaptureThread = class(TThread) private FCommand: string; FTargetMemo: TMemo; procedure AppendToMemo(const OutputLine: string); protected procedure Execute; override; public constructor Create(const CmdLine: string; TargetMemo: TMemo); end; constructor TConsoleCaptureThread.Create(const CmdLine: string; TargetMemo: TMemo); begin inherited Create(True); FCommand := CmdLine; FTargetMemo := TargetMemo; FreeOnTerminate := True; end; procedure TConsoleCaptureThread.AppendToMemo(const OutputLine: string); begin FTargetMemo.Lines.Add(OutputLine); end; procedure TConsoleCaptureThread.Execute; var StartupInfo: TStartupInfoW; ProcessInfo: TProcessInformation; PipeRead, PipeWrite: THandle; SecurityAttr: TSecurityAttributes; BytesRead: DWORD; WideBuffer: array[0..4095] of WideChar; OutputStr: UnicodeString; begin // 配置管道安全属性,允许句柄继承 FillChar(SecurityAttr, SizeOf(SecurityAttr), 0); SecurityAttr.nLength := SizeOf(SecurityAttr); SecurityAttr.bInheritHandle := True; SecurityAttr.lpSecurityDescriptor := nil; // 创建匿名管道用于捕获输出 if not CreatePipe(PipeRead, PipeWrite, @SecurityAttr, 0) then Exit; try // 初始化进程启动信息,指定标准输出为管道 FillChar(StartupInfo, SizeOf(StartupInfo), 0); StartupInfo.cb := SizeOf(StartupInfo); StartupInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; StartupInfo.hStdOutput := PipeWrite; StartupInfo.hStdError := PipeWrite; // 同时捕获错误输出 StartupInfo.wShowWindow := SW_HIDE; // 隐藏cmd窗口 // 启动cmd,使用/U参数强制Unicode输出,指定Unicode环境 if not CreateProcessW(nil, PWideChar('cmd.exe /U /C ' + FCommand), nil, nil, True, CREATE_UNICODE_ENVIRONMENT or NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then Exit; try // 关闭管道写端,确保进程输出完毕后管道触发EOF CloseHandle(PipeWrite); // 循环读取管道内容,直到进程结束或线程终止 while not Terminated and ReadFile(PipeRead, WideBuffer, SizeOf(WideBuffer), BytesRead, nil) and (BytesRead > 0) do begin // 将读取的宽字符转换为Delphi UnicodeString SetLength(OutputStr, BytesRead div SizeOf(WideChar)); Move(WideBuffer, OutputStr[1], BytesRead); // 同步更新UI,避免跨线程访问控件错误 Synchronize(procedure begin AppendToMemo(OutputStr); end); end; finally // 释放进程和线程句柄 CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); end; finally // 释放管道读端句柄 CloseHandle(PipeRead); end; end; // 调用示例(按钮点击事件) procedure TForm1.RunCommandBtnClick(Sender: TObject); begin CommandMemo.Clear; // 示例:捕获tree命令输出 TConsoleCaptureThread.Create('tree .', CommandMemo).Start; end;
关键修改说明
cmd /U参数:强制cmd以UTF-16 Unicode格式输出内容,确保tree分支符号、中文/俄文等字符编码正确- Unicode版本WinAPI:全程使用
CreatePipeW、CreateProcessW、TStartupInfoW等宽字符API,避免ANSI转码丢失数据 - 线程安全更新UI:通过
Synchronize方法跨线程更新Memo控件,避免UI阻塞和控件访问错误 - 正确的句柄管理:及时关闭不需要的管道和进程句柄,防止系统资源泄漏
内容的提问来源于stack exchange,提问作者Some1Else
相关产品推荐
相关产品推荐

