Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年02月15日] [更新 2019年07月15日] Delphi サンプルプログラム集
   
500_コマンドプロンプトを CreateProcess 関数で起動
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
500_CmdPrompt2.zip [3,474 KB] 2019年07月15日版 (EXE 同梱)



  • 2005年03月27日
  • 表示サイズの変更コードを追加
    これは CreateProcess の TProcessInformation 構造体のパラメータの設定を追加しただけ
  • 2015年03月21日
  • 管理者権限での起動の記事を追加




コマンドプロンプトを CreateProcess 関数で起動


CreateProcess 関数でコマンドプロンプトを起動するサンプルです.単なるCreateProcess 関数使用例とも言えます.
当サイトの,CreateProcess 関数を使用した他のサンプルとの違いはほとんどありません.違いは,起動するアプリケーションがコマンドプロセッサということです.

コマンドプロンプト (コマンドプロセッサ) もアプリケーションですから,コマンドラインとしての引数を使用することがあります.コマンドライン引数の扱いについては,アプリケーションの起動関係の記事も参考にしてください.



[備考]
cmd.exe (コマンドプロセッサ) を起動する際,作業ディレクトリを指定すると,その作業ディレクトリがカレントディレクトリになります.CreateProcess 関数では,8 番目 (最後から 3 番目) の引数で作業ディレクトリを指定します.




01_コマンドラインオプションとコマンドを指定して起動


CreateProcess 関数は,コードを追加して,起動したアプリが終了するまで待つことができます.そこで,ここでは,起動したコマンドプロンプトを EXIT コマンド,または,閉じる [X] ボタンで終了するまで,メインフォームに制御が戻らないようにしています.
また,CrateProcess 関数の引数の TStartupInfo 構造体の値を設定することにより,表示位置を画面の左上にして,タイトルも変更しています.



図1
設計時画面
  • ボタン1つだけのフォーム
図2
ボタンクリック
  • コマンドプロンプトが起動
  • 表示位置はデスクトップの左上隅
  • 表示サイズも変更
  • 終了しないとフォームには戻らない
図3
コマンドプロンプトの起動画面
  • ウィンドウのタイトルを [コマンドプロンプト] に変更
  • カレントディレクトリを C:\ に変更

リスト1
CreateProcess 関数でコマンドプロンプトを,位置とサイズを指定して起動
コマンドプロンプトが終了するまで待つ例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して,指定したファイルを実行する
//  起動したアプリが終了するまでこの関数が終了しないコードになっている
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
begin
  Result := 0;

  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  //以下の4行とdwFlagsにSTARTF_USERPOSITION,STARTF_USESIZEを追加
  //位置とサイズを指定する必要がなければこれらの設定は不要
  LStartupInfo.dwX         := 0;
  LStartupInfo.dwY         := 0;
  LStartupInfo.dwXSize     := 500;
  LStartupInfo.dwYSize     := 600;
  LStartupInfo.lpTitle     := PChar('コマンドプロンプト');
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW or
                              STARTF_USEPOSITION or
                              STARTF_USESIZE;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := WAIT_FAILED;
  end else begin
    //起動したプロセスが終了するまで待つ
    while WaitForSingleObject(LProcessInfo.hProcess, 100) = WAIT_TIMEOUT do
      Application.ProcessMessages;

    GetExitCodeProcess(LProcessInfo.hProcess, Result);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  コマンドプロンプトを表示
//  コマンドプロンプトを終了しないと,このアプリは終了できない
//  そこで,このフォームにフォーカス移動できないようにしている
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Self.Enabled := False;
  WinExecAndWait32V2('Cmd.exe /k cd /d c:\', SW_SHOWNORMAL);
  Self.Enabled := True;
end;

end.




02_コマンドプロンプトが起動するまで待つ


上のサンプルコードでは,起動したアプリを終了しない限り,起動元のプログラムは終了できません.
起動したアプリ起動後,自アプリを操作可能にするには,以下のように,CreateProcess 関係のコードを修正すれば可能です.
タイトルは起動するまで待つとなっていますが,これは実際にはコマンドプロンプトのプロセスが起動するまで待つのであって,コマンドプロンプトが入力可能な状態になるわけではありません.

このサンプルでは,自アプリを終了しても,起動したコマンドプロンプトは終了しません.



リスト2
指定したアプリ,またはファイルが起動するまで待つ例
//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して,指定したファイルを実行する
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
  LiRet        : DWORD;
begin
  Result := 0;

  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  //以下の4行とdwFlagsにSTARTF_USERPOSITION,STARTF_USESIZEを追加
  //位置とサイズを指定する必要がなければこれらの設定は不要
  LStartupInfo.dwX         := 0;
  LStartupInfo.dwY         := 0;
  LStartupInfo.dwXSize     := 500;
  LStartupInfo.dwYSize     := 600;
  LStartupInfo.lpTitle     := PChar('コマンドプロンプト');
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW or
                              STARTF_USEPOSITION or
                              STARTF_USESIZE;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := WAIT_FAILED;
  end else begin
    //プロセスが起動するまで待つ
    repeat
      LiRet := WaitForInputIdle(LProcessInfo.hProcess, 50);
      Application.ProcessMessages;
    until LiRet <> WAIT_TIMEOUT;

    GetExitCodeProcess(LProcessInfo.hProcess, Result);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  コマンドプロンプトを表示
//  このアプリを終了しても,起動したコマンドプロンプトは終了しないので注意
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  WinExecAndWait32V2('Cmd.exe /k cd /d c:\', SW_SHOWNORMAL);
end;




03_管理者権限でコマンドプロンプトや他のアプリを起動


CreateProcess 関数で他のプログラムを起動する場合,そのプログラムだけを管理者権限で起動することはできません.対象のプログラムを管理者権限で起動したい場合は,自分自身を管理者権限で実行することになります.

自分自身を管理者権限として実行するには,アプリケーションマニフェストを組み込みます.アプリケーションマニフェストに,EXE 実行開始時に管理者への昇格を確認する「ユーザアカウント制御」のダイアログを表示するための記述をします.
当然ですが,起動するプログラム側にアプリケーションマニフェストを組み込んで,管理者権限で起動するようにしておけば,管理者への昇格ダイアログを表示することができます.

ShellExecute 関数または ShellExecuteEx 関数で他のプログラムを起動する場合は,アプリケーションマニフェストを使用しなくても,管理者への昇格ダイアログを表示して,そのプログラムを管理者権限で実行することができます.



図4
[ユーザーアカウント制御] ダイアログを表示する
  • [管理者への昇格ダイアログ] とも言う




04_コマンドプロンプトを非表示で起動する方法


CreateProcess 関数を使用して,コマンドプロンプトを非表示で起動するには,cmd.exe の起動オプションと CreateProcess 関数の TStartupInfo 構造体のメンバーに対して以下の設定をします.
  • cmd.exe の起動オプションを,/k ではなく /c にする
  • TStartupInfo.wShowWindow メンバの値を SW_HIDE にする
  • SW_HIDE を有効にするために TStartupInfo.dwFlags に STARTF_USESHOWWINDOW を追加
ただし,最初のサンプルコードで単純にこの設定をしてしまうと,プログラムが永久に終了しなくなってしまいます.


リスト3
コマンドプロンプトを非表示で起動してコマンドを実行する
//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して,指定プログラムを実行
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
  LiRet        : DWORD;
begin
  Result := 0;

  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := WAIT_FAILED;
  end else begin
    //プロセスが起動するまで待つ
    repeat
      LiRet := WaitForInputIdle(LProcessInfo.hProcess, 50);
      Application.ProcessMessages;
    until LiRet <> WAIT_TIMEOUT;

    GetExitCodeProcess(LProcessInfo.hProcess, Result);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  cmd.exeでコマンドを実行
//  コマンドプロンプトを表示しない
//  cmd.exeの起動オプションを /c にする
//  CreateProcessのTStartupInfo.dwFlagsにSTARTF_USESHOWWINDOWが必要
//  CreateProcessのTStartupInfo.wShowWindowをSW_HIDEにする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LCmdLine : string;
begin
  //カレントディレクトリをWindowsのシステムフォルダとしてdirコマンドを実行
  //結果は000.txtにリダイレクト(保存)
  LCmdLine := 'Cmd.exe /c cd /d c:\windows\system32\'
            + '&dir>' + ExtractFilePath(Application.ExeName) + '000.txt';
  WinExecAndWait32V2(LCmdLine, SW_HIDE);
end;




05_CreateProcess 関数で起動したプロセスを強制終了


CreateProcess 関数でプロセスやプログラムを起動した場合は TProcessInformation.hProcess の値から,GetProcessId 関数でプロセス ID を取得することができます.このプロセス ID の値を使用して起動中のプロセスを強制終了させることができます.
以下のサンプルは,自分自身が CreateProcess 関数を使用して起動したコマンドプロンプトを強制終了するサンプルコードです.



リスト4
自分自身が起動したコマンドプロンプトを強制終了する
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    FProcessID : DWORD;
    function CheckValidProcess(APID: Cardinal; ATerminate: Boolean): Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して,指定したファイルを実行する
//  この関数の戻り値はプロセスIDの値
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
  LiRet        : DWORD;
begin
  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  //以下の4行とdwFlagsにSTARTF_USERPOSITION,STARTF_USESIZEを追加
  //位置とサイズを指定する必要がなければこれらの設定は不要
  LStartupInfo.dwX         := 0;
  LStartupInfo.dwY         := 0;
  LStartupInfo.dwXSize     := 500;
  LStartupInfo.dwYSize     := 600;
  LStartupInfo.lpTitle     := PChar('コマンドプロンプト');
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW or
                              STARTF_USEPOSITION or
                              STARTF_USESIZE;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := 0;
  end else begin
    //プロセスが起動するまで待つ
    repeat
      LiRet := WaitForInputIdle(LProcessInfo.hProcess, 50);
      Application.ProcessMessages;
    until LiRet <> WAIT_TIMEOUT;

    Result := GetProcessId(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  コマンドプロンプトを表示
//  このアプリを終了しても,起動したコマンドプロンプトは終了しない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  // 複数起動させない場合
  if CheckValidProcess(FProcessID, False) then Exit;

  FProcessID := WinExecAndWait32V2('Cmd.exe /k cd /d c:\', SW_SHOWNORMAL);
end;

//=============================================================================
//  このアプリで生成したプロセスを強制終了
//  本サンプルではコマンドプロンプトを終了
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  CheckValidProcess(FProcessID, True);
  FProcessID := 0;
end;

//-----------------------------------------------------------------------------
//  指定したプロセス ID のプロセスが起動しているかを検出
//  強制終了するかは引数で指定
//
//  戻り値が False の場合
//  対象のプロセスは存在しない
//  他の方法で終了していることもある
//  目的の処理が終了すると自動的に終了するプログラムもある
//-----------------------------------------------------------------------------
function TForm1.CheckValidProcess(APID: Cardinal; ATerminate: Boolean): Boolean;
var
  LhProcHandle : Cardinal;
begin
  if APID = 0 then begin
    Result := False;
    Exit;
  end;

  // Terminate(プロセスの終了)アクセスモードでOpenProcess
  LhProcHandle := OpenProcess(PROCESS_TERMINATE, False, APID);
  Result := LhProcHandle <> 0;
  if Result and ATerminate then begin
    try
      TerminateProcess(LhProcHandle, 0);
    finally
      CloseHandle(LhProcHandle);
    end;
  end;
end;

end.


プロセス ID の値からウィンドウハンドルを取得して.起動したアプリケーションを WM_CLOSE メッセージを使用して終了させる例です.ウィンドウハンドルの値は,EnumWindows 関数のコールバック関数内で検出しています.



リスト5
自分自身が起動したコマンドプロンプトを,ウィンドウハンドルを使用して終了する
//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  プロセス ID に相当するゥインドウハンドルを取得
//-----------------------------------------------------------------------------
function EnumWndProc(hWindow: HWND; lPar: PNativeUInt):
  Boolean; Stdcall;
var
  LdwProcessID : Cardinal;
begin
  Result := True;

  GetWindowThreadProcessId(hWindow, LdwProcessID);
  if LPar^ = LdwProcessID then begin
    LPar^ := hWindow;
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
//  指定したプログラムが起動しているかを検出
//  強制終了するかは引数で指定
//
//  戻り値が False の場合
//  対象のプログラムは起動していない
//  別の方法で終了しているかも知れない
//  処理が終了すると自動的に終了するプログラムもある
//-----------------------------------------------------------------------------
function TForm1.CheckValidProcess(APID: Cardinal; AClose: Boolean): Boolean;
var
  LhWnd : NativeUInt;
begin
  if APID = 0 then begin
    Result := False;
    Exit;
  end;

  // プロセス ID に相当するウィンドウハンドルを取得
  LhWnd := APID;
  EnumWindows(@EnumWndProc, LPARAM(@LhWnd));
  Result := LhWnd <> APID;

  if Result and AClose then begin
    PostMessage(LhWnd, WM_CLOSE, 0, 0);
  end;
end;


本ページの最初のサンプルのように,アプリケーションを起動したら,そのアプリが終了するまで待ちたいことがあります.また,アプリケーションによってはそのアプリ内での処理が終了するまで処理を戻さないことがあります.
そのような場合にプロセス ID の値が必要な時は,プロセスが生成されたらプロセスの ID の値を取得してグローバル変数等に代入しておきます.その後に起動したアプリが終了するまでの処理を実行します.以下のコードはその例です.



リスト6
自分自身が起動したアプリの終了を待つ場合
アプリのプロセスが起動したらプロセス ID の値を取得しておく
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    FProcessID : DWORD;
    function CheckValidProcess(APID: Cardinal; ATerminate: Boolean): Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して,指定したファイルを実行する
//  起動したプロセスが終了するまで待つ処理コード
//
//  プロセスが起動したらそのプロセスID の値を取得して引数に返す
//  この値を使用して起動して実行中のプロセスの強制終了に使用する
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer;
  var AProcessID: Cardinal): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
  LiRet        : DWORD;
begin
  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  //以下の4行とdwFlagsにSTARTF_USERPOSITION,STARTF_USESIZEを追加
  //位置とサイズを指定する必要がなければこれらの設定は不要
  LStartupInfo.dwX         := 0;
  LStartupInfo.dwY         := 0;
  LStartupInfo.dwXSize     := 500;
  LStartupInfo.dwYSize     := 600;
  LStartupInfo.lpTitle     := PChar('コマンドプロンプト');
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW or
                              STARTF_USEPOSITION or
                              STARTF_USESIZE;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := WAIT_FAILED;
  end else begin
    //プロセスが起動するまで待つ
    repeat
      LiRet := WaitForInputIdle(LProcessInfo.hProcess, 50);
      Application.ProcessMessages;
    until LiRet <> WAIT_TIMEOUT;

    // 起動したらプロセス ID を取得
    AProcessID := GetProcessId(LProcessInfo.hProcess);

    //起動したプロセスが終了するまで待つ
    while WaitForSingleObject(LProcessInfo.hProcess, 100) = WAIT_TIMEOUT do
      Application.ProcessMessages;

    GetExitCodeProcess(LProcessInfo.hProcess, Result);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  コマンドプロンプトを表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  // 複数起動させない場合
  if CheckValidProcess(FProcessID, False) then Exit;

  Button1.Enabled := False;
  WinExecAndWait32V2('Cmd.exe /k cd /d c:\', SW_SHOWNORMAL, FProcessID);
  // 起動したアプリが終了すると以下が実行される
  FProcessID := 0;
  Button1.Enabled := True;
end;

//=============================================================================
//  このアプリで生成したプロセスを強制終了
//  本サンプルではコマンドプロンプトを終了
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  CheckValidProcess(FProcessID, True);
  FProcessID := 0;
end;

//-----------------------------------------------------------------------------
//  指定したプロセス ID のプロセスが起動しているかを検出
//  強制終了するかは引数で指定
//
//  戻り値が False の場合
//  対象のプロセスは存在しない
//  他の方法で終了していることもある
//  目的の処理が終了すると自動的に終了するプログラムもある
//-----------------------------------------------------------------------------
function TForm1.CheckValidProcess(APID: Cardinal; ATerminate: Boolean): Boolean;
var
  LhProcHandle : Cardinal;
begin
  if APID = 0 then begin
    Result := False;
    Exit;
  end;

  // Terminate(プロセスの終了)アクセスモードでOpenProcess
  LhProcHandle := OpenProcess(PROCESS_TERMINATE, False, APID);
  Result := LhProcHandle <> 0;
  if Result and ATerminate then begin
    try
      TerminateProcess(LhProcHandle, 0);
    finally
      CloseHandle(LhProcHandle);
    end;
  end;
end;

end.