delphi 在线程中运行控制台命令(console)  
官方Delphi 学习QQ群: 682628230(三千人)\n
频道

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


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.



https://www.cnblogs.com/lackey/p/10357331.html


推荐分享
图文皆来源于网络,内容仅做公益性分享,版权归原作者所有,如有侵权请告知删除!
 

Copyright © 2014 DelphiW.com 开发 源码 文档 技巧 All Rights Reserved
晋ICP备14006235号-8 晋公网安备 14108102000087号

执行时间: 0.097198009490967 seconds