簡體   English   中英

如何從命令行啟動的GUI應用程序寫入StdOut?

[英]How do I write to StdOut from a GUI app started from the command line?

我正在Delphi 7中編寫一個標准的Windows應用程序。

如果我正在編寫控制台應用程序,我可以調用以下內容輸出到cmd行或輸出文件。

writeln('Some info');

如果我從我從命令行啟動的標准GUI應用程序執行此操作,則會出現錯誤。

I/O Error 105

必須有一個簡單的解決方案來解決這個問題。 基本上我希望我的應用程序有兩種模式,GUI模式和非GUI模式。 如何正確設置以便我可以回寫cmd窗口?

這個問題與我試圖完成的事情非常相似(如果不完全相同)。 我想檢測我的應用程序是否是從cmd.exe執行並將輸出發送到父控制台,否則它將顯示一個gui。 這里的答案幫助我解決了我的問題。 這是我作為實驗提出的代碼:

ParentChecker.dpr

program ParentChecker;

uses
  Vcl.Forms,
  SysUtils,
  PsAPI,
  Windows,
  TLHelp32,
  Main in 'Main.pas' {frmParentChecker};

{$R *.res}

function AttachConsole(dwProcessID: Integer): Boolean; stdcall; external 'kernel32.dll';
function FreeConsole(): Boolean; stdcall; external 'kernel32.dll';

function GetParentProcessName(): String;
const
  BufferSize = 4096;
var
  HandleSnapShot: THandle;
  EntryParentProc: TProcessEntry32;
  CurrentProcessId: THandle;
  HandleParentProc: THandle;
  ParentProcessId: THandle;
  ParentProcessFound: Boolean;
  ParentProcPath: String;
begin
  ParentProcessFound:=False;
  HandleSnapShot:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
  if HandleSnapShot<>INVALID_HANDLE_VALUE then
  begin
    EntryParentProc.dwSize:=SizeOf(EntryParentProc);
    if Process32First(HandleSnapShot,EntryParentProc) then
    begin
      CurrentProcessId:=GetCurrentProcessId();
      repeat
        if EntryParentProc.th32ProcessID=CurrentProcessId then
        begin
          ParentProcessId:=EntryParentProc.th32ParentProcessID;
          HandleParentProc:=OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,False,ParentProcessId);
          if HandleParentProc<>0 then
          begin
            ParentProcessFound:=True;
            SetLength(ParentProcPath,BufferSize);
            GetModuleFileNameEx(HandleParentProc,0,PChar(ParentProcPath),BufferSize);
            ParentProcPath:=PChar(ParentProcPath);
            CloseHandle(HandleParentProc);
          end;
          Break;
        end;
      until not Process32Next(HandleSnapShot,EntryParentProc);
    end;
    CloseHandle(HandleSnapShot);
  end;
  if ParentProcessFound then Result:=ParentProcPath
  else Result:='';
end;

function IsPrime(n: Integer): Boolean;
var
  i: Integer;
begin
  Result:=False;
  if n<2 then Exit;
  Result:=True;
  if n=2 then Exit;
  i:=2;
  while i<(n div i + 1) do
  begin
    if (n mod i)=0 then
    begin
      Result:=False;
      Exit;
    end;
    Inc(i);
  end;
end;

var
  i: Integer;
  ParentName: String;

begin
  ParentName:=GetParentProcessName().ToLower;
  Delete(ParentName,1,ParentName.LastIndexOf('\')+1);
  if ParentName='cmd.exe' then
  begin
    AttachConsole(-1);
    Writeln('');
    for i:=1 to 100 do if IsPrime(i) then Writeln(IntToStr(i)+' is prime');
    FreeConsole();
  end
  else
  begin
    Application.Initialize;
    Application.MainFormOnTaskbar:=True;
    Application.CreateForm(TfrmParentChecker, frmParentChecker);
    frmParentChecker.Label1.Caption:='Executed from '+ParentName;
    Application.Run;
  end;
end.

Main.pas(帶標簽的表格):

unit Main;

interface

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

type
  TfrmParentChecker = class(TForm)
    Label1: TLabel;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmParentChecker: TfrmParentChecker;

implementation

{$R *.dfm}

end.

這允許我從命令提示符運行我的GUI應用程序並將輸出顯示到我的應用程序啟動的同一控制台。 否則,它將運行應用程序的完整GUI部分。

控制台窗口的輸出示例:

I:\Delphi\Tests and Demos\ParentChecker\Win32\Debug>start /wait ParentChecker.exe

2 is prime
3 is prime
5 is prime
7 is prime
11 is prime
13 is prime
17 is prime
19 is prime
23 is prime
29 is prime
31 is prime
37 is prime
41 is prime
43 is prime
47 is prime
53 is prime
59 is prime
61 is prime
67 is prime
71 is prime
73 is prime
79 is prime
83 is prime
89 is prime
97 is prime

I:\Delphi\Tests and Demos\ParentChecker\Win32\Debug>

調用AllocConsole避免錯誤105。

GUI子系統應用程序沒有可靠的方法來附加到其父進程的控制台。 如果您嘗試這樣做,最終會有兩個共享同一控制台的活動進程。 這導致了麻煩。

根據bummi的建議,替代方案雖然只保留了一個可執行文件,但是如果要求它以GUI模式運行,則可以使用一個控制台應用程序釋放其控制台。 這是一種更好的方法,但是當您想要在GUI模式下運行時,會導致控制台窗口閃爍,然后關閉。

我在Stack Overflow上遇到的關於這個主題的最佳討論是Rob Kennedy的極好答案: 一個可執行文件可以同時是控制台和GUI應用程序嗎?

我相信,根據您在評論中的說法,您最好的選擇是創建兩個單獨的可執行文件。 一個用於GUI子系統,另一個用於控制台子系統。 這是采取的方法:

  • Java:java.exe,javaw.exe。
  • Python:python.exe,pythonw.exe。
  • Visual Studio:devenv.com,devenv.exe。

是的,你必須運送多個可執行文件。 但這樣做可以為用戶提供最佳體驗。

我不太確定你想要達到的目的。
我理解這個問題的方法可能是

program Project1;
{$APPTYPE CONSOLE}

uses
  Forms, Classes, Windows,
  Unit1 in 'Unit1.pas' { Form1 } ;
{$R *.res}

var
  Finished: Boolean;
  Input: String;

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

procedure HandleInput;
begin
  Finished := Input = 'quit';
  if not Finished then
  begin
    Writeln('Echo: ' + Input);
  end
  else
    Writeln('Bye');
end;

begin
  if IsConsoleMode then
  begin
    Finished := false;
    Writeln('Welcome to console mode');
    while not Finished do
    begin
      readln(Input);
      HandleInput;
    end;
  end
  else
  begin
    Writeln('Entering GUI Mode');
    FreeConsole;
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TForm1, Form1);
    Application.Run;
  end;

end.

FWIW,我玩弄了這個問題,發生在AttachConsole上 ,這似乎可以解決問題。 我遇到的唯一問題就是程序在沒有額外的一兩個ENTER鍵的情況下不會給控制台。 自從我試圖解決這個問題並且(有點)放棄以來,這並不是真正的拋光。 也許這里有人會看到它?

program writecon; uses windows, dialogs;

  function AttachConsole(dwProcessID: DWord): BOOL; stdcall; external 'kernel32.dll';

  function load_attach_console: boolean;
    begin
      Result := AttachConsole(-1);
    end;

  begin
    // the function requires XP or greater, you might want to check for that here.
    if load_attach_console = true then
      begin
        writeln;
        writeln('This is running in the console.');
        write('Press ENTER to continue.');
        readln;
        // from the linked page, you have to detach yourself from the console
        // when you're done, this is probably where the problem is.
        Flush(Output);
        Flush(Input);
        FreeConsole;
      end
    else
      MessageDlg('This is not running in the console.', mtInformation, [mbOk], 0);
  end.

AttachConsole似乎工作,如上所述,它等待ENTER。

但是,程序仍然是一個勝利編程,而不是dos看到它的控制台程序,因此cmd在啟動后繼續執行下一個命令。

test.exe & dir

首先顯示目錄列表,然后顯示test.exe的輸出

start /w test.exe & dir 

確實有效,並且不會為ENTER鍵暫停

BTW,上面的建議:PostMessage(GetCurrentProcess,$ 0101,$ 0D,0); 是否輸入但是發出了響聲。

我發現這篇關於整個問題的完整文章: http//www.boku.ru/2016/02/28/posting-to-console-from-gui-app/

我創建了一個單元來執行AttachConsole,掛鈎異常處理程序以將消息鏡像到控制台。

要使用它,您只需要在代碼中調用ATTACH。 最好附加命令行選項,例如-console

if FindCmdLineSwitch('console',true) then AttachConsole(true,true);

這是一個gui應用程序,當使用它時,你必須使用START / W來啟動你的程序,你希望它在命令行/批處理上阻塞,例如start /w myprogram.exe -console

一個方便的好處是,如果需要,您可以使用控制台單獨啟動它,並在控制台中查看所有錯誤消息。

unit ConsoleConnector;
// Connects the/a console to a GUI program
// Can hook exception handler to mirror messages to console.
// To use it, you only need to call ATTACH
// best to make attaching a commandline option e.g -console
//    if FindCmdLineSwitch('console',true) then AttachConsole(true,true);
// When using this, you will use START to launch your program e.g.
// start /w myprogram.exe -console
// creates Console var at end in initialise/finalise - you might want to do this explicitly in your own program instead.
// see: http://www.boku.ru/2016/02/28/posting-to-console-from-gui-app/

//sjb 18Nov16

interface
uses sysutils,forms;

type
  TConsoleConnector = class
  private
    OldExceptionEvent:TExceptionEvent;
    Hooked:boolean;
    BlockApplicationExceptionHandler:boolean; //errors ONLY to console, no error messageboxes blocking program
    procedure DetachErrorHandler;
    procedure GlobalExceptionHandler(Sender: TObject; E: Exception);
    procedure HookExceptionHandler;
  public
    IsAttached:boolean;

    function Attach(
        CreateIfNeeded:boolean=true; //Call ALLOCCONSOLE if no console to attach to
        HookExceptions:boolean=false;  //Hook Application.OnException to echo all unhandled exceptions to console
        OnlyToConsole:boolean=false  // Suppresses exception popups in gui, errors only go to console
        ):boolean;
    procedure Detach;            //detach and unhook
    procedure writeln(S:string); //only writes if console is attached
    procedure ShowMessage(S:string); //Popup ShowMessage box and mirror to console. Obeys OnlyToConsole
  end;

  var Console:TConsoleConnector;

implementation

uses Windows,dialogs;

//winapi function
function AttachConsole(dwProcessId: Int32): boolean; stdcall; external kernel32 name 'AttachConsole';

function TConsoleConnector.Attach(CreateIfNeeded:boolean=true;HookExceptions:boolean=false;OnlyToConsole:boolean=false):boolean;
begin
  IsAttached:=AttachConsole(-1);
  if not IsAttached and CreateIfNeeded
    then begin
      IsAttached:=AllocConsole;
    end;
  result:=IsAttached;
  if HookExceptions then HookExceptionHandler;
end;

procedure TConsoleConnector.Detach;
begin
  FreeConsole;
  IsAttached:=false;
  DetachErrorHandler;
end;

procedure TConsoleConnector.WriteLn(S:string);
begin
  if IsAttached then system.writeln(S);
end;
procedure TConsoleConnector.ShowMessage(S:string);
begin
  self.Writeln(S);
  if BlockApplicationExceptionHandler then exit;
  dialogs.ShowMessage(S);
end;
procedure TConsoleConnector.GlobalExceptionHandler(Sender: TObject; E: Exception);
begin
  self.Writeln(E.Message);
  if BlockApplicationExceptionHandler then exit;
  if assigned(OldExceptionEvent) //i.e there was an old event before we hooked it
    then OldExceptionEvent(Sender,E)
    else Application.ShowException(E);
end;

procedure TConsoleConnector.HookExceptionHandler;
begin
  OldExceptionEvent:=Application.OnException;
  Application.OnException:=GlobalExceptionHandler;
  Hooked:=true;
end;

procedure TConsoleConnector.DetachErrorHandler;
begin
  if Hooked //I have hooked it
    then begin
      Application.OnException:=OldExceptionEvent;
      OldExceptionEvent:=nil;
      Hooked:=false;
    end;
end;

initialization
  Console:=TconsoleConnector.create;
finalization
  Console.Detach;
  Console.Destroy;
end.

我在一個帶有運行腳本的報告中總結了這個主題:

http://www.softwareschule.ch/download/maxbox_starter70.pdf作為第二個備份:

https://www.slideshare.net/maxkleiner1/nogui-maxbox-starter70

主例程有一個nativewriteline與writeline分開:

 for it:=1 to 50 do if IsPrime(it) then NativeWriteln(IntToStr(it)+' is prime');

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM