2006. szeptember 25., hétfő

Capturing all of the Output from a Console application (16 bit)


Problem/Question/Abstract:

In my article "Capturing all of the Output from a Console application (32 bit)" posted a function for retrieving all of the output of a console application. Unfortunately, though it worked fine on 32-bit apps it did not work well with 16-bit apps. This was not a problem with the code, but rather a bug in Windows (http://support.microsoft.com/support/kb/articles/Q150/9/56.ASP). But how can this be done?

Answer:

Here is new function I have been working on which seems to do the trick.  It bypasses the problem with 16-bit apps by directing windows to send the output to a text file, and then reads it back in, deletes the file, and sends the result back to you.  Be careful when calling this with command.com.  Because it waits on the process infinitely it will hang on this because command.com waits for user input...

Special thanks to Theo Bebekis for his help on this.

If you have questions or comments please email me  at johnwlong@characterlink.net, I have not had a chance to thoroughly test this version so any feed back would be helpful.

function GetConsoleOutput(const CommandLine: string): string;
var
  SA: TSecurityAttributes;
  SI: TStartupInfo;
  PI: TProcessInformation;
  StdOutFile, AppProcess, AppThread: THandle;
  RootDir, WorkDir, StdOutFileName: string;
const
  FUNC_NAME = 'GetConsoleOuput';
begin
  try
    StdOutFile := 0;
    AppProcess := 0;
    AppThread := 0;
    Result := '';

    // Initialize dirs
    RootDir := ExtractFilePath(ParamStr(0));
    WorkDir := ExtractFilePath(CommandLine);

    // Check WorkDir
    if not (FileSearch(ExtractFileName(CommandLine), WorkDir) <> '') then
      WorkDir := RootDir;

    // Initialize output file security attributes
    FillChar(SA, SizeOf(SA), #0);
    SA.nLength := SizeOf(SA);
    SA.lpSecurityDescriptor := nil;
    SA.bInheritHandle := True;

    // Create Output File
    StdOutFileName := RootDir + 'output.tmp';
    StdOutFile := CreateFile(PChar(StdOutFileName),
      GENERIC_READ or GENERIC_WRITE,
      FILE_SHARE_READ or FILE_SHARE_WRITE,
      @SA,
      CREATE_ALWAYS, // Always create it
      FILE_ATTRIBUTE_TEMPORARY or // Will cache in memory
      // if possible
      FILE_FLAG_WRITE_THROUGH,
      0);

    // Check Output Handle
    if StdOutFile = INVALID_HANDLE_VALUE then
      raise Exception.CreateFmt('Function %s() failed!' + #10#13 +
        'Command line = %s', [FUNC_NAME, CommandLine]);

    // Initialize Startup Info
    FillChar(SI, SizeOf(SI), #0);
    with SI do
    begin
      cb := SizeOf(SI);
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      wShowWindow := SW_HIDE;
      hStdInput := GetStdHandle(STD_INPUT_HANDLE);
      hStdError := StdOutFile;
      hStdOutput := StdOutFile;
    end;

    // Create the process
    if CreateProcess(nil, PChar(CommandLine), nil, nil,
      True, 0, nil,
      PChar(WorkDir), SI, PI) then
    begin
      WaitForSingleObject(PI.hProcess, INFINITE);
      AppProcess := PI.hProcess;
      AppThread := PI.hThread;
    end
    else
      raise Exception.CreateFmt('CreateProcess() in function %s() failed!'
        + #10#13 + 'Command line = %s', [FUNC_NAME, CommandLine]);

    CloseHandle(StdOutFile);
    StdOutFile := 0;

    with TStringList.Create do
    try
      LoadFromFile(StdOutFileName);
      Result := Text;
    finally
      Free;
    end;

  finally
    // Close handles
    if StdOutFile <> 0 then
      CloseHandle(StdOutFile);
    if AppProcess <> 0 then
      CloseHandle(AppProcess);
    if AppThread <> 0 then
      CloseHandle(AppThread);

    // Delete Output file
    if FileExists(StdOutFileName) then
      DeleteFile(StdOutFileName);
  end;

end;

Nincsenek megjegyzések:

Megjegyzés küldése