Subversion Repositories fastphp

Rev

Rev 31 | Rev 40 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit RunPHP;
  2.  
  3. interface
  4.  
  5. uses
  6.   SysUtils, Forms, Classes, Windows;
  7.  
  8. type
  9.   TInputRequestCallback = function: AnsiString of object;
  10.   TOutputNotifyCallback = procedure(const output: AnsiString) of object;
  11.   TRunCodeExplorer = class(TThread)
  12.   private
  13.     FInputRequestCallback: TInputRequestCallback;
  14.     FOutputNotifyCallback: TOutputNotifyCallback;
  15.     FInputWaiting: AnsiString;
  16.     FOutputWaiting: AnsiString;
  17.   protected
  18.     procedure CallInputRequestCallback;
  19.     procedure CallOutputNotifyCallback;
  20.   public
  21.     PhpExe: string;
  22.     PhpFile: string;
  23.     WorkDir: string;
  24.     property InputRequestCallback: TInputRequestCallback read FInputRequestCallback write FInputRequestCallback;
  25.     property OutputNotifyCallback: TOutputNotifyCallback read FOutputNotifyCallback write FOutputNotifyCallback;
  26.     procedure Execute; override;
  27.   end;
  28.  
  29. implementation
  30.  
  31. uses
  32.   CRC32;
  33.  
  34. procedure TRunCodeExplorer.Execute;
  35.  
  36.   function ProcessRunning(PI: TProcessInformation): boolean; inline;
  37.   var
  38.     exitcode: Cardinal;
  39.   begin
  40.     result := GetExitCodeProcess(PI.hProcess, exitcode) and (exitcode = STILL_ACTIVE);
  41.   end;
  42.  
  43. var
  44.   SA: TSecurityAttributes;
  45.   SI: TStartupInfo;
  46.   PI: TProcessInformation;
  47.   StdOutPipeRead, StdOutPipeWrite: THandle;
  48.   StdInPipeRead, StdInPipeWrite: THandle;
  49.   WasOK: Boolean;
  50.   Buffer: array[0..255] of AnsiChar;
  51.   BytesRead, BytesWritten: Cardinal;
  52.   WorkDir: string;
  53.   Handle: Boolean;
  54.   testString: AnsiString;
  55.   CommandLine: string;
  56.   result: string;
  57. begin
  58.   if Self.WorkDir = '' then
  59.     WorkDir := ExtractFilePath(ParamStr(0))
  60.   else
  61.     WorkDir := Self.WorkDir;
  62.  
  63.   if not FileExists(Self.PhpExe) then exit;
  64.   if not FileExists(Self.PhpFile) then exit;
  65.  
  66.   CommandLine := '"'+Self.PhpExe+'" "'+Self.PhpFile+'"';
  67.  
  68.   Result := '';
  69.   with SA do begin
  70.     nLength := SizeOf(SA);
  71.     bInheritHandle := True;
  72.     lpSecurityDescriptor := nil;
  73.   end;
  74.   CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
  75.   CreatePipe(StdInPipeRead, StdInPipeWrite, @SA, 0);
  76.   try
  77.     with SI do
  78.     begin
  79.       FillChar(SI, SizeOf(SI), 0);
  80.       cb := SizeOf(SI);
  81.       dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
  82.       wShowWindow := SW_HIDE;
  83.       hStdInput := StdInPipeRead;
  84.       hStdOutput := StdOutPipeWrite;
  85.       hStdError := StdOutPipeWrite;
  86.     end;
  87.  
  88.     Handle := CreateProcess(nil, PChar('cmd.exe /C "' + CommandLine + '"'),
  89.                             nil, nil, True, 0, nil, PChar(WorkDir), SI, PI);
  90.     CloseHandle(StdOutPipeWrite);
  91.  
  92.     sleep(100);
  93.  
  94.     if Handle then
  95.       try
  96.         while not Self.Terminated and ProcessRunning(PI) do
  97.         begin
  98.           {$REGION 'Get input from mainthread'}
  99.           if Assigned(FInputRequestCallback) then
  100.           begin
  101.             Synchronize(CallInputRequestCallback);
  102.  
  103.             // Attention: This call will block if the process exited
  104.             WriteFile(StdInPipeWrite, FInputWaiting[1], Length(FInputWaiting), BytesWritten, nil);
  105.           end;
  106.           {$ENDREGION}
  107.  
  108.           {$REGION 'Terminate input sequence'}
  109.           testString := #13#10#1#2#3#4#5#6#7#8#13#10;
  110.           WriteFile(StdInPipeWrite, testString[1], Length(testString), BytesWritten, nil);
  111.           {$ENDREGION}
  112.  
  113.           {$REGION 'Gather output'}
  114.           result := '';
  115.           repeat
  116.             WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
  117.             if BytesRead > 0 then
  118.             begin
  119.               Buffer[BytesRead] := #0;
  120.               Result := Result + Buffer;
  121.               if Pos(#1#2#3#4#5#6#7#8, result) >= 1 then
  122.               begin
  123.                 result := StringReplace(result, #1#2#3#4#5#6#7#8, '', []);
  124.                 break;
  125.               end;
  126.             end;
  127.           until not WasOK or (BytesRead = 0) or Self.Terminated or not ProcessRunning(PI);
  128.           {$ENDREGION}
  129.  
  130.           {$REGION 'Notify main thread about output'}
  131.           if Assigned(FOutputNotifyCallback) and not Self.Terminated and ProcessRunning(PI) then
  132.           begin
  133.             FOutputWaiting := IntToHex(CalculateCRC32String(result), 8) + result;
  134.             Synchronize(CallOutputNotifyCallback);
  135.           end;
  136.           {$ENDREGION}
  137.         end;
  138.  
  139.         CloseHandle(StdInPipeWrite);
  140.         TerminateProcess(pi.hProcess, 0);  // TODO: for some reason, after closing the editor, php.exe keeps running in the task list
  141.         WaitForSingleObject(PI.hProcess, INFINITE);
  142.       finally
  143.         CloseHandle(PI.hThread);
  144.         CloseHandle(PI.hProcess);
  145.       end;
  146.   finally
  147.     CloseHandle(StdOutPipeRead);
  148.     CloseHandle(StdInPipeRead);
  149.   end;
  150. end;
  151.  
  152. procedure TRunCodeExplorer.CallInputRequestCallback;
  153. begin
  154.   FInputWaiting := FInputRequestCallback;
  155. end;
  156.  
  157. procedure TRunCodeExplorer.CallOutputNotifyCallback;
  158. begin
  159.   FOutputNotifyCallback(FOutputWaiting);
  160. end;
  161.  
  162. end.
  163.