优秀的编程知识分享平台

网站首页 > 技术文章 正文

「3.Lazarus执行外部程序」3.TProcess

nanyue 2024-12-16 15:10:17 技术文章 12 ℃

3.Lazarus执行外部程序之TProcess

可以使用 TProcess 来启动外部程序。使用 TProcess 的一些好处:

  • 平台独立
  • 从stdout读和写到stdin的能力
  • 能做到等待一个命令完成或当你的程序移动时让它运行

重要注释:

  • TProcess 不是一个 terminal(控制台)/shell,不能直接执行 scripts(脚本),或使用运算符像"|", ">","<", "&" 等等重定向输出,使用 pascal 的 TProcess 获得相同的结果 是可能的,下面有一些示例。
  • 很可能在Linux/Unix上必须指定完整的路径到可执行文件。例如:'/bin/cp' 代替 'cp' 。如果程序是在标准的PATH(路径)上,那么可以从 LCL 的单元 FileUtil,使用函数 FindDefaultExecutablePath。
  • 在Windows上,如果该命令是在 PATH 路径中,你不需要指定完整的路径。

3.1 TProcess

TProcess是一个组件,可用于启动和控制其他进程(程序/二进制文件)。它包含许多控制进程如何启动的选项。其中许多是特定于 Win32 的,对其他平台没有影响,因此应谨慎使用。

使用此组件的最简单方法是创建一个实例,将 Executable 属性设置为应执行的程序的完整路径名,然后调用Execute。要确定进程是否仍在运行(即没有停止执行),可以检查 Running属性。

属性、事件和方法:

public


constructor Create(); override;

创建TProcess类的新实例。

destructor Destroy; override;

销毁这个TProcess实例

procedure Execute; virtual;

使用给定的选项执行程序

procedure CloseInput; virtual;

关闭进程的输入流

procedure CloseOutput; virtual;

关闭进程的输出流

procedure CloseStderr; virtual;

关闭进程的错误流

function Resume; virtual;

恢复执行暂停的进程

function Suspend; virtual;

暂停正在运行的进程

function Terminate(); virtual;

终止正在运行的进程

function WaitOnExit;

等待程序停止执行。

property WindowRect: Trect; [rw]

主程序窗口的位置。

property Handle: THandle; [r]

进程的句柄

property ProcessHandle: THandle; [r]

句柄的别名

property ThreadHandle: THandle; [r]

主进程线程句柄

` property ProcessID: Integer; [r]

进程的标识。

property ThreadID: Integer; [r]

主进程线程ID

property Input: TOutputPipeStream; [r]

流连接到进程的标准输入。

property Output: TInputPipeStream; [r]

流连接到进程的标准输出。

property Stderr: TInputPipeStream; [r]

流连接到进程的标准诊断输出。

property ExitStatus: Integer; [r]

进程的退出状态。

property ExitCode: Integer; [r]

进程退出代码

property InheritHandles: Boolean; [rw]

创建的进程是否应该继承当前进程的打开句柄。

` property OnForkEvent: TProcessForkEvent; [rw]

Linux 上 fork 发生后触发的事件

published


property PipeBufferSize: Cardinal; [rw]

使用管道时要使用的缓冲区大小

property Active: Boolean; [rw]

启动或停止该过程。

property ApplicationName: string; [rw] deprecated ;

要启动的应用程序的名称(已弃用)

property Executable: string; [rw]

可执行名称。

property Parameters: TStrings; [rw]

命令行参数。

property ConsoleTitle: string; [rw]

控制台窗口的标题

property CurrentDirectory: string; [rw]

进程的工作目录。

` property Desktop: string; [rw]

启动进程的桌面。

property Environment: TStrings; [rw]

新进程的环境变量。

property Options: TProcessOptions; [rw]

启动进程时要使用的选项。

property Priority: TProcessPriority; [rw]

进程运行的优先级。

property StartupOptions: TStartupOptions; [rw]

其他 (Windows) 启动选项

property Running: Boolean; [r]

确定进程是否仍在运行。

property ShowWindow: TShowWindowOptions; [rw]

确定进程主窗口的显示方式(仅限 Windows)

property WindowColumns: Cardinal; [rw]

控制台窗口中的列数(仅限 Windows)

property WindowHeight: Cardinal; [rw]

进程主窗口的高度

property WindowLeft: Cardinal; [rw]

初始窗口的 X 坐标(仅限 Windows)

property WindowRows: Cardinal; [rw]

控制台窗口中的行数(仅限 Windows)

property WindowTop: Cardinal; [rw]

初始窗口的 Y 坐标(仅限 Windows)

property WindowWidth: Cardinal; [rw]

进程主窗口的高度(仅限 Windows)

property FillAttribute: Cardinal; [rw]

控制台窗口中字符的颜色属性(仅限 Windows)

property XTermProgram: string; [rw]

要使用的 XTerm 程序(仅限 unix)

3.2 一个简单的示例

这个示例仅显示如何运行一个外部程序,这不能使用在生产中,看大量输出

// This is a demo program that shows
// how to launch an external program.
program launchprogram;
 
// Here we include files that have useful functions
// and procedures we will need.
uses 
  Classes, SysUtils, Process;
 
// This defines the var "AProcess" as a variable 
// of the type "TProcess"
var 
  AProcess: TProcess;
 
// This is where our program starts to run
begin
  // Now we will create the TProcess object, and
  // assign it to the var AProcess.
  AProcess := TProcess.Create(nil);
 
  // Tell the new AProcess what the command to execute is.
  // Let's use the Free Pascal compiler (i386 version that is)
  AProcess.Executable:= 'ppc386';

  // Pass -h together with ppc386 so actually 'ppc386 -h' is executed:
  AProcess.Parameters.Add('-h');
 
  // We will define an option for when the program
  // is run. This option will make sure that our program
  // does not continue until the program we will launch
  // has stopped running.                vvvvvvvvvvvvvv
  AProcess.Options := AProcess.Options + [poWaitOnExit];
 
  // Now let AProcess run the program
  AProcess.Execute;
 
  // This is not reached until ppc386 stops running.
  AProcess.Free;   
end.

上面的程序学习从程序内部来运行一个外部程序。

3.3 一个提高的示例

如何读取一个运行程序的输出?好的,让我们稍微扩张我们的示例,并保持这个示例简单,从而可以从中学习。请不要在产品代码中使用这个示例,因为在代码中有大量输出。

// This is a 
// FLAWED
// demo program that shows
// how to launch an external program
// and read from its output.
program launchprogram;
 
// Here we include files that have useful functions
// and procedures we will need.
uses 
  Classes, SysUtils, Process;
 
// This is defining the var "AProcess" as a variable 
// of the type "TProcess"
// Also now we are adding a TStringList to store the 
// data read from the programs output.
var 
  AProcess: TProcess;
  AStringList: TStringList;

// This is where our program starts to run
begin
  // Now we will create the TProcess object, and
  // assign it to the var AProcess.
  AProcess := TProcess.Create(nil);
 
  // Tell the new AProcess what the command to execute is.
  AProcess.Executable := '/usr/bin/ppc386'; 
  AProcess.Parameters.Add('-h'); 

  // We will define an option for when the program
  // is run. This option will make sure that our program
  // does not continue until the program we will launch
  // has stopped running. Also now we will tell it that
  // we want to read the output of the file.
  AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
  // Now that AProcess knows what the commandline is it can be run.
  AProcess.Execute;
  
  // After AProcess has finished, the rest of the program will be executed.
 
  // Now read the output of the program we just ran into a TStringList.
  AStringList := TStringList.Create;
  AStringList.LoadFromStream(AProcess.Output);
   
  // Save the output to a file and clean up the TStringList.
  AStringList.SaveToFile('output.txt');
  AStringList.Free;
 
  // Now that the output from the process is processed, it can be freed.
  AProcess.Free;   
end.

3.4 读大量输出

在前一个示例中,我们一直等到程序退出。然后,我们读取程序的输出。假设程序写很多数据到输出。然后,输出管道变满,并且调用程序等待,直到管道被读取。但是,调用程序不从它读,直到调用的程序被结束。一个停滞发生。

下面的示例因为不使用 poWaitOnExit 选项,但是,当程序仍然在运行时,从中输出读取。输出被存储在内存流中,随后可以被使用于读输出到一个 TStringList 中。如果我们想从一个外部进程中读输出,这是需要改写到产品使用的代码。

program LargeOutputDemo;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, Process; // Process is the unit that holds TProcess

const
  BUF_SIZE = 2048; // Buffer size for reading the output in chunks

var
  AProcess     : TProcess;
  OutputStream : TStream;
  BytesRead    : longint;
  Buffer       : array[1..BUF_SIZE] of byte;

begin
  // Set up the process; as an example a recursive directory search is used
  // because that will usually result in a lot of data.
  AProcess := TProcess.Create(nil);

  // The commands for Windows and *nix are different hence the $IFDEFs
  {$IFDEF Windows}
    // In Windows the dir command cannot be used directly because it's a build-in
    // shell command. Therefore cmd.exe and the extra parameters are needed.
    AProcess.Executable := 'c:\windows\system32\cmd.exe';
    AProcess.Parameters.Add('/c');
    AProcess.Parameters.Add('dir /s c:\windows');
  {$ENDIF Windows}

  {$IFDEF Unix}
    AProcess.Executable := '/bin/ls';
    AProcess.Parameters.Add('--recursive');
    AProcess.Parameters.Add('--all');
    AProcess.Parameters.Add('-l');
  {$ENDIF Unix}

  // Process option poUsePipes has to be used so the output can be captured.
  // Process option poWaitOnExit can not be used because that would block
  // this program, preventing it from reading the output data of the process.
  AProcess.Options := [poUsePipes];

  // Start the process (run the dir/ls command)
  AProcess.Execute;

  // Create a stream object to store the generated output in. This could
  // also be a file stream to directly save the output to disk.
  OutputStream := TMemoryStream.Create;

  // All generated output from AProcess is read in a loop until no more data is available
  repeat
    // Get the new data from the process to a maximum of the buffer size that was allocated.
    // Note that all read(...) calls will block except for the last one, which returns 0 (zero).
    BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);

    // Add the bytes that were read to the stream for later usage
    OutputStream.Write(Buffer, BytesRead)

  until BytesRead = 0;  // Stop if no more data is available

  // The process has finished so it can be cleaned up
  AProcess.Free;

  // Now that all data has been read it can be used; for example to save it to a file on disk
  with TFileStream.Create('output.txt', fmCreate) do
  begin
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
    CopyFrom(OutputStream, OutputStream.Size);
    Free
  end;

  // Or the data can be shown on screen
  with TStringList.Create do
  begin
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
    LoadFromStream(OutputStream);
    writeln(Text);
    writeln('--- Number of lines = ', Count, '----');
    Free
  end;

  // Clean up
  OutputStream.Free;
end.

注意,上面应使用 RunCommand 完成:

var s: string;
...
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);

3.5 关于TProcess的使用的提示

当创建一个跨平台程序,指定的操作系统可执行文件名称可以使用指令"{$IFDEF}"和{$ENDIF}"设置。

示例:

{...}
AProcess := TProcess.Create(nil)

{$IFDEF WIN32}
  AProcess.Executable := 'calc.exe'; 
{$ENDIF}

{$IFDEF LINUX}
  AProcess.Executable := FindDefaultExecutablePath('kcalc');
{$ENDIF}

AProcess.Execute;
{...}

3.6 OS X 在前台显示应用程序包

可以凭借 TProces s通过在软件包中启动可执行文件开始一个应用程序包。 例如:

AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';

这将开始 Calendar,但是窗口将在当前应用程序的后面。为在前台获取应用程序,你可以使用 open 带有-n参数:

AProcess.Executable:='/usr/bin/open';
AProcess.Parameters.Add('-n');
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole
AProcess.Parameters.Add('/Application/iCal.app');

如果应用程序需要参数,可以传送 open 程序的 --args 参数,将所有的参数传送到应用程序。

AProcess.Parameters.Add('--args');
AProcess.Parameters.Add('argument1');
AProcess.Parameters.Add('argument2');

3.7 运行独立的程序

一般的,由应用程序开始的程序是一个子进程,当你的应用程序被杀死时,子进程亦被杀死。当我们想运行一个独立的保持运行的程序,你可以使用下面的代码:

var
  Process: TProcess;
  I: Integer;
begin
  Process := TProcess.Create(nil);
  try
    Process.InheritHandles := False;
    Process.Options := [];
    Process.ShowWindow := swoShow;

    // Copy default environment variables including DISPLAY variable for GUI application to work
    for I := 1 to GetEnvironmentVariableCount do
      Process.Environment.Add(GetEnvironmentString(I));

    Process.Executable := '/usr/bin/gedit';  
    Process.Execute;
  finally
    Process.Free;
  end;
end;

3.8 替换shell运算符,像 "| < >"

有时,我们想运行更复杂的命令,命令传输它的数据到另一个命令或文件。类似于

ShellExecute('firstcommand.exe | secondcommand.exe');

ShellExecute('dir > output.txt');

使用 TProcess 执行这样的命令将不工作,例如:

// this won't work
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; 
Process.Execute;

为什么使用指定运算符到重定向输出不工作?TProcess ,它不是一个 shell 环境,仅是一个进程 process(进程)。它不是两个进程,它仅是一个。

如何使用TProcess 重定向输出呢?可以为每一个命令使用一个 TProcess 实例重定向一个命令的输出到另一个命令。以下是一个示例,解释重定向一个进程的输出到另一个。为重定向一个进程的输出到一个文件/流,不仅可以重定向"正常"输出(也被称为stdout),也可以重定向错误输出(stderr),如果你指定 poStderrToOutPut 选项,像在第二个进程选项中所看到的。

program Project1;
  
uses
  Classes, sysutils, process;
  
var
  FirstProcess,
  SecondProcess: TProcess;
  Buffer: array[0..127] of char;
  ReadCount: Integer;
  ReadSize: Integer;
begin
  FirstProcess  := TProcess.Create(nil);
  SecondProcess := TProcess.Create(nil);
 
  FirstProcess.Options     := [poUsePipes]; 
  FirstProcess.Executable  := 'pwd'; 
  
  SecondProcess.Options    := [poUsePipes,poStderrToOutPut];
  SecondProcess.Executable := 'grep'; 
  SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); 
  // this would be the same as "pwd | grep / -"
  
  FirstProcess.Execute;
  SecondProcess.Execute;
  
  while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
  begin
    if FirstProcess.Output.NumBytesAvailable > 0 then
    begin
      // make sure that we don't read more data than we have allocated
      // in the buffer
      ReadSize := FirstProcess.Output.NumBytesAvailable;
      if ReadSize > SizeOf(Buffer) then
        ReadSize := SizeOf(Buffer);
      // now read the output into the buffer
      ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
      // and write the buffer to the second process
      SecondProcess.Input.Write(Buffer[0], ReadCount);
  
      // if SecondProcess writes much data to it's Output then 
      // we should read that data here to prevent a deadlock
      // see the previous example "Reading Large Output"
    end;
  end;
  // Close the input on the SecondProcess
  // so it finishes processing it's data
  SecondProcess.CloseInput;
 
  // and wait for it to complete
  // be carefull what command you run because it may not exit when
  // it's input is closed and the following line may loop forever
  while SecondProcess.Running do
    Sleep(1);
  // that's it! the rest of the program is just so the example
  // is a little 'useful'

  // we will reuse Buffer to output the SecondProcess's
  // output to *this* programs stdout
  WriteLn('Grep output Start:');
  ReadSize := SecondProcess.Output.NumBytesAvailable;
  if ReadSize > SizeOf(Buffer) then
    ReadSize := SizeOf(Buffer);
  if ReadSize > 0 then
  begin
    ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);
    WriteLn(Copy(Buffer,0, ReadCount));
  end
  else
    WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);
  WriteLn('Grep output Finish:');
  
  // free our process objects
  FirstProcess.Free;
  SecondProcess.Free;
end.

这是全部。现在可以从一个程序重定向输出到另一个。



最近发表
标签列表