Delphiでお手軽プログラミング

Delphiでお手軽プログラミングメニュー

DelphiでコマンドプロンプトをGUIで操作するアプリケーションを作る

Delphi XE10.3.3 Community EditionでコマンドプロンプトをGUI操作するアプリケーションを作成します。
(DelphiXE10.2だと以下ソースコードはうまく動きませんでした。)


コマンドプロンプトGUIアプリのファイルの準備

本ページの下部のソースコードをコピーして「MamCmdThread.pas」ファイルを作成し、プロジェクトフォルダ内に入れる。

ソースコードの記述

プロジェクトを新規作成(VCLアプリケーション)し、フォーム(Form1)にTEdit、TMenuを配置する。
Edit1のAlignプロパティを「alTop」に設定する。
Menu1のAlignプロパティを「alClient」に設定する。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  MamCmdThread ;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Edit1KeyPress(Sender: TObject; var Key: Char);
    procedure FormResize(Sender: TObject);
  private
    { Private 宣言 }
    cmd:TMamCmdThread;
    procedure OnTextChange({Sender:TObject;}text:String);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key=#13 then
  begin
    Key:=#0;
    cmd.Send(Edit1.Text);
    Edit1.Text:='';
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var coordx:integer;
begin
  Memo1.ScrollBars:=ssBoth;
  Memo1.DoubleBuffered:=True;
  Memo1.Font.Name:='MS ゴシック';
  Memo1.ReadOnly:=True;
  Memo1.Font.Size:=10;
  coordx:=trunc(
    (Memo1.ClientWidth-10) / ((Abs(Memo1.Font.Height)+1))
  )*2-1;
  cmd:=TMamCmdThread.Create(self.Handle,OnTextChange,coordx,9000);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  cmd.Terminate;
  cmd.Free;
end;

procedure TForm1.FormResize(Sender: TObject);
var coordx:integer;
begin
  coordx:=trunc(
    (Memo1.ClientWidth-10) / ((Abs(Memo1.Font.Height)+1))
  )*2-1;
  cmd.SetCoord(coordx,9000);
end;

procedure TForm1.OnTextChange({Sender: TObject;} text: String);
begin
  Memo1.Lines.Text:=text;
  //一番下までスクロールする
  Memo1.Perform(
    EM_LINESCROLL, 0, Memo1.Lines.Count);
  Memo1.SelStart :=length(Memo1.Lines.Text)-1;
end;

end.

実行する

実行ボタンを押して実行します。(デバッグ実行でもOK)
Edit1に「dir」と入力してエンターキーを押すと、GUI上にコマンドプロンプトの実行結果が表示されます。


「MamCmdThread.pas」ファイルのソースコード

unit MamCmdThread;

interface
uses
  Winapi.Windows, Winapi.Messages,Winapi.ShellAPI,
  system.sysutils,system.classes
  ,Vcl.Dialogs,system.strutils;

function GetConsoleWindow: HWnd; stdcall;
  external 'kernel32.dll' name 'GetConsoleWindow';
function AttachConsole(ProcessId: DWORD): BOOL; stdcall;
  external 'kernel32.dll' name 'AttachConsole';

type
  TMamCmdThreadEnc=(
    MamCmdThreadEncSJIS,
    MamCmdThreadEncJIS,
    MamCmdThreadEncEUCJP,
    MamCmdThreadEncUTF8,
    MamCmdThreadEncUTF16
  );

  TMamCmdThreadOnTextChange=procedure(
    {sender:TObject;}text:String) of Object;

  TMamCmdThread=class(TThread)
    private
      fWindowHandle:HWND;
      fcmd:String;
      fShellExecuteInfo:TShellExecuteInfo;
      fBufList:TStringList;
      fOnChangeTextFunc:TMamCmdThreadOnTextChange;
      procedure wProc(var Msg:TMessage);
      function getStdBuf():Boolean;
    protected
      procedure Execute; override;
    public
      constructor Create(
        ParentHandle:HWND;
        OnChangeTextFunc:TMamCmdThreadOnTextChange;
        CoordX:Integer=120;CoordY:Integer=9001;
        Enc: TMamCmdThreadEnc=TMamCmdThreadEnc.MamCmdThreadEncUTF16
      );
      destructor Destroy; override;
      procedure Send(text: String);
      //コードページを設定する
      procedure SetEncoding(Enc:TMamCmdThreadEnc);
      //コンソールのサイスを設定する
      procedure SetCoord(CoordX,CoordY:Integer);
  end;

implementation

uses Unit1;

var fCmdWndHandle:hwnd;

function EnumWindowCallbackFunc(h: HWND; lp: NativeInt): boolean;
var processID:Cardinal;
begin
  GetWindowThreadProcessId(h, processID);
  if lp=processID then
  begin
    fCmdWndHandle:=h;
  end;
  result:=True;
end;


{ TMamCmdThread }

constructor TMamCmdThread.Create(
  ParentHandle:HWND;
  OnChangeTextFunc:TMamCmdThreadOnTextChange;
  CoordX:Integer=120;
  CoordY:Integer=9001;
  Enc: TMamCmdThreadEnc=TMamCmdThreadEnc.MamCmdThreadEncUTF16
);
var ret,pid:Cardinal;
    SRect:_SMALL_RECT;
begin
  inherited Create(False);
  fCmdWndHandle:=0;
  fBufList:=TStringList.Create;
  FreeOnTerminate:=False;

  fOnChangeTextFunc:=OnChangeTextFunc;

  //commandプロンプトの実行フルパスを取得
  fcmd:=GetEnvironmentVariable('COMSPEC');

  fWindowHandle:=ParentHandle;

  ZeroMemory(@fShellExecuteInfo,SizeOf(fShellExecuteInfo));
  fShellExecuteInfo.cbSize:=SizeOf(fShellExecuteInfo);
  //SEE_MASK_NOCLOSEPROCESS プロセスハンドルをクローズしない
  fShellExecuteInfo.fMask:=SEE_MASK_NOCLOSEPROCESS;
  fShellExecuteInfo.lpFile:=PChar(fCmd);
  fShellExecuteInfo.lpVerb:=PChar('');
  fShellExecuteInfo.Wnd:=fWindowHandle;
  //fShellExecuteInfo.nShow:=SW_SHOW;
  fShellExecuteInfo.nShow:=SW_HIDE;

  ShellExecuteEx(@fShellExecuteInfo);
  //成功すると33以上の値になる
  if fShellExecuteInfo.hInstApp>32 then
  begin
    repeat
      //プロセスがアイドル状態になるまで500ms待機する
      ret:=WaitForInputIdle(fShellExecuteInfo.hProcess,500);
    until (ret<>WAIT_TIMEOUT);
    sleep(500);

    //プロセスハンドルからプロセスIDを取得
    pid:=GetProcessId(fShellExecuteInfo.hProcess);
    //プロセスIDからウィンドウハンドルを取得
    EnumWindows(@EnumWindowCallbackFunc, NativeInt(pid));
    //ウィンドウハンドルが取得できなかった場合
    while fCmdWndHandle=0 do
    begin
      fCmdWndHandle := FindWindow(PChar('ConsoleWindowClass'), nil);
      sleep(100);
    end;

    //コンソールのスレッドにアタッチ
    while AttachConsole(pid) do begin end;

    //Ctrl+CとCtrl+Breakを無効にする
    SetConsoleCtrlHandler(nil, true);

    //コンソールウィンドウを小さく設定
    SRect.Left:=0;
    SRect.Top:=0;
    SRect.Right:=1;
    SRect.Bottom:=1;
    SetConsoleWindowInfo(
      GetStdHandle(STD_OUTPUT_HANDLE), True, SRect);
    //コードページ設定
    SetEncoding(Enc);
    //コンソールの幅高さ設定
    SetCoord(CoordX,CoordY);
  end;

end;

destructor TMamCmdThread.Destroy;
//var pid,hPh:Cardinal;
begin
  fOnChangeTextFunc:=nil;
  FreeConsole();
  PostMessage(fCmdWndHandle,WM_CLOSE,0,0);
  fBufList.Free;
  inherited;
end;


procedure TMamCmdThread.Execute;
begin
  inherited;
  while not Terminated do
  begin
    if getStdBuf() then
    begin
      if Assigned(fOnChangeTextFunc) then
        Synchronize(
          procedure
          begin
            fOnChangeTextFunc({nil,}fBufList.Text);
          end
        );
    end;
    sleep(250);
  end;
end;

function TMamCmdThread.getStdBuf:Boolean;
var ret:Boolean;
    Buf:TConsoleScreenBufferInfo;
    pC:PChar;
    y:integer;
    ReadCount:Cardinal;
    st:String;
    BufList:TStringList;
    flag:boolean;
    Cd:TCoord;
begin
  result:=False;
  //バッファの情報を取得(標準エラーから読みだす)
  ret:=GetConsoleScreenBufferInfo(
    GetStdHandle(STD_ERROR_HANDLE),Buf);
  if ret then
  begin
    //メモリの確保
    GetMem(pC, Buf.dwSize.X*2);
    BufList:=TStringList.Create;
    try
      flag:=False;
      for y:=Buf.dwSize.Y-1 downto 0 do
      begin
        zeromemory(pC,Buf.dwSize.X*2);
        Cd.X:=0;
        Cd.Y:=y;
        if ReadConsoleOutputCharacter(
          GetStdHandle(STD_ERROR_HANDLE),
          pC, Buf.dwSize.X, Cd, ReadCount) then
        begin
          st:=String(pC);
          st:=LeftStr(st,ReadCount);
          if trim(st)<>'' then flag:=True;
          if flag then
            BufList.Insert(0,st);
        end;
      end;
      if fBufList.Text<>BufList.Text then
      begin
        fBufList.Text:=BufList.Text;
        result:=True;
      end;
    finally
      FreeMem(pC);
      BufList.Free;
    end;
  end;
end;

procedure TMamCmdThread.Send(text: String);
var InputRecord:TInputRecord;
    NoEW:Cardinal;
    i:Integer;
    ca:array of Char;
begin
  text:=text+Chr(VK_RETURN);
  setlength(ca,length(text));
  strCopy(@ca[0],PChar(text));
  for i:=0  To Length(ca)-1 do
  begin
    ZeroMemory(@InputRecord,sizeof(InputRecord));
    InputRecord.EventType := KEY_EVENT;
    InputRecord.Event.KeyEvent.wRepeatCount:=1;
    InputRecord.Event.KeyEvent.UnicodeChar:=ca[i];
    InputRecord.Event.KeyEvent.bKeyDown:=True;
    WriteConsoleInput(
      GetStdHandle(STD_INPUT_HANDLE),
      InputRecord,1,NoEW
    );
    InputRecord.Event.KeyEvent.bKeyDown:=False;
    WriteConsoleInput(
      GetStdHandle(STD_INPUT_HANDLE),
      InputRecord,1,NoEW
    );
  end;
end;

procedure TMamCmdThread.SetCoord(CoordX, CoordY: Integer);
var cod:TCoord;
begin
  if CoordX<20 then CoordX:=20;
  if CoordY<20 then CoordY:=20;
  if CoordX>32767 then CoordX:=32767;
  if CoordY>50000 then CoordY:=50000;
  cod.X:=CoordX;
  cod.Y:=CoordY;
  SetConsoleScreenBufferSize(
    GetStdHandle(STD_ERROR_HANDLE),cod);
end;

procedure TMamCmdThread.SetEncoding(Enc: TMamCmdThreadEnc);
begin
  if Enc=TMamCmdThreadEnc.MamCmdThreadEncSJIS then
  begin
    SetConsoleCP(932);
    SetConsoleOutputCP(932);
  end
  else if Enc=TMamCmdThreadEnc.MamCmdThreadEncJIS then
  begin
    SetConsoleCP(50220);
    SetConsoleOutputCP(50220);
  end
  else if Enc=TMamCmdThreadEnc.MamCmdThreadEncEUCJP then
  begin
    SetConsoleCP(20932);
    SetConsoleOutputCP(20932);
  end
  else if Enc=TMamCmdThreadEnc.MamCmdThreadEncUTF8 then
  begin
    SetConsoleCP(65001);
    SetConsoleOutputCP(65001);
  end
  else
  begin
    //UTF-16
    SetConsoleCP(1200);
    SetConsoleOutputCP(1200);
  end;
end;

procedure TMamCmdThread.wProc(var Msg: TMessage);
begin

end;

end.



Copyright 2021 Mam