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

后端 未结 8 2152
一向
一向 2021-02-09 05:52

I am writing a standard windows app in Delphi 7.

If I was writing a console app, I can call the following to output to the cmd line or output file.

write         


        
相关标签:
8条回答
  • 2021-02-09 06:40

    This question is very similar (if not exactly the same) as something I was trying to accomplish. I wanted to detect if my app was executed from a cmd.exe and send output to the parent console, otherwise it would display a gui. The answers here helped me solve my issue. Here is the code I came up with as an experiment:

    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 (form with label):

    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.
    

    This allows me to run my GUI app from a command prompt and display output to the same console where my app was launched. Otherwise, it will run the full GUI part of the app.

    Example output from console window:

    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>
    
    0 讨论(0)
  • 2021-02-09 06:43

    I found this very complete article about the whole issue: http://www.boku.ru/2016/02/28/posting-to-console-from-gui-app/

    I made a unit to do the AttachConsole, hook the exception handler to mirror messages to console.

    To use it, you only need to call ATTACH in your code. It is best to make attaching a commandline option e.g -console

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

    This is for a gui application, and when using this, you must use START /W to launch your program is you expect it to be blocking on the commandline/batch e.g. start /w myprogram.exe -console

    One handy benefit is that you can launch it standalone with a console if you want, and get to see all the error messages in the 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.
    
    0 讨论(0)
提交回复
热议问题