views:

673

answers:

2

I try several samples in the internet and none of them work - the scripts are not executed- (maybe because are for pre Delphi 2009 unicode?).

I need to run some python scripts and pass arguments to them, like:

python "..\Plugins\RunPlugin.py" -a login -u Test -p test

And capture the output to a string & the errors to other.

This is what I have now:

procedure RunDosInMemo(DosApp:String; var OutData: String);
var
  SA: TSecurityAttributes;
  SI: TStartupInfo;
  PI: TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite: THandle;
  WasOK: Boolean;
  Buffer: array[0..255] of Char;
  BytesRead: Cardinal;
  WorkDir: string;
  Handle: Boolean;
begin
  OutData := '';
  with SA do begin
    nLength := SizeOf(SA);
    bInheritHandle := True;
    lpSecurityDescriptor := nil;
  end;
  CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
  try
    with SI do
    begin
      FillChar(SI, SizeOf(SI), 0);
      cb := SizeOf(SI);
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES or CREATE_UNICODE_ENVIRONMENT;
      wShowWindow := SW_HIDE;
      hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
      hStdOutput := StdOutPipeWrite;
      hStdError := StdOutPipeWrite;
    end;
    WorkDir := 'C:\';
    Handle := CreateProcess(nil, PChar(DosApp),
                            nil, nil, True, 0, nil,
                            PChar(WorkDir), SI, PI);
    CloseHandle(StdOutPipeWrite);
    if Handle then
    begin
      try
        repeat
          WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
          if BytesRead > 0 then
          begin
            Buffer[BytesRead] := #0;
            OutData := OutData + String(Buffer);
          end;
        until not WasOK or (BytesRead = 0);
        WaitForSingleObject(PI.hProcess, INFINITE);
      finally
        CloseHandle(PI.hThread);
        CloseHandle(PI.hProcess);
      end;
    end else begin
      raise Exception.Create('Failed to load python plugin');
    end;
  finally
    CloseHandle(StdOutPipeRead);
  end;
end;
+4  A: 

I'm not certain the WaitForSingleObject is the way to go... I think its better to loop with GetExitCodeProcess(pi.hProcess,iExitCode) until iExitCode <> STILL_ACTIVE and then check for data on each pass through the loop.

The code as written does not operate under Delphi 2007 either, so its not a Delphi 2009 unicode issue.

Changing your inner loop to the following works:

if Handle then
begin
  try
    repeat
      WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
      for ix := 0 to BytesRead-1 do
        begin
          OutData := OutData + AnsiChar(Buffer[ix]);
        end;
      GetExitCodeProcess(pi.hProcess,iExit);
    until (iExit <> STILL_ACTIVE);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;

I made the following corrections/additions to the local variables:

Buffer: array[0..255] of byte;
iExit : Cardinal;
IX : integer;

I also moved the CloseHandle(StdOutPipeWrite) just before the close of the StdOutPipeRead.

skamradt
If the script didn't run before, those changes won't make the script run. The "for" loop runs one too many times; fix that, and the assignment of zero into Buffer[BytesRead] no longer has any effect. Avoid the type-cast by declaring Buffer as an array of AnsiChar from the get-go. What's the purpose of your WaitForSingleObject call? You ignore the return value.
Rob Kennedy
The waitforsingleobject call is a place holder to "burn some time". A sleep would have worked too, but the waitforsingleobject with a timeout just says to wait UNLESS the application is no longer running. I was just trying to not hog the CPU in the loop.
skamradt
Ok, I copy this code from elsewhere, I have not skills in API development so I hardly understand what was going. Thanks for your input!
mamcx
The delay is unnecessary. ReadFile won't return until there's data available to read, or the pipe is broken.
Rob Kennedy
+5  A: 

Create_Unicode_Environment is a process creation flag, meant for use in the dwCreationFlags parameter of CreateFile. It is not a flag for use in the TStartupInfo record. API functions are liable to fail if you give them flag values they don't understand, and they're liable to do strange things if you give them flag values that mean something other than what you expected.

You declare a buffer of 256 Chars; recall that Char in Delphi 2009 is a 2-byte Unicode type. You then call ReadFile and tell it that the buffer is 255 bytes long instead of the real value, 512. When the documentation says that a value is the number of bytes, take that as your cue to use the SizeOf function.

Since ReadFile reads bytes, it would be a good idea to declare your buffer array to be an array of byte-sized elements, such as AnsiChar. That way, when you set Buffer[BytesRead], you won't include twice the data you actually read.

The Unicode version of CreateProcess may modify its command-line argument. You must ensure that the string you pass to that parameter has a reference count of 1. Call UniqueString(DosApp) before you call CreateProcess.

When an API function fails, you will of course want to know why. Don't just make up a reason. Use the functions provided, such as Win32Check and RaiseLastOSError. At the very least, call GetLastError, like MSDN tells you to. Don't throw a generic exception type when a more specific one is readily available.

Rob Kennedy