7

Delphiコンソールアプリケーションは、既存のコンソールウィンドウのコマンドラインから実行でき、アイコンをダブルクリックして実行できます。後者の場合、独自のコンソールウィンドウを作成し、アプリケーションが終了したら閉じます。

コンソールアプリケーションが独自のウィンドウを作成したかどうかを確認するにはどうすればよいですか?

これを検出して、「Enterキーを押してウィンドウを閉じる」などのメッセージを表示し、ウィンドウが閉じる前に表示されている内容をユーザーに読み取らせることができるようにします。明らかに、アプリケーションがコマンドラインから実行されている場合は、これを行うのは適切ではありません。

重要な場合に備えて、私はDelphi2010を使用しています。

4

6 に答える 6

8

基本的に、テストするものが2つあります。

  1. アプリケーションコンソールはプロセス間で共有されていますか?コンソールアプリケーションの実行に使用する場合cmd.exe、デフォルトでコンソールを共有するため、「Enterキーを押してウィンドウを閉じる」というメッセージを表示する必要はありません。

  2. 出力はファイルにリダイレクトされますか?その場合、メッセージを表示する必要もありません。

GetConsoleProcessList()最初のものには、 WindowsAPI関数の形式の簡単なソリューションがあります。残念ながら、Windows XP以降のバージョンでのみ使用できますが、それで十分かもしれません。Delphi 2009Windowsユニットには含まれていないため、自分でインポートする必要があります。

function GetConsoleProcessList(lpdwProcessList: PDWORD;
  dwProcessCount: DWORD): DWORD; stdcall; external 'kernel32.dll';

もちろん、ソフトウェアが以前のバージョンのWindowsで実行できる場合はLoadLibrary()GetProcAddress()代わりに使用する必要があります。

プロセスハンドルの数が1より大きいかどうかだけに関心があるので、たとえば次のように、ハンドル用の非常に小さなバッファーで呼び出すことができます。

var
  HandleCount: DWORD;
  ProcessHandle: DWORD;
begin
  HandleCount := GetConsoleProcessList(@ProcessHandle, 1);
  // ...
end;

ハンドル数が1より大きい場合は、コンソールを開いたままにする他のプロセスがあるため、メッセージの表示をスキップできます。

Windows API関数を使用してGetFileInformationByHandle()、コンソール出力ハンドルが実際のファイルを参照しているかどうかを確認できます。

var
  StdOutHandle: THandle;
  IsNotRedirected: boolean;
  FileInfo: TByHandleFileInformation;
begin
  StdOutHandle := GetStdHandle(STD_OUTPUT_HANDLE);
  IsNotRedirected := not GetFileInformationByHandle(StdOutHandle, FileInfo)
    and (GetLastError = ERROR_INVALID_HANDLE);
  // ...
end;

このコードはあなたが始めることだけを目的としています、私はいくつかのコーナーケースが適切に処理されていないことを確信しています。

于 2009-09-27T11:12:39.027 に答える
5

私は過去に以下のようなものを使用しました:


program ConsoleTest;
{$APPTYPE CONSOLE}
uses Windows;
function GetConsoleWindow: HWND; stdcall; external kernel32 name 'GetConsoleWindow';
function IsOwnConsoleWindow: Boolean;
//ONLY POSSIBLE FOR CONSOLE APPS!!!
//If False, we're being called from the console;
//If True, we have our own console (we weren't called from console)
var pPID: DWORD;
begin
  GetWindowThreadProcessId (GetConsoleWindow,pPID);
  Result:= (pPID = GetCurrentProcessId);
end;

begin writeln ('Hello '); if IsOwnConsoleWindow then begin writeln ('Press enter to close console'); readln; end; end.

于 2009-09-27T11:38:23.417 に答える
2

私は知っています、これは古いスレッドですが、私はこれに対する素晴らしい解決策を持っています。

バッチファイルをいじくり回す必要はありません。秘訣はexeの種類にあり、それはサブシステム属性です。exeをGUIアプリケーションとしてコンパイルした後({$ APPTYPE CONSOLE}ディレクティブなしで、そのサブシステム属性IMAGE_SUBSYSTEM_WINDOWS_GUIをIMAGE_SUBSYSTEM_WINDOWS_CUIに変更する必要があります。コンソールからコンソールアプリを実行すると、追加のコンソールウィンドウが表示されません。その時点では、「Enterキーを押してウィンドウを閉じる」などのメッセージは必要ありません。編集:私のプロジェクトで行ったように、コンソールアプリ内で別のコンソールアプリを起動する場合)

エクスプローラーなどからクリックまたはstart|runで実行すると、サブシステム属性がIMAGE_SUBSYSTEM_WINDOWS_CUIの場合、Windowsは自動的にコンソールウィンドウを開きます。{$APPTYPECONSOLE}ディレクティブを指定する必要はありません。すべてはサブシステム属性に関するものです。

RRUZのソリューションは、私も使用しているソリューションですが、重要な違いが1つあります。親プロセスのサブシステムをチェックして、「Enterキーを押してこのウィンドウを閉じます」と表示します。RUZZのソリューションは、cmdまたはexplorerの2つの場合にのみ機能します。親プロセスの属性がNOTIMAGE_SUBSYSTEM_WINDOWS_CUIであるかどうかを確認するだけで、メッセージを表示できます。

しかし、exeサブシステムをチェックする方法は?PEヘッダー情報を取得して2つの関数setExeSubSys()とgetExeSubSys()に変更するための、トーリーのヒント(http://www.swissdelphicenter.ch/torry/showcode.php?id=1302 )の解決策を見つけました。setExeSubSys()を使用して、コンパイル後にexeのサブシステム属性を変更できるように小さなコンソールアプリを作成しました(わずか50 kbです!)。

親/潜在的なプロセスファイル名を取得したら、次のように簡単に実行できます。

    //In the very beginning in the app determine the parent process (as fast as is possible).
// later on you can do:
if( getExeSubSys( parentFilename ) <> IMAGE_SUBSYSTEM_WINDOWS_CUI ) then
 begin
  writeln( 'Press Enter to close the window' );
  readln;
 end;

これが私が作った2つの関数ですが、ストリームでは機能しません(torryの例のように)、私は愚かな例外のないものなしでそれのためのファイルのために私自身の簡単なユニットを使用します。しかし、基本的に私はあなたがそれの周りの考えを得ると思います。

設定するには(また、longint(nil)へのポインターを指定しない場合に取得するため):

type
 PLongInt = ^LongInt;

function setExeSubSys( fileName : string; pSubSystemId : PLongInt = nil ) : LongInt;
var
  signature: DWORD;
  dos_header: IMAGE_DOS_HEADER;
  pe_header: IMAGE_FILE_HEADER;
  opt_header: IMAGE_OPTIONAL_HEADER;
  f : TFile;

begin
 Result:=-1;
 FillChar( f, sizeOf( f ), 0 );
 if( fOpenEx( f, fileName, fomReadWrite )) and ( fRead( f, dos_header, SizeOf(dos_header)))
  and ( dos_header.e_magic = IMAGE_DOS_SIGNATURE ) then
  begin
   if( fSeek( f, dos_header._lfanew )) and ( fRead( f, signature, SizeOf(signature))) and ( signature = IMAGE_NT_SIGNATURE ) then
    begin
     if( fRead( f, pe_header, SizeOf(pe_header))) and ( pe_header.SizeOfOptionalHeader > 0 ) then
      begin
       if( fRead( f, opt_header, SizeOf(opt_header))) then
        begin
         if( Assigned( pSubSystemId )) then
         begin
          opt_header.Subsystem:=pSubSystemId^;
          if( fSeek( f, fPos( f )-SizeOf(opt_header) )) then
           begin
            if( fWrite( f, opt_header, SizeOf(opt_header)) ) then
             Result:=opt_header.Subsystem;
           end;
         end
        else Result:=opt_header.Subsystem;
        end;
      end;
    end;
  end;

 fClose( f );
end;

取得するため:

function GetExeSubSystem( fileName : string ) : LongInt;
var
  f         : TFile;
  signature : DWORD;
  dos_header: IMAGE_DOS_HEADER;
  pe_header : IMAGE_FILE_HEADER;
  opt_header: IMAGE_OPTIONAL_HEADER;

begin
 Result:=IMAGE_SUBSYSTEM_WINDOWS_CUI; // Result default is console app

 FillChar( f, sizeOf( f ), 0 );

 if( fOpenEx( f, fileName, fomRead )) and ( fRead( f, dos_header, SizeOf(dos_header)))
  and ( dos_header.e_magic = IMAGE_DOS_SIGNATURE ) then
  begin
   if( fSeek( f, dos_header._lfanew )) and ( fRead( f, signature, SizeOf(signature))) and ( signature = IMAGE_NT_SIGNATURE ) then
    begin
     if( fRead( f, pe_header, SizeOf(pe_header))) and ( pe_header.SizeOfOptionalHeader > 0 ) then
      begin
       if( fRead( f, opt_header, SizeOf(opt_header))) then
        Result:=opt_header.Subsystem;
      end;
    end;
  end;

 fClose( f );
end;

サブシステムで詳細情報が必要な場合は、グーグルで検索するか、MSDNWebサイトにアクセスしてください。それが誰にとっても役に立ったことを願っています。

グリーツ、アーウィン・ハンチェス

于 2010-08-26T16:54:21.860 に答える
2

私は使用します(どこで見つけたか思い出せません):

function WasRanFromConsole() : Boolean;
var
  SI: TStartupInfo;
begin
  SI.cb := SizeOf(TStartupInfo);
  GetStartupInfo(SI);

  Result := ((SI.dwFlags and STARTF_USESHOWWINDOW) = 0);
end;

そしてそれをそのように使用します:

  if (not WasRanFromConsole()) then
  begin
    Writeln('');
    Writeln('Press ENTER to continue');
    Readln;
  end;
于 2010-08-26T19:54:01.433 に答える
2

うわーニック、それは本当に印象的です!私はあなたのソリューションをテストし、うまく機能しています。

したがって、次のようなことができます。

function isOutputRedirected() : boolean;
var
  StdOutHandle     : THandle;
  bIsNotRedirected : boolean;
  FileInfo         : TByHandleFileInformation;

begin
  StdOutHandle:= GetStdHandle(STD_OUTPUT_HANDLE);
  bIsNotRedirected:=( NOT GetFileInformationByHandle(StdOutHandle, FileInfo)
    and (GetLastError = ERROR_INVALID_HANDLE));
  Result:=( NOT bIsNotRedirected );
end;

function isStartedFromConsole() : boolean;
var
  SI: TStartupInfo;
begin
  SI.cb := SizeOf(TStartupInfo);
  GetStartupInfo(SI);
  Result := ((SI.dwFlags and STARTF_USESHOWWINDOW) = 0);
end;

function GetConsoleSize() : _COORD;
var
  BufferInfo: TConsoleScreenBufferInfo;
begin
  GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), BufferInfo);
  Result.x:=BufferInfo.srWindow.Right - BufferInfo.srWindow.Left + 1;
  Result.y:=BufferInfo.srWindow.Bottom - BufferInfo.srWindow.Top + 1;
end;

そして最後に:

var
 cKey : Char;
 fCursorPos  : _COORD;

    if( NOT isOutputRedirected() ) and( NOT isStartedFromConsole() ) then
           begin
             // Windows app starts console.
             // Show message in yellow (highlight) and at the bottom of the window
            writeln;
            fCursorPos:=getConsoleSize();
            Dec( fCursorPos.y );
            Dec( fCursorPos.x, 40 );
            SetConsoleTextAttribute( GetStdHandle(STD_OUTPUT_HANDLE), 14 );
            SetConsoleCursorPosition( GetStdHandle(STD_OUTPUT_HANDLE), fCursorPos );
            write( '<< Press ENTER to close this window >>' );
            read(cKey);
           end;

歓声メイト!

アーウィン・ハンチェス

于 2010-08-27T15:31:13.610 に答える
1

プログラムfoo.exeの場合、 foo_runner.batという名前のバッチファイルを作成します。このコマンドは、名前で使用することを意図したものではないため、文書化しないでください。ただし、インストーラーが作成するショートカットアイコンのターゲットとして使用してください。その内容は単純になります:

@echo off
%~dp0\foo.exe %*
pause

その部分はバッチファイルが存在するディレクトリを提供するので、検索パス上の他の場所からfoo.exeを取得するのではなく、バッチファイルのディレクトリでfoo.exe%~dp0を実行することが保証されます。

于 2009-09-27T16:06:45.033 に答える