delphi 在线程中运行控制台命令(console)

Posted lackey

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了delphi 在线程中运行控制台命令(console)相关的知识,希望对你有一定的参考价值。

在编程开发的时候,我们时常会调用windows本身的功能,如:检测网络通断,连接无线wifi等。

虽然,用 windows api 操作可以完美地完成这些操作,但是,函数参数太难了。令人望而生畏,不是普通开发者能办到的。

但是,我们可以用一种变通的方法,来解决这个问题,就是使用控制台命令行,如 ping , netsh 等。

我在网络上,搜索到了delphi调用命令行,并返回接收返回的结果(字符串信息)代码,但这些代码仅仅只是功能实现了,离实用性还差一步。

所以做了如下改进:

1.将 cmd 运行进程放入线程中,不放入线程,界面就卡死了,阻塞的,实用性大大降低,可能只能采用运行一次命令,就创建一次cmd进程的方式来实现。

本例的CMD只创建一次,可以复用。

2.提供了明确的执行结果事件,就是命令真正执行完毕的事件,因为返回的结果字符串不是一次性全部返回的,太长的结果是分批次返回的。这一点,但其它的控制台的设备中也是一样的。如路由器的 console 下。

3.实现了 ctrl + c 这类特殊事件的触发,如果没有这个功能,运行 ping 127.0.0.1 -t 就无法正常结束。

经过工作实践中运行,觉得还不错,不敢独享,故分享给大家。也算是 delphi 线程的一个教学实例。

技术图片
unit uSimpleConsole;

interface

uses
  System.Classes, WinApi.Windows, uElegantThread, uSimpleThread, uSimpleList;

type

  TSimpleConsole = class;

  TConsoleStatus = (ccUnknown, ccInit, ccCmdResult);
  TOnConsoleStatus = procedure(Sender: TSimpleConsole; AStatus: TConsoleStatus) of object;

  TInnerConsoleStatus = (iccInit, iccExecCmd, iccSpecEvent, iccWait);

  PCmdStr = ^TCmdStr;

  TCmdStr = record
    Status: TInnerConsoleStatus;
    CmdStr: string;
    Event: integer;
  end;

  TCmdStrList = class(TSimpleList<PCmdStr>)
  private
    function AddCmdStr(ACmdStr: string): PCmdStr;
    function AddSpecialEvent(AEvent: integer): PCmdStr;
  protected
    procedure FreeItem(Item: PCmdStr); override;
  end;

  TSimpleConsole = class(TSimpleThread)
  private

    FInRead: THandle; // in 用于控制台输入
    FInWrite: THandle;
    FOutRead: THandle; // out 用于控制台输出
    FOutWrite: THandle;
    FFileName: String;
    FProcessInfo: TProcessInformation;
    FProcessCreated: Boolean;
    FCmdStrList: TCmdStrList;
    FCmdResultStrs: TStringList;

    FConsoleStatus: TInnerConsoleStatus;

    procedure Peek;
    procedure DoPeek;
    procedure DoCreateProcess;
    procedure DoExecCmd(ACmdStr: string);
    function WriteCmd(ACmdStr: string): Boolean;
    procedure DoOnConsoleStatus(AStatus: TConsoleStatus);

    procedure ClearCmdResultStrs;
    procedure AddCmdResultText(AText: string);
    function CheckCmdResultSign(AText: string): Boolean;

  public
    constructor Create(AFileName: string); reintroduce;
    destructor Destroy; override;
    procedure StartThread; override;
    procedure ExecCmd(ACmdStr: String);
    procedure ExecSpecialEvent(AEvent: integer); // 执行特殊事件,如 ctrl + c
    property CmdResultStrs: TStringList read FCmdResultStrs;
  public
    WorkDir: string;
    ShowConsoleWindow: Boolean;
    OnConsoleStatus: TOnConsoleStatus;
  end;

function AttachConsole(dwprocessid: DWORD): BOOL;
stdcall external kernel32;

implementation

uses
  Vcl.Forms, System.SysUtils, System.StrUtils;

{ TSimpleConsole }
const
  cnSecAttrLen = sizeof(TSecurityAttributes);

procedure TSimpleConsole.AddCmdResultText(AText: string);
var
  L: TStringList;
begin
  L := TStringList.Create;
  try
    L.Text := Trim(AText);
    FCmdResultStrs.AddStrings(L);
  finally
    L.Free;
  end;
end;

function TSimpleConsole.CheckCmdResultSign(AText: string): Boolean;
var
  L: TStringList;
  i, n: integer;
  sTemp: string;
begin
  Result := false;
  L := TStringList.Create;
  try
    L.Text := Trim(AText);
    for i := L.Count - 1 downto 0 do
    begin
      sTemp := Trim(L[i]);
      n := length(sTemp);
      if (PosEx(:‘, sTemp) = 2) and (PosEx(>, sTemp, 3) >= n) then
      begin
        Result := true;
        exit;
      end;
    end;
  finally
    L.Free;
  end;
end;

procedure TSimpleConsole.ClearCmdResultStrs;
begin
  FCmdResultStrs.Clear;
end;

constructor TSimpleConsole.Create(AFileName: string);
begin
  inherited Create(true);
  FFileName := AFileName;
  FProcessCreated := false;
  ShowConsoleWindow := false;

  FCmdResultStrs := TStringList.Create;
  FCmdStrList := TCmdStrList.Create;

end;

destructor TSimpleConsole.Destroy;
var
  Ret: integer;
begin
  Ret := 0;
  if FProcessCreated then
  begin

    TerminateProcess(FProcessInfo.hProcess, Ret);

    closehandle(FInRead);
    closehandle(FInWrite);
    closehandle(FOutRead);
    closehandle(FOutWrite);

  end;

  FCmdResultStrs.Free;
  FCmdStrList.Free;

  inherited;
end;

procedure TSimpleConsole.DoCreateProcess;
const
  cnBuffLen = 256;
  cnReadByteLen = cnBuffLen;
  cnSecAttrLen = sizeof(TSecurityAttributes);
  cnStartUpInfoLen = sizeof(TStartupInfo);
var
  sWorkDir: string;
  LStartupInfo: TStartupInfo;
  LSecAttr: TSecurityAttributes;
  sCmd: string;
  v: integer;
begin

  if length(WorkDir) > 0 then
  begin
    sWorkDir := WorkDir;
  end
  else
  begin
    sWorkDir := ExtractFileDir(Application.ExeName);
    WorkDir := sWorkDir;
  end;

  if ShowConsoleWindow then
    v := 1
  else
    v := 0;

  ZeroMemory(@LSecAttr, cnSecAttrLen);

  LSecAttr.nLength := cnSecAttrLen;
  LSecAttr.bInheritHandle := true;
  LSecAttr.lpSecurityDescriptor := nil;

  CreatePipe(FInRead, FInWrite, @LSecAttr, 0);
  CreatePipe(FOutRead, FOutWrite, @LSecAttr, 0);

  ZeroMemory(@LStartupInfo, cnStartUpInfoLen);

  LStartupInfo.cb := cnStartUpInfoLen;
  LStartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
  LStartupInfo.wShowWindow := v;

  LStartupInfo.hStdInput := FInRead; // 如果为空,则可以由键盘输入
  LStartupInfo.hStdOutput := FOutWrite; // 如果为空,则显示到屏幕上
  LStartupInfo.hStdError := FOutWrite;

  setlength(sCmd, length(FFileName));

  CopyMemory(@sCmd[1], @FFileName[1], length(FFileName) * sizeof(char));

  if CreateProcess(nil, PChar(sCmd), { pointer to command line string }
    @LSecAttr, { pointer to process security attributes }
    @LSecAttr, { pointer to thread security attributes }
    true, { handle inheritance flag }
    NORMAL_PRIORITY_CLASS, nil, { pointer to new environment block }
    PChar(sWorkDir), { pointer to current directory name, PChar }
    LStartupInfo, { pointer to STARTUPINFO }
    FProcessInfo) { pointer to PROCESS_INF }
  then
  begin
    // ClearCmdResultStrs;
    // FInnerConsoleList.AddInerStatus(iccInit);
  end
  else
  begin
    DoOnStatusMsg(进程[ + FFileName + ]创建失败);
  end;

end;

procedure TSimpleConsole.DoExecCmd(ACmdStr: string);
var
  sCmdStr: string;
begin
  sCmdStr := ACmdStr + #13#10;
  if WriteCmd(sCmdStr) then
  begin
    // FInnerConsoleList.AddCmdStr(iccExecCmd);
    // Peek
  end
  else
  begin
    DoOnStatusMsg(执行:[ + ACmdStr + ]失败);
  end;
end;

procedure TSimpleConsole.DoOnConsoleStatus(AStatus: TConsoleStatus);
begin
  if Assigned(OnConsoleStatus) then
    OnConsoleStatus(self, AStatus);
end;

procedure TSimpleConsole.DoPeek;
var
  strBuff: array [0 .. 255] of AnsiChar;
  nBytesRead: cardinal;
  sOutStr: string;
  sOut: AnsiString;
  nOut: cardinal;
  BPeek: Boolean;
  p: PCmdStr;

begin

  if not FProcessCreated then
  begin
    FConsoleStatus := iccInit;
    DoCreateProcess;
    FProcessCreated := true;
  end;

  sOutStr := ‘‘;
  nBytesRead := 0;

  nOut := 0;
  sOut := ‘‘;

  BPeek := PeekNamedPipe(FOutRead, @strBuff, 256, @nBytesRead, nil, nil);

  while BPeek and (nBytesRead > 0) do
  begin

    inc(nOut, nBytesRead);
    setlength(sOut, nOut);
    CopyMemory(@sOut[nOut - nBytesRead + 1], @strBuff[0], nBytesRead);
    ReadFile(FOutRead, strBuff[0], nBytesRead, nBytesRead, nil);

    BPeek := PeekNamedPipe(FOutRead, @strBuff, 256, @nBytesRead, nil, nil);

  end;

  if length(sOut) > 0 then
  begin
    sOutStr := String(sOut);

    DoOnStatusMsg(sOutStr);

    if CheckCmdResultSign(sOutStr) then
    begin

      if FConsoleStatus = iccInit then
      begin
        DoOnConsoleStatus(ccInit)
      end
      else if FConsoleStatus = iccExecCmd then
      begin
        AddCmdResultText(sOutStr);
        DoOnConsoleStatus(ccCmdResult)
      end
      else
        DoOnConsoleStatus(ccUnknown);

      ClearCmdResultStrs;

    end;

  end;

  FCmdStrList.Lock;
  try

    p := FCmdStrList.PopFirst;
    if Assigned(p) then
    begin

      FConsoleStatus := iccExecCmd;

      if p.Status = iccExecCmd then
        DoExecCmd(p.CmdStr)
      else if p.Status = iccSpecEvent then
      begin
        AttachConsole(self.FProcessInfo.dwprocessid);
        SetConsoleCtrlHandler(nil, true);
        GenerateConsoleCtrlEvent(p.Event, 0);
      end;

      dispose(p);

    end;

  finally

    FCmdStrList.Unlock;
  end;

  Peek;
  SleepExceptStopped(200);

end;

procedure TSimpleConsole.ExecCmd(ACmdStr: String);
begin

  FCmdStrList.Lock;
  try
    FCmdStrList.AddCmdStr(ACmdStr);
  finally
    FCmdStrList.Unlock;
  end;

  Peek;

end;

procedure TSimpleConsole.Peek;
begin
  ExeProcInThread(DoPeek);
end;

procedure TSimpleConsole.ExecSpecialEvent(AEvent: integer);
begin
  FCmdStrList.Lock;
  try
    FCmdStrList.AddSpecialEvent(AEvent);
  finally
    FCmdStrList.Unlock;
  end;

  Peek;

end;

procedure TSimpleConsole.StartThread;
begin
  inherited;
  Peek;
end;

function TSimpleConsole.WriteCmd(ACmdStr: string): Boolean;
var
  nCmdLen: cardinal;
  nRetBytes: cardinal;
  sCmdStr: AnsiString;
begin
  nCmdLen := length(ACmdStr);
  sCmdStr := AnsiString(ACmdStr);
  Result := WriteFile(FInWrite, sCmdStr[1], (nCmdLen), nRetBytes, nil);
end;

{ TInnerStatusList }

function TCmdStrList.AddCmdStr(ACmdStr: string): PCmdStr;
begin
  New(Result);
  Add(Result);
  Result.Status := iccExecCmd;
  Result.CmdStr := ACmdStr;
end;

function TCmdStrList.AddSpecialEvent(AEvent: integer): PCmdStr;
begin
  New(Result);
  Add(Result);
  Result.Status := iccSpecEvent;
  Result.Event := AEvent;
end;

procedure TCmdStrList.FreeItem(Item: PCmdStr);
begin
  inherited;
  dispose(Item);
end;

end.
uSimpleConsole

本例程XE8源码下载

以上是关于delphi 在线程中运行控制台命令(console)的主要内容,如果未能解决你的问题,请参考以下文章

delphi控制台程序输出中文乱码,如何解决?

delphi 的多线程问题

Yii2中Console定时任务

Yii2中Console定时任务

delphi 在线程A中终止线程B

MySQL5.7 在Windows命令行下输入命令mysqld --console,命令行提示以下错误,请问怎么解决?