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

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 runbegin  // 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 runbegin  // 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 TProcessconst  BUF_SIZE = 2048; // Buffer size for reading the output in chunksvar  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 poNoConsoleAProcess.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 workProcess.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.

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



发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章