1496 lines
43 KiB
ObjectPascal
1496 lines
43 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
The contents of this file are subject to the Mozilla Public License
|
|
Version 1.1 (the "License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/MPL-1.1.html
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: JvSysComp.PAS, released Dec 26, 1999.
|
|
|
|
The Initial Developer of the Original Code is Petr Vones (petr dott v att mujmail dott cz)
|
|
Portions created by Petr Vones are Copyright (C) 1999 Petr Vones.
|
|
Portions created by Microsoft are Copyright (C) 1998, 1999 Microsoft Corp.
|
|
All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
Marcel van Brakel <brakelm att bart dott nl>.
|
|
Remko Bonte <remkobonte att myrealbox dott com> (redirect console output)
|
|
|
|
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
|
|
located at http://jvcl.sourceforge.net
|
|
|
|
Known Issues:
|
|
-----------------------------------------------------------------------------}
|
|
// $Id: JvCreateProcess.pas,v 1.30 2005/10/28 08:37:22 marquardt Exp $
|
|
|
|
unit JvCreateProcess;
|
|
|
|
{$I jvcl.inc}
|
|
{$I windowsonly.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF UNITVERSIONING}
|
|
JclUnitVersioning,
|
|
{$ENDIF UNITVERSIONING}
|
|
Windows, Messages, SysUtils, Classes,
|
|
{$IFDEF COMPILER5}
|
|
Forms,
|
|
{$ENDIF COMPILER5}
|
|
ShellAPI, SyncObjs,
|
|
JvComponentBase, JvTypes;
|
|
|
|
const
|
|
CCPS_BufferSize = 1024;
|
|
CCPS_MaxBufferSize = 65536;
|
|
|
|
type
|
|
EJvProcessError = EJVCLException;
|
|
|
|
TJvProcessPriority = (ppIdle, ppNormal, ppHigh, ppRealTime);
|
|
|
|
TJvConsoleOption = (coOwnerData, coRedirect);
|
|
TJvConsoleOptions = set of TJvConsoleOption;
|
|
|
|
TJvCPSRawReadEvent = procedure(Sender: TObject; const S: string) of object;
|
|
TJvCPSReadEvent = procedure(Sender: TObject; const S: string; const StartsOnNewLine: Boolean) of object;
|
|
TJvCPSTerminateEvent = procedure(Sender: TObject; ExitCode: DWORD) of object;
|
|
|
|
TJvRWHandles = record
|
|
Read: THandle;
|
|
Write: THandle;
|
|
end;
|
|
|
|
TJvRWEHandles = record
|
|
Read: THandle;
|
|
Write: THandle;
|
|
Error: THandle;
|
|
end;
|
|
|
|
TJvProcessEntry = class(TObject)
|
|
private
|
|
FFileName: TFileName;
|
|
FProcessID: DWORD;
|
|
FProcessName: string;
|
|
function GetSystemIconIndex(IconType: Integer): Integer;
|
|
function GetPriority: TJvProcessPriority;
|
|
procedure SetPriority(const Value: TJvProcessPriority);
|
|
public
|
|
constructor Create(AProcessID: DWORD; const AFileName: TFileName;
|
|
const AProcessName: string);
|
|
function Close(UseQuit: Boolean = False): Boolean;
|
|
class function PriorityText(Priority: TJvProcessPriority): string;
|
|
function Terminate: Boolean;
|
|
property FileName: TFileName read FFileName;
|
|
property LargeIconIndex: Integer index SHGFI_LARGEICON read
|
|
GetSystemIconIndex;
|
|
property Priority: TJvProcessPriority read GetPriority write SetPriority;
|
|
property ProcessID: DWORD read FProcessID;
|
|
property ProcessName: string read FProcessName;
|
|
property SmallIconIndex: Integer index SHGFI_SMALLICON read
|
|
GetSystemIconIndex;
|
|
end;
|
|
|
|
TJvCPSBuffer = array [0..CCPS_BufferSize - 1] of Char;
|
|
TJvCPSState = (psReady, psRunning, psWaiting);
|
|
TJvCPSFlag = (cfDefaultErrorMode, cfNewConsole, cfNewProcGroup, cfSeparateWdm,
|
|
cfSharedWdm, cfSuspended, cfUnicode, cfDetached);
|
|
TJvCPSFlags = set of TJvCPSFlag;
|
|
TJvCPSShowWindow = (swHide, swMinimize, swMaximize, swNormal);
|
|
|
|
TJvCPSStartupInfo = class(TPersistent)
|
|
private
|
|
FDesktop: string;
|
|
FTitle: string;
|
|
FDefaultPosition: Boolean;
|
|
FDefaultWindowState: Boolean;
|
|
FDefaultSize: Boolean;
|
|
FHeight: Integer;
|
|
FLeft: Integer;
|
|
FWidth: Integer;
|
|
FShowWindow: TJvCPSShowWindow;
|
|
FTop: Integer;
|
|
FForceOnFeedback: Boolean;
|
|
FForceOffFeedback: Boolean;
|
|
function GetStartupInfo: TStartupInfo;
|
|
protected
|
|
procedure AssignTo(Dest: TPersistent); override;
|
|
public
|
|
constructor Create;
|
|
property StartupInfo: TStartupInfo read GetStartupInfo;
|
|
published
|
|
property Desktop: string read FDesktop write FDesktop;
|
|
property Title: string read FTitle write FTitle;
|
|
property Left: Integer read FLeft write FLeft default 0;
|
|
property Top: Integer read FTop write FTop default 0;
|
|
property DefaultPosition: Boolean read FDefaultPosition write
|
|
FDefaultPosition default True;
|
|
property Width: Integer read FWidth write FWidth default 0;
|
|
property Height: Integer read FHeight write FHeight default 0;
|
|
property DefaultSize: Boolean read FDefaultSize write FDefaultSize default True;
|
|
property ShowWindow: TJvCPSShowWindow read FShowWindow write FShowWindow
|
|
default swNormal;
|
|
property DefaultWindowState: Boolean read FDefaultWindowState write
|
|
FDefaultWindowState default True;
|
|
property ForceOnFeedback: Boolean read FForceOnFeedback write
|
|
FForceOnFeedback default False;
|
|
property ForceOffFeedback: Boolean read FForceOffFeedback write
|
|
FForceOffFeedback default False;
|
|
end;
|
|
|
|
TJvCreateProcess = class(TJvComponent)
|
|
private
|
|
FApplicationName: string;
|
|
FCommandLine: string;
|
|
FCreationFlags: TJvCPSFlags;
|
|
FCurrentDirectory: string;
|
|
FEnvironment: TStringList;
|
|
FState: TJvCPSState;
|
|
FStartupInfo: TJvCPSStartupInfo;
|
|
FPriority: TJvProcessPriority;
|
|
FProcessInfo: TProcessInformation;
|
|
FWaitForTerminate: Boolean;
|
|
FConsoleOptions: TJvConsoleOptions;
|
|
FOnTerminate: TJvCPSTerminateEvent;
|
|
FOnRead: TJvCPSReadEvent;
|
|
FOnRawRead: TJvCPSRawReadEvent;
|
|
FWaitThread: TThread;
|
|
FReadThread: TThread;
|
|
FHandle: THandle;
|
|
FCurrentLine: string; // Last output of the console with no #10 char.
|
|
FCursorPosition: Integer; // Position of the cursor on FCurrentLine
|
|
FConsoleOutput: TStringList;
|
|
FParseBuffer: TJvCPSBuffer;
|
|
FExitCode: Cardinal;
|
|
FEndLock: TCriticalSection; // lock to synchronize ending of the threads
|
|
FStartsOnNewLine: Boolean;
|
|
function GetConsoleOutput: TStrings;
|
|
function GetEnvironment: TStrings;
|
|
procedure SetWaitForTerminate(const Value: Boolean);
|
|
procedure WaitThreadTerminated(Sender: TObject);
|
|
procedure ConsoleWaitThreadTerminated(Sender: TObject);
|
|
procedure ReadThreadTerminated(Sender: TObject);
|
|
procedure SetEnvironment(const Value: TStrings);
|
|
function GetHandle: THandle;
|
|
|
|
procedure BeginConsoleEnding;
|
|
procedure EndConsoleEnding;
|
|
protected
|
|
procedure CheckRunning;
|
|
procedure CheckNotWaiting;
|
|
procedure CloseProcessHandles;
|
|
procedure TerminateWaitThread;
|
|
procedure HandleReadEvent;
|
|
procedure ParseConsoleOutput(Data: PChar; ASize: Cardinal);
|
|
procedure DoReadEvent(const EndsWithNewLine: Boolean);
|
|
procedure DoRawReadEvent(Data: PChar; const ASize: Cardinal);
|
|
procedure DoTerminateEvent;
|
|
procedure WndProc(var Msg: TMessage);
|
|
property Handle: THandle read GetHandle;
|
|
procedure CloseRead;
|
|
procedure CloseWrite;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function CloseApplication(SendQuit: Boolean = False): Boolean;
|
|
procedure Run;
|
|
procedure StopWaiting;
|
|
procedure Terminate;
|
|
function Write(const S: string): Boolean;
|
|
function WriteLn(const S: string): Boolean;
|
|
property ProcessInfo: TProcessInformation read FProcessInfo;
|
|
property State: TJvCPSState read FState;
|
|
property ConsoleOutput: TStrings read GetConsoleOutput;
|
|
published
|
|
property ApplicationName: string read FApplicationName write FApplicationName;
|
|
property CommandLine: string read FCommandLine write FCommandLine;
|
|
property CreationFlags: TJvCPSFlags read FCreationFlags write FCreationFlags
|
|
default [];
|
|
property CurrentDirectory: string read FCurrentDirectory write
|
|
FCurrentDirectory;
|
|
property Environment: TStrings read GetEnvironment write SetEnvironment;
|
|
property Priority: TJvProcessPriority read FPriority write FPriority default
|
|
ppNormal;
|
|
property StartupInfo: TJvCPSStartupInfo read FStartupInfo write
|
|
FStartupInfo;
|
|
property WaitForTerminate: Boolean read FWaitForTerminate write
|
|
SetWaitForTerminate default True;
|
|
property ConsoleOptions: TJvConsoleOptions read FConsoleOptions write
|
|
FConsoleOptions default [coOwnerData];
|
|
property OnTerminate: TJvCPSTerminateEvent read FOnTerminate write
|
|
FOnTerminate;
|
|
property OnRead: TJvCPSReadEvent read FOnRead write FOnRead;
|
|
property OnRawRead: TJvCPSRawReadEvent read FOnRawRead write FOnRawRead;
|
|
end;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
const
|
|
UnitVersioning: TUnitVersionInfo = (
|
|
RCSfile: '$RCSfile: JvCreateProcess.pas,v $';
|
|
Revision: '$Revision: 1.30 $';
|
|
Date: '$Date: 2005/10/28 08:37:22 $';
|
|
LogPath: 'JVCL\run'
|
|
);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
implementation
|
|
|
|
uses
|
|
Math,
|
|
JclStrings,
|
|
JvVCL5Utils, JvJCLUtils, JvJVCLUtils, JvConsts, JvResources;
|
|
|
|
const
|
|
CM_READ = WM_USER + 1;
|
|
|
|
//MaxProcessCount = 4096;
|
|
ProcessPriorities: array [TJvProcessPriority] of DWORD =
|
|
(IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS,
|
|
REALTIME_PRIORITY_CLASS);
|
|
|
|
type
|
|
{ Threads which monitor the created process }
|
|
|
|
TJvWaitForProcessThread = class(TThread)
|
|
private
|
|
FExitCode: DWORD;
|
|
FCloseEvent: THandle;
|
|
FProcessHandle: THandle;
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
constructor Create(ProcessHandle: DWORD);
|
|
destructor Destroy; override;
|
|
procedure TerminateThread;
|
|
end;
|
|
|
|
TJvConsoleThread = class(TJvWaitForProcessThread)
|
|
private
|
|
// Write end of the pipe
|
|
FWriteHandle: THandle;
|
|
FWriteEvent: THandle;
|
|
// Critical sections to synchronize access to the buffers
|
|
FWriteLock: TCriticalSection;
|
|
// Fixed size buffer; maybe change to sizeable
|
|
FOutputBuffer: TJvCPSBuffer;
|
|
FOutputBufferEnd: Cardinal;
|
|
protected
|
|
procedure Execute; override;
|
|
function TryWrite: Boolean;
|
|
public
|
|
constructor Create(ProcessHandle: DWORD; AWriteHandle: THandle);
|
|
destructor Destroy; override;
|
|
function Write(const S: string): Boolean;
|
|
procedure CloseWrite;
|
|
end;
|
|
|
|
TJvReadThread = class(TThread)
|
|
private
|
|
// Read end of the pipe
|
|
FReadHandle: THandle;
|
|
// Critical sections to synchronize access to the buffers
|
|
FReadLock: TCriticalSection;
|
|
// Handle to the TJvCreateProcess
|
|
FDestHandle: THandle;
|
|
FPreBuffer: PChar;
|
|
FInputBuffer: PChar;
|
|
FInputBufferSize: Cardinal;
|
|
FInputBufferEnd: Cardinal;
|
|
protected
|
|
procedure CopyToBuffer(Buffer: PChar; ASize: Cardinal);
|
|
procedure Execute; override;
|
|
public
|
|
constructor Create(AReadHandle, ADestHandle: THandle);
|
|
destructor Destroy; override;
|
|
procedure CloseRead;
|
|
function ReadBuffer(var ABuffer: TJvCPSBuffer; out ABufferSize: Cardinal): Boolean;
|
|
procedure TerminateThread;
|
|
end;
|
|
|
|
//=== Local procedures =======================================================
|
|
|
|
var
|
|
GWinSrvHandle: HMODULE;
|
|
GTriedLoadWinSrvDll: Boolean;
|
|
|
|
const
|
|
WinSrvDllName = 'WINSRV.DLL';
|
|
|
|
function WinSrvHandle: HMODULE;
|
|
begin
|
|
if (GWinSrvHandle = 0) and not GTriedLoadWinSrvDll then
|
|
begin
|
|
GTriedLoadWinSrvDll := True;
|
|
|
|
GWinSrvHandle := SafeLoadLibrary(WinSrvDllName);
|
|
if GWinSrvHandle <> 0 then
|
|
FreeLibrary(GWinSrvHandle);
|
|
end;
|
|
Result := GWinSrvHandle;
|
|
end;
|
|
|
|
function IsConsoleWindow(AHandle: THandle): Boolean;
|
|
begin
|
|
Result := LongWord(GetWindowLong(AHandle, GWL_HINSTANCE)) = WinSrvHandle;
|
|
end;
|
|
|
|
function InternalCloseApp(ProcessID: DWORD; UseQuit: Boolean): Boolean;
|
|
type
|
|
PEnumWinRec = ^TEnumWinRec;
|
|
TEnumWinRec = record
|
|
ProcessID: DWORD;
|
|
PostQuit: Boolean;
|
|
FoundWin: Boolean;
|
|
end;
|
|
var
|
|
EnumWinRec: TEnumWinRec;
|
|
|
|
function EnumWinProc(Wnd: HWND; Param: PEnumWinRec): BOOL; stdcall;
|
|
var
|
|
PID, TID: DWORD;
|
|
begin
|
|
TID := GetWindowThreadProcessId(Wnd, @PID);
|
|
if PID = Param.ProcessID then
|
|
begin
|
|
if Param.PostQuit then
|
|
PostThreadMessage(TID, WM_QUIT, 0, 0)
|
|
else
|
|
if IsWindowVisible(Wnd) or IsConsoleWindow(Wnd) then
|
|
PostMessage(Wnd, WM_CLOSE, 0, 0);
|
|
Param.FoundWin := True;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
begin
|
|
EnumWinRec.ProcessID := ProcessID;
|
|
EnumWinRec.PostQuit := UseQuit;
|
|
EnumWinRec.FoundWin := False;
|
|
EnumWindows(@EnumWinProc, Integer(@EnumWinRec));
|
|
Result := EnumWinRec.FoundWin;
|
|
end;
|
|
|
|
function InternalTerminateProcess(ProcessID: DWORD): Boolean;
|
|
var
|
|
ProcessHandle: THandle;
|
|
begin
|
|
ProcessHandle := OpenProcess(PROCESS_TERMINATE, False, ProcessID);
|
|
OSCheck(ProcessHandle <> 0);
|
|
Result := TerminateProcess(ProcessHandle, 0);
|
|
CloseHandle(ProcessHandle);
|
|
end;
|
|
|
|
function SafeCloseHandle(var H: THandle): Boolean;
|
|
begin
|
|
if H <> 0 then
|
|
begin
|
|
Result := CloseHandle(H);
|
|
if Result then
|
|
H := 0;
|
|
end
|
|
else
|
|
Result := True;
|
|
end;
|
|
|
|
function ConstructPipe(var ConsoleHandles: TJvRWEHandles; var LocalHandles: TJvRWHandles): Boolean;
|
|
var
|
|
LHandles: TJvRWHandles;
|
|
LSecurityAttr: TSecurityAttributes;
|
|
LSecurityDesc: TSecurityDescriptor;
|
|
|
|
procedure CloseAllHandles;
|
|
begin
|
|
// Some error occurred; close all possibly created handles
|
|
SafeCloseHandle(ConsoleHandles.Read);
|
|
SafeCloseHandle(ConsoleHandles.Write);
|
|
SafeCloseHandle(ConsoleHandles.Error);
|
|
SafeCloseHandle(LocalHandles.Read);
|
|
SafeCloseHandle(LocalHandles.Write);
|
|
SafeCloseHandle(LHandles.Read);
|
|
SafeCloseHandle(LHandles.Write);
|
|
end;
|
|
|
|
begin
|
|
{ http://support.microsoft.com/default.aspx?scid=KB;EN-US;q190351& }
|
|
{ http://community.borland.com/article/0,1410,10387,00.html }
|
|
FillChar(LSecurityAttr, SizeOf(TSecurityAttributes), 0);
|
|
FillChar(ConsoleHandles, SizeOf(TJvRWEHandles), 0);
|
|
FillChar(LocalHandles, SizeOf(TJvRWHandles), 0);
|
|
FillChar(LHandles, SizeOf(TJvRWHandles), 0);
|
|
|
|
// Set up the security attributes struct.
|
|
LSecurityAttr.nLength := SizeOf(TSecurityAttributes);
|
|
if Win32Platform = VER_PLATFORM_WIN32_NT then
|
|
begin
|
|
// Initialize security descriptor (Windows NT)
|
|
InitializeSecurityDescriptor(@LSecurityDesc, SECURITY_DESCRIPTOR_REVISION);
|
|
SetSecurityDescriptorDacl(@LSecurityDesc, True, nil, False);
|
|
LSecurityAttr.lpSecurityDescriptor := @LSecurityDesc;
|
|
end
|
|
else
|
|
LSecurityAttr.lpSecurityDescriptor := nil;
|
|
LSecurityAttr.bInheritHandle := True;
|
|
|
|
// Create the child output pipe.
|
|
Result := CreatePipe(LHandles.Read, ConsoleHandles.Write, @LSecurityAttr, 0);
|
|
if not Result then
|
|
begin
|
|
CloseAllHandles;
|
|
Exit;
|
|
end;
|
|
|
|
{ Create a duplicate of the output write handle for the std error
|
|
write handle. This is necessary in case the child application
|
|
closes one of its std output handles.
|
|
}
|
|
Result := DuplicateHandle(GetCurrentProcess, ConsoleHandles.Write,
|
|
GetCurrentProcess,
|
|
@ConsoleHandles.Error, // Address of new handle.
|
|
0, True, // Make it inheritable.
|
|
DUPLICATE_SAME_ACCESS);
|
|
if not Result then
|
|
begin
|
|
CloseAllHandles;
|
|
Exit;
|
|
end;
|
|
|
|
// Create the child input pipe.
|
|
Result := CreatePipe(ConsoleHandles.Read, LHandles.Write, @LSecurityAttr, 0);
|
|
if not Result then
|
|
begin
|
|
CloseAllHandles;
|
|
Exit;
|
|
end;
|
|
|
|
{ Create new output read handle and the input write handles. Set
|
|
the Properties to FALSE. Otherwise, the child inherits the
|
|
properties and, as a result, non-closeable handles to the pipes
|
|
are created.
|
|
}
|
|
Result := DuplicateHandle(GetCurrentProcess, LHandles.Read,
|
|
GetCurrentProcess,
|
|
@LocalHandles.Read, // Address of new handle.
|
|
0, False, // Make it uninheritable.
|
|
DUPLICATE_SAME_ACCESS);
|
|
if not Result then
|
|
begin
|
|
CloseAllHandles;
|
|
Exit;
|
|
end;
|
|
|
|
Result := DuplicateHandle(GetCurrentProcess, LHandles.Write,
|
|
GetCurrentProcess,
|
|
@LocalHandles.Write, // Address of new handle.
|
|
0, False, // Make it uninheritable.
|
|
DUPLICATE_SAME_ACCESS);
|
|
if not Result then
|
|
begin
|
|
CloseAllHandles;
|
|
Exit;
|
|
end;
|
|
|
|
{ Okay, everything went as expected; now close inheritable copies of the
|
|
handles you do not want to be inherited.
|
|
}
|
|
SafeCloseHandle(LHandles.Read);
|
|
SafeCloseHandle(LHandles.Write);
|
|
end;
|
|
|
|
//=== { TJvProcessEntry } ====================================================
|
|
|
|
constructor TJvProcessEntry.Create(AProcessID: DWORD;
|
|
const AFileName: TFileName; const AProcessName: string);
|
|
begin
|
|
inherited Create;
|
|
FFileName := AFileName;
|
|
FProcessID := AProcessID;
|
|
FProcessName := AProcessName;
|
|
end;
|
|
|
|
function TJvProcessEntry.Close(UseQuit: Boolean): Boolean;
|
|
begin
|
|
Result := InternalCloseApp(ProcessID, UseQuit);
|
|
end;
|
|
|
|
function TJvProcessEntry.GetPriority: TJvProcessPriority;
|
|
var
|
|
ProcessHandle: THandle;
|
|
PriorityClass: DWORD;
|
|
begin
|
|
if ProcessID = 0 then
|
|
Result := ppNormal
|
|
else
|
|
begin
|
|
ProcessHandle := OpenProcess(PROCESS_ALL_ACCESS, False, ProcessID);
|
|
OSCheck(ProcessHandle <> 0);
|
|
try
|
|
PriorityClass := GetPriorityClass(ProcessHandle);
|
|
OSCheck(PriorityClass <> 0);
|
|
case PriorityClass of
|
|
NORMAL_PRIORITY_CLASS:
|
|
Result := ppNormal;
|
|
IDLE_PRIORITY_CLASS:
|
|
Result := ppIdle;
|
|
HIGH_PRIORITY_CLASS:
|
|
Result := ppHigh;
|
|
REALTIME_PRIORITY_CLASS:
|
|
Result := ppRealTime;
|
|
else
|
|
Result := ppNormal;
|
|
end;
|
|
finally
|
|
CloseHandle(ProcessHandle);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvProcessEntry.GetSystemIconIndex(IconType: Integer): Integer;
|
|
var
|
|
FileInfo: TSHFileInfo;
|
|
begin
|
|
FillChar(FileInfo, SizeOf(FileInfo), #0);
|
|
SHGetFileInfo(PChar(FileName), 0, FileInfo, SizeOf(FileInfo),
|
|
SHGFI_SYSICONINDEX or IconType);
|
|
Result := FileInfo.iIcon;
|
|
end;
|
|
|
|
class function TJvProcessEntry.PriorityText(Priority: TJvProcessPriority): string;
|
|
begin
|
|
case Priority of
|
|
ppIdle:
|
|
Result := RsIdle;
|
|
ppNormal:
|
|
Result := RsNormal;
|
|
ppHigh:
|
|
Result := RsHigh;
|
|
ppRealTime:
|
|
Result := RsRealTime;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvProcessEntry.SetPriority(const Value: TJvProcessPriority);
|
|
var
|
|
ProcessHandle: THandle;
|
|
begin
|
|
ProcessHandle := OpenProcess(PROCESS_SET_INFORMATION, False, ProcessID);
|
|
OSCheck(ProcessHandle <> 0);
|
|
try
|
|
OSCheck(SetPriorityClass(ProcessHandle, ProcessPriorities[Value]));
|
|
finally
|
|
CloseHandle(ProcessHandle);
|
|
end;
|
|
end;
|
|
|
|
function TJvProcessEntry.Terminate: Boolean;
|
|
begin
|
|
Result := InternalTerminateProcess(FProcessID);
|
|
end;
|
|
|
|
//=== { TJvCPSStartupInfo } ==================================================
|
|
|
|
constructor TJvCPSStartupInfo.Create;
|
|
begin
|
|
inherited Create;
|
|
FDefaultSize := True;
|
|
FDefaultPosition := True;
|
|
FDefaultWindowState := True;
|
|
FShowWindow := swNormal;
|
|
end;
|
|
|
|
procedure TJvCPSStartupInfo.AssignTo(Dest: TPersistent);
|
|
begin
|
|
if Dest is TJvCPSStartupInfo then
|
|
with TJvCPSStartupInfo(Dest) do
|
|
begin
|
|
FDesktop := Self.FDesktop;
|
|
FTitle := Self.FTitle;
|
|
FLeft := Self.FLeft;
|
|
FTop := Self.FTop;
|
|
FDefaultPosition := Self.FDefaultPosition;
|
|
FWidth := Self.FWidth;
|
|
FHeight := Self.FHeight;
|
|
FDefaultSize := Self.FDefaultSize;
|
|
FShowWindow := Self.FShowWindow;
|
|
FDefaultWindowState := Self.FDefaultWindowState;
|
|
FForceOnFeedback := Self.FForceOnFeedback;
|
|
FForceOffFeedback := Self.FForceOffFeedback;
|
|
end
|
|
else
|
|
inherited AssignTo(Dest);
|
|
end;
|
|
|
|
function TJvCPSStartupInfo.GetStartupInfo: TStartupInfo;
|
|
const
|
|
ShowWindowValues: array [TJvCPSShowWindow] of DWORD =
|
|
(SW_HIDE, SW_SHOWMINIMIZED, SW_SHOWMAXIMIZED, SW_SHOWNORMAL);
|
|
begin
|
|
FillChar(Result, SizeOf(TStartupInfo), #0);
|
|
with Result do
|
|
begin
|
|
cb := SizeOf(TStartupInfo);
|
|
if Length(FDesktop) > 0 then
|
|
lpDesktop := PChar(FDesktop);
|
|
if Length(FTitle) > 0 then
|
|
lpTitle := PChar(Title);
|
|
if not FDefaultPosition then
|
|
begin
|
|
dwX := FLeft;
|
|
dwY := FTop;
|
|
Inc(dwFlags, STARTF_USEPOSITION);
|
|
end;
|
|
if not FDefaultSize then
|
|
begin
|
|
dwXSize := FWidth;
|
|
dwYSize := FHeight;
|
|
Inc(dwFlags, STARTF_USESIZE);
|
|
end;
|
|
if not FDefaultWindowState then
|
|
begin
|
|
wShowWindow := ShowWindowValues[FShowWindow];
|
|
Inc(dwFlags, STARTF_USESHOWWINDOW);
|
|
end;
|
|
if FForceOnFeedback then
|
|
Inc(dwFlags, STARTF_FORCEONFEEDBACK);
|
|
if FForceOffFeedback then
|
|
Inc(dwFlags, STARTF_FORCEOFFFEEDBACK);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvWaitForProcessThread } ============================================
|
|
|
|
constructor TJvWaitForProcessThread.Create(ProcessHandle: DWORD);
|
|
begin
|
|
inherited Create(True);
|
|
FreeOnTerminate := True;
|
|
Priority := tpLower;
|
|
FCloseEvent := CreateEvent(nil, True, False, nil);
|
|
FProcessHandle := ProcessHandle;
|
|
end;
|
|
|
|
destructor TJvWaitForProcessThread.Destroy;
|
|
begin
|
|
SafeCloseHandle(FCloseEvent);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvWaitForProcessThread.Execute;
|
|
var
|
|
WaitHandles: array [0..1] of THandle;
|
|
begin
|
|
WaitHandles[0] := FCloseEvent;
|
|
WaitHandles[1] := FProcessHandle;
|
|
WaitForInputIdle(FProcessHandle, INFINITE);
|
|
case WaitForMultipleObjects(2, PWOHandleArray(@WaitHandles[0]), False, INFINITE) of
|
|
WAIT_OBJECT_0:
|
|
FExitCode := MAXDWORD;
|
|
WAIT_OBJECT_0 + 1:
|
|
GetExitCodeProcess(FProcessHandle, FExitCode);
|
|
else
|
|
RaiseLastOSError;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvWaitForProcessThread.TerminateThread;
|
|
begin
|
|
Terminate;
|
|
SetEvent(FCloseEvent);
|
|
end;
|
|
|
|
//=== { TJvReadThread } ======================================================
|
|
|
|
constructor TJvReadThread.Create(AReadHandle, ADestHandle: THandle);
|
|
begin
|
|
inherited Create(True);
|
|
|
|
FreeOnTerminate := True;
|
|
Priority := tpLower;
|
|
|
|
FReadLock := TCriticalSection.Create;
|
|
|
|
// Note: TJvReadThread is responsible for closing the FReadHandle
|
|
FReadHandle := AReadHandle;
|
|
FDestHandle := ADestHandle;
|
|
|
|
FInputBuffer := nil;
|
|
FInputBufferSize := CCPS_BufferSize;
|
|
FInputBufferEnd := 0;
|
|
ReallocMem(FInputBuffer, FInputBufferSize);
|
|
GetMem(FPreBuffer, CCPS_BufferSize);
|
|
end;
|
|
|
|
destructor TJvReadThread.Destroy;
|
|
begin
|
|
SafeCloseHandle(FReadHandle);
|
|
inherited Destroy;
|
|
{ It is (theoretically) possible that the inherited Destroy triggers an
|
|
OnTerminate event and the following fields can be accessed in the handler,
|
|
thus free them after the destroy.
|
|
}
|
|
ReallocMem(FInputBuffer, 0);
|
|
FReadLock.Free;
|
|
FreeMem(FPreBuffer);
|
|
end;
|
|
|
|
procedure TJvReadThread.CloseRead;
|
|
begin
|
|
FReadLock.Acquire;
|
|
try
|
|
SafeCloseHandle(FReadHandle);
|
|
finally
|
|
FReadLock.Release;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvReadThread.CopyToBuffer(Buffer: PChar; ASize: Cardinal);
|
|
// Copy data in Buffer (with size ASize) to FInputBuffer.
|
|
begin
|
|
FReadLock.Acquire;
|
|
try
|
|
if FInputBufferEnd + ASize > FInputBufferSize then
|
|
begin
|
|
// Safety check..
|
|
if FInputBufferSize > CCPS_MaxBufferSize then
|
|
// ..main thread seems to be blocked; flush the input buffer
|
|
FInputBufferEnd := 0
|
|
else
|
|
begin
|
|
// Need to upscale FInputBuffer
|
|
FInputBufferSize := FInputBufferSize * 2;
|
|
ReallocMem(FInputBuffer, FInputBufferSize);
|
|
end;
|
|
end;
|
|
|
|
// Do the copy
|
|
Move(Buffer[0], FInputBuffer[FInputBufferEnd], ASize);
|
|
Inc(FInputBufferEnd, ASize);
|
|
finally
|
|
FReadLock.Release;
|
|
end;
|
|
|
|
// Notify TJvCreateProcess that data has been read from the pipe
|
|
PostMessage(FDestHandle, CM_READ, 0, 0);
|
|
end;
|
|
|
|
procedure TJvReadThread.Execute;
|
|
// Read data from the pipe (FReadHandle) to FPreBuffer
|
|
var
|
|
BytesRead: Cardinal;
|
|
begin
|
|
while not Terminated do
|
|
begin
|
|
{ ReadFile will block until *some* data is available on the pipe }
|
|
if not ReadFile(FReadHandle, FPreBuffer[0], CCPS_BufferSize, BytesRead, nil) then
|
|
begin
|
|
// Only exit if last error is ERROR_BROKEN_PIPE, thus ignore other errors
|
|
if GetLastError = ERROR_BROKEN_PIPE then
|
|
// pipe done - normal exit path.
|
|
Exit;
|
|
end
|
|
else
|
|
CopyToBuffer(FPreBuffer, BytesRead);
|
|
end;
|
|
end;
|
|
|
|
function TJvReadThread.ReadBuffer(var ABuffer: TJvCPSBuffer;
|
|
out ABufferSize: Cardinal): Boolean;
|
|
// Copy FInputBuffer to ABuffer.
|
|
// This function is executed in the context of the main thread;
|
|
// FReadLock is for synchronization with the read thread.
|
|
begin
|
|
FReadLock.Acquire;
|
|
try
|
|
Result := FInputBufferEnd > 0;
|
|
if not Result then
|
|
Exit;
|
|
|
|
ABufferSize := Min(FInputBufferEnd, CCPS_BufferSize);
|
|
|
|
// Copy the data from FInputBuffer to ABuffer.
|
|
Move(FInputBuffer[0], ABuffer[0], ABufferSize);
|
|
|
|
// If not all data in FInputBuffer is copied to ABuffer, then place
|
|
// the data not copied at the begin of FInputBuffer.
|
|
if FInputBufferEnd > ABufferSize then
|
|
Move(FInputBuffer[ABufferSize], FInputBuffer[0],
|
|
FInputBufferEnd - ABufferSize);
|
|
|
|
Dec(FInputBufferEnd, ABufferSize);
|
|
finally
|
|
FReadLock.Release;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvReadThread.TerminateThread;
|
|
begin
|
|
Terminate;
|
|
CloseRead;
|
|
end;
|
|
|
|
//=== { TJvConsoleThread } ===================================================
|
|
|
|
constructor TJvConsoleThread.Create(ProcessHandle: DWORD;
|
|
AWriteHandle: THandle);
|
|
begin
|
|
inherited Create(ProcessHandle);
|
|
|
|
FWriteLock := TCriticalSection.Create;
|
|
|
|
// Note: TJvConsoleThread is responsible for closing the FWriteHandle
|
|
FWriteHandle := AWriteHandle;
|
|
|
|
FWriteEvent := CreateEvent(
|
|
nil, // No security attributes
|
|
True, // Manual reset
|
|
False, // Initial state
|
|
nil // No name
|
|
);
|
|
end;
|
|
|
|
destructor TJvConsoleThread.Destroy;
|
|
begin
|
|
SafeCloseHandle(FWriteHandle);
|
|
SafeCloseHandle(FWriteEvent);
|
|
inherited Destroy;
|
|
{ It is (theoretically) possible that the inherited Destroy triggers an
|
|
OnTerminate event and the following fields can be accessed in the handler,
|
|
thus free them after the destroy.
|
|
}
|
|
FWriteLock.Free;
|
|
end;
|
|
|
|
procedure TJvConsoleThread.CloseWrite;
|
|
begin
|
|
FWriteLock.Acquire;
|
|
try
|
|
SafeCloseHandle(FWriteHandle);
|
|
finally
|
|
FWriteLock.Release;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvConsoleThread.Execute;
|
|
var
|
|
WaitHandles: array [0..2] of THandle;
|
|
HandleCount: Cardinal;
|
|
begin
|
|
WaitHandles[0] := FCloseEvent;
|
|
WaitHandles[1] := FProcessHandle;
|
|
WaitHandles[2] := FWriteEvent;
|
|
HandleCount := 3;
|
|
|
|
WaitForInputIdle(FProcessHandle, INFINITE);
|
|
|
|
while not Terminated do
|
|
case WaitForMultipleObjects(HandleCount, PWOHandleArray(@WaitHandles[0]), False, INFINITE) of
|
|
WAIT_OBJECT_0:
|
|
begin
|
|
// Close event fired; exit
|
|
FExitCode := MAXDWORD;
|
|
Exit;
|
|
end;
|
|
WAIT_OBJECT_0 + 1:
|
|
begin
|
|
// process ended; exit
|
|
GetExitCodeProcess(FProcessHandle, FExitCode);
|
|
Exit;
|
|
end;
|
|
WAIT_OBJECT_0 + 2:
|
|
// Write event fired; try to write
|
|
if not TryWrite then
|
|
// No longer respond when write event fires
|
|
HandleCount := 2;
|
|
else
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
function TJvConsoleThread.TryWrite: Boolean;
|
|
// Write data in FOutputBuffer to the pipe (FWriteHandle)
|
|
// Result = False; if console or user has closed the pipe.
|
|
var
|
|
BytesWritten: Cardinal;
|
|
BytesToWrite: Cardinal;
|
|
begin
|
|
Result := True;
|
|
|
|
FWriteLock.Acquire;
|
|
try
|
|
try
|
|
{ Check handle inside lock, because it can be closed by another thread, by
|
|
calling CloseWrite }
|
|
if FWriteHandle = 0 then
|
|
Exit;
|
|
|
|
if FOutputBufferEnd <= 0 then
|
|
Exit;
|
|
|
|
BytesToWrite := FOutputBufferEnd;
|
|
|
|
if not WriteFile(FWriteHandle, FOutputBuffer, BytesToWrite, BytesWritten, nil) then
|
|
begin
|
|
{ WriteFile documentation on MSDN states that WriteFile returns
|
|
ERROR_BROKEN_PIPE if the console closes it's read handle, but that
|
|
seems incorrect; check it anyway }
|
|
if (GetLastError = ERROR_NO_DATA) or (GetLastError = ERROR_BROKEN_PIPE) then
|
|
// Pipe was closed (normal exit path).
|
|
SafeCloseHandle(FWriteHandle);
|
|
Exit;
|
|
end;
|
|
|
|
if BytesWritten <= 0 then
|
|
Exit;
|
|
|
|
if BytesWritten < BytesToWrite then
|
|
// Move unwritten tail to the begin of the buffer
|
|
Move(FOutputBuffer[BytesWritten], FOutputBuffer[0], BytesToWrite - BytesWritten);
|
|
|
|
Dec(FOutputBufferEnd, BytesWritten);
|
|
finally
|
|
Result := FWriteHandle <> 0;
|
|
if FOutputBufferEnd = 0 then
|
|
ResetEvent(FWriteEvent);
|
|
end;
|
|
finally
|
|
FWriteLock.Release;
|
|
end;
|
|
end;
|
|
|
|
function TJvConsoleThread.Write(const S: string): Boolean;
|
|
// Add S to FOutputBuffer; actual writing is done in TryWrite.
|
|
// This function is executed in the context of the main thread;
|
|
// FWriteLock is for synchronization with the write thread.
|
|
begin
|
|
if Length(S) <= 0 then
|
|
begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
|
|
FWriteLock.Acquire;
|
|
try
|
|
Result := FWriteHandle <> 0;
|
|
if not Result then
|
|
Exit;
|
|
|
|
Result := Cardinal(Length(S)) + FOutputBufferEnd <= CCPS_BufferSize;
|
|
if not Result then
|
|
Exit;
|
|
|
|
Move(PChar(S)^, FOutputBuffer[FOutputBufferEnd], Length(S));
|
|
Inc(FOutputBufferEnd, Length(S));
|
|
|
|
if FOutputBufferEnd > 0 then
|
|
// Notify the TJvConsoleThread that there is some data to write
|
|
SetEvent(FWriteEvent);
|
|
finally
|
|
FWriteLock.Release;
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvCreateProcess } ===================================================
|
|
|
|
constructor TJvCreateProcess.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FCreationFlags := [];
|
|
FEnvironment := TStringList.Create;
|
|
FPriority := ppNormal;
|
|
FState := psReady;
|
|
FWaitForTerminate := True;
|
|
FStartupInfo := TJvCPSStartupInfo.Create;
|
|
FConsoleOutput := TStringList.Create;
|
|
FConsoleOptions := [coOwnerData];
|
|
end;
|
|
|
|
destructor TJvCreateProcess.Destroy;
|
|
begin
|
|
TerminateWaitThread;
|
|
// CloseProcessHandles;
|
|
FreeAndNil(FEndLock);
|
|
FreeAndNil(FEnvironment);
|
|
FreeAndNil(FStartupInfo);
|
|
if FHandle <> 0 then
|
|
DeallocateHWndEx(FHandle);
|
|
inherited Destroy;
|
|
FConsoleOutput.Free;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.CheckNotWaiting;
|
|
begin
|
|
if FState = psWaiting then
|
|
raise EJvProcessError.CreateRes(@RsEProcessIsRunning);
|
|
end;
|
|
|
|
procedure TJvCreateProcess.CheckRunning;
|
|
begin
|
|
if FState = psReady then
|
|
raise EJvProcessError.CreateRes(@RsEProcessNotRunning);
|
|
end;
|
|
|
|
function TJvCreateProcess.CloseApplication(SendQuit: Boolean): Boolean;
|
|
begin
|
|
CheckRunning;
|
|
Result := InternalCloseApp(ProcessInfo.dwProcessId, SendQuit);
|
|
end;
|
|
|
|
procedure TJvCreateProcess.CloseProcessHandles;
|
|
begin
|
|
OSCheck(SafeCloseHandle(FProcessInfo.hProcess));
|
|
OSCheck(SafeCloseHandle(FProcessInfo.hThread));
|
|
end;
|
|
|
|
procedure TJvCreateProcess.CloseRead;
|
|
begin
|
|
if FReadThread is TJvReadThread then
|
|
TJvReadThread(FReadThread).CloseRead;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.CloseWrite;
|
|
begin
|
|
if FWaitThread is TJvConsoleThread then
|
|
TJvConsoleThread(FWaitThread).CloseWrite;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.ConsoleWaitThreadTerminated(Sender: TObject);
|
|
var
|
|
AllThreadsDone: Boolean;
|
|
begin
|
|
FExitCode := TJvWaitForProcessThread(Sender).FExitCode;
|
|
|
|
BeginConsoleEnding;
|
|
try
|
|
{ We only fire a TerminateEvent if both the read thread and the wait thread
|
|
have terminated; usually the read thread will terminate before the wait
|
|
thread; must be determined inside the lock (FEndLock) }
|
|
AllThreadsDone := FReadThread = nil;
|
|
|
|
// Indicates that the wait thread is done; must bo set inside lock.
|
|
FWaitThread := nil;
|
|
finally
|
|
EndConsoleEnding;
|
|
end;
|
|
|
|
if AllThreadsDone then
|
|
DoTerminateEvent;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.DoRawReadEvent(Data: PChar; const ASize: Cardinal);
|
|
var
|
|
S: string;
|
|
begin
|
|
if Assigned(FOnRawRead) then
|
|
begin
|
|
// Do copy because of possible #0's etc.
|
|
SetLength(S, ASize);
|
|
Move(Data^, PChar(S)^, ASize);
|
|
FOnRawRead(Self, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.DoReadEvent(const EndsWithNewLine: Boolean);
|
|
begin
|
|
// Notify user and update current line & cursor
|
|
if not (coOwnerData in ConsoleOptions) then
|
|
begin
|
|
if FStartsOnNewLine or (ConsoleOutput.Count = 0) then
|
|
ConsoleOutput.Add(FCurrentLine)
|
|
else
|
|
ConsoleOutput[ConsoleOutput.Count - 1] := FCurrentLine;
|
|
end;
|
|
if Assigned(FOnRead) then
|
|
FOnRead(Self, FCurrentLine, FStartsOnNewLine);
|
|
if EndsWithNewLine then
|
|
begin
|
|
FCurrentLine := '';
|
|
FCursorPosition := 0;
|
|
end;
|
|
FStartsOnNewLine := EndsWithNewLine;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.DoTerminateEvent;
|
|
begin
|
|
FState := psReady;
|
|
FreeAndNil(FEndLock);
|
|
CloseProcessHandles;
|
|
if Assigned(FOnTerminate) then
|
|
FOnTerminate(Self, FExitCode);
|
|
end;
|
|
|
|
procedure TJvCreateProcess.EndConsoleEnding;
|
|
begin
|
|
FEndLock.Leave;
|
|
end;
|
|
|
|
function TJvCreateProcess.GetConsoleOutput: TStrings;
|
|
begin
|
|
Result := FConsoleOutput;
|
|
end;
|
|
|
|
function TJvCreateProcess.GetEnvironment: TStrings;
|
|
begin
|
|
Result := FEnvironment;
|
|
end;
|
|
|
|
function TJvCreateProcess.GetHandle: THandle;
|
|
begin
|
|
if FHandle = 0 then
|
|
FHandle := AllocateHWndEx(WndProc);
|
|
Result := FHandle;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.HandleReadEvent;
|
|
var
|
|
ASize: Cardinal;
|
|
begin
|
|
{ Copy the data from the read thread to the this (main) thread and
|
|
parse the console output }
|
|
if FReadThread is TJvReadThread then
|
|
while Assigned(FReadThread) and
|
|
TJvReadThread(FReadThread).ReadBuffer(FParseBuffer, ASize) do
|
|
ParseConsoleOutput(FParseBuffer, ASize);
|
|
end;
|
|
|
|
procedure TJvCreateProcess.ParseConsoleOutput(Data: PChar; ASize: Cardinal);
|
|
var
|
|
P, Q: PChar;
|
|
|
|
procedure DoOutput;
|
|
{ Copy chunk [Q..P) to the current line & Update cursor position }
|
|
var
|
|
ChunkSize: Integer;
|
|
begin
|
|
ChunkSize := P - Q;
|
|
if ChunkSize <= 0 then
|
|
Exit;
|
|
|
|
// Does the chunck fit on the current line..
|
|
if Length(FCurrentLine) < FCursorPosition + ChunkSize then
|
|
// .. if not resize current line
|
|
SetLength(FCurrentLine, FCursorPosition + ChunkSize);
|
|
|
|
// Move the chunk to the current line
|
|
Move(Q^, (PChar(FCurrentLine) + FCursorPosition)^, ChunkSize);
|
|
|
|
// Update the cursor
|
|
Inc(FCursorPosition, ChunkSize);
|
|
end;
|
|
|
|
procedure DoTab;
|
|
begin
|
|
// Does the chunck (8 spaces) fit on the current line..
|
|
if Length(FCurrentLine) < FCursorPosition + 8 then
|
|
// .. if not resize current line }
|
|
SetLength(FCurrentLine, FCursorPosition + 8);
|
|
|
|
// Fill 8 spaces on the currentline at the cursor position
|
|
FillChar((PChar(FCurrentLine) + FCursorPosition)^, 8, #32);
|
|
|
|
// Update the cursor
|
|
Inc(FCursorPosition, 8);
|
|
end;
|
|
|
|
begin
|
|
DoRawReadEvent(Data, ASize);
|
|
|
|
P := Data;
|
|
Q := Data;
|
|
|
|
while Cardinal(P - Data) < ASize do
|
|
case P^ of
|
|
#0, #7: // NULL and BELL
|
|
begin
|
|
// Replace with space
|
|
P^ := #32;
|
|
Inc(P);
|
|
end;
|
|
Backspace:
|
|
begin
|
|
DoOutput;
|
|
Dec(FCursorPosition);
|
|
if FCursorPosition < 0 then
|
|
FCursorPosition := 0;
|
|
Inc(P);
|
|
Q := P;
|
|
end;
|
|
Tab:
|
|
begin
|
|
// Replace with 8 spaces
|
|
DoOutput;
|
|
DoTab;
|
|
Inc(P);
|
|
Q := P;
|
|
end;
|
|
Lf:
|
|
begin
|
|
DoOutput;
|
|
DoReadEvent(True);
|
|
Inc(P);
|
|
Q := P;
|
|
end;
|
|
Cr:
|
|
begin
|
|
DoOutput;
|
|
FCursorPosition := 0;
|
|
Inc(P);
|
|
Q := P;
|
|
end;
|
|
else
|
|
Inc(P);
|
|
end;
|
|
DoOutput;
|
|
DoReadEvent(False);
|
|
end;
|
|
|
|
procedure TJvCreateProcess.ReadThreadTerminated(Sender: TObject);
|
|
var
|
|
AllThreadsDone: Boolean;
|
|
begin
|
|
// Read for the last time data from the read thread
|
|
HandleReadEvent;
|
|
if FCurrentLine <> '' then
|
|
DoReadEvent(False);
|
|
|
|
BeginConsoleEnding;
|
|
try
|
|
{ We only fire a TerminateEvent if both the read thread and the wait thread
|
|
have terminated; usually the read thread will terminate before the wait
|
|
thread; must be determined inside the lock (FEndLock) }
|
|
AllThreadsDone := FWaitThread = nil;
|
|
|
|
// Indicates that the wait thread is done; must bo set inside lock.
|
|
FReadThread := nil;
|
|
finally
|
|
EndConsoleEnding;
|
|
end;
|
|
|
|
if AllThreadsDone then
|
|
DoTerminateEvent;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.Run;
|
|
const
|
|
CreationFlagsValues: array [TJvCPSFlag] of DWORD =
|
|
(CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP,
|
|
CREATE_SEPARATE_WOW_VDM, CREATE_SHARED_WOW_VDM, CREATE_SUSPENDED,
|
|
CREATE_UNICODE_ENVIRONMENT, DETACHED_PROCESS);
|
|
var
|
|
LConsoleHandles: TJvRWEHandles; // Handles which the console will use
|
|
LLocalHandles: TJvRWHandles; // Handles which we will use
|
|
LStartupInfo: TStartupInfo;
|
|
Flags: DWORD;
|
|
F: TJvCPSFlag;
|
|
AppName, CurrDir: PChar;
|
|
EnvironmentData: PChar;
|
|
DoRedirect: Boolean;
|
|
begin
|
|
CheckNotWaiting;
|
|
FState := psReady;
|
|
FStartsOnNewLine := True;
|
|
FCurrentLine := '';
|
|
FCursorPosition := 0;
|
|
DoRedirect := coRedirect in ConsoleOptions;
|
|
|
|
FillChar(FProcessInfo, SizeOf(FProcessInfo), #0);
|
|
FillChar(LLocalHandles, SizeOf(LLocalHandles), #0);
|
|
FillChar(LConsoleHandles, SizeOf(LConsoleHandles), #0);
|
|
|
|
Flags := ProcessPriorities[FPriority];
|
|
for F := Low(TJvCPSFlag) to High(TJvCPSFlag) do
|
|
if F in FCreationFlags then
|
|
Inc(Flags, CreationFlagsValues[F]);
|
|
AppName := Pointer(Trim(FApplicationName));
|
|
CurrDir := Pointer(Trim(FCurrentDirectory));
|
|
if Environment.Count = 0 then
|
|
EnvironmentData := nil
|
|
else
|
|
StringsToMultiSz(EnvironmentData, Environment);
|
|
|
|
try
|
|
LStartupInfo := FStartupInfo.GetStartupInfo;
|
|
|
|
if DoRedirect then
|
|
begin
|
|
if not ConstructPipe(LConsoleHandles, LLocalHandles) then
|
|
RaiseLastOSError;
|
|
|
|
with LStartupInfo do
|
|
begin
|
|
dwFlags := dwFlags or STARTF_USESTDHANDLES;
|
|
hStdOutput := LConsoleHandles.Write;
|
|
hStdInput := LConsoleHandles.Read;
|
|
hStdError := LConsoleHandles.Error;
|
|
end;
|
|
end;
|
|
|
|
if not CreateProcess(AppName, PChar(FCommandLine), nil, nil, DoRedirect,
|
|
Flags, EnvironmentData, CurrDir, LStartupInfo, FProcessInfo) then
|
|
begin
|
|
CloseProcessHandles;
|
|
SafeCloseHandle(LLocalHandles.Write);
|
|
SafeCloseHandle(LLocalHandles.Read);
|
|
RaiseLastOSError;
|
|
end;
|
|
|
|
if DoRedirect then
|
|
begin
|
|
{ (rb) We assume that a thread is done if its pointer (FReadThread/FWaitThread)
|
|
is nil (See code of ReadThreadTerminated and WaitThreadTerminated).
|
|
Thus we have to create both threads before we start any of them
|
|
(otherwise it will go wrong with very fast finishing executables)
|
|
(See Mantis #1393)
|
|
}
|
|
FState := psWaiting;
|
|
|
|
FEndLock := TCriticalSection.Create;
|
|
|
|
FReadThread := TJvReadThread.Create(LLocalHandles.Read, Handle);
|
|
FReadThread.OnTerminate := ReadThreadTerminated;
|
|
|
|
FWaitThread := TJvConsoleThread.Create(FProcessInfo.hProcess, LLocalHandles.Write);
|
|
FWaitThread.OnTerminate := ConsoleWaitThreadTerminated;
|
|
|
|
FReadThread.Resume;
|
|
FWaitThread.Resume;
|
|
end
|
|
else
|
|
if FWaitForTerminate then
|
|
begin
|
|
FState := psWaiting;
|
|
|
|
FWaitThread := TJvWaitForProcessThread.Create(FProcessInfo.hProcess);
|
|
FWaitThread.OnTerminate := WaitThreadTerminated;
|
|
FWaitThread.Resume;
|
|
end
|
|
else
|
|
begin
|
|
{ http://support.microsoft.com/default.aspx?scid=kb;en-us;124121 }
|
|
WaitForInputIdle(FProcessInfo.hProcess, INFINITE);
|
|
CloseProcessHandles;
|
|
FState := psRunning;
|
|
end;
|
|
finally
|
|
{ Close pipe handles (do not continue to modify the parent).
|
|
You need to make sure that no handles to the write end of the
|
|
output pipe are maintained in this process or else the pipe will
|
|
not close when the child process exits and the ReadFile will hang.
|
|
}
|
|
SafeCloseHandle(LConsoleHandles.Write);
|
|
SafeCloseHandle(LConsoleHandles.Read);
|
|
SafeCloseHandle(LConsoleHandles.Error);
|
|
FreeMultiSz(EnvironmentData);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.SetEnvironment(const Value: TStrings);
|
|
begin
|
|
FEnvironment.Assign(Value);
|
|
end;
|
|
|
|
procedure TJvCreateProcess.SetWaitForTerminate(const Value: Boolean);
|
|
begin
|
|
CheckNotWaiting;
|
|
FWaitForTerminate := Value;
|
|
FState := psReady;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.BeginConsoleEnding;
|
|
begin
|
|
FEndLock.Enter;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.StopWaiting;
|
|
begin
|
|
TerminateWaitThread;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.Terminate;
|
|
begin
|
|
CheckRunning;
|
|
InternalTerminateProcess(FProcessInfo.dwProcessId);
|
|
end;
|
|
|
|
procedure TJvCreateProcess.TerminateWaitThread;
|
|
begin
|
|
{ This is a dangerous function; because the read thread uses a blocking
|
|
function there's no way we can stop it (normally); just signal the
|
|
thread that is has to end;
|
|
|
|
Note that thus it's the user responsibility to ensure that the console
|
|
will end. If the console ends, the read thread will end also.
|
|
|
|
An console can (always?) be ended by calling 'TJvCreateProcess.Terminate'
|
|
}
|
|
if FState = psWaiting then
|
|
begin
|
|
if Assigned(FWaitThread) then
|
|
begin
|
|
FWaitThread.OnTerminate := nil;
|
|
TJvWaitForProcessThread(FWaitThread).TerminateThread;
|
|
FWaitThread := nil;
|
|
end;
|
|
if Assigned(FReadThread) then
|
|
begin
|
|
FReadThread.OnTerminate := nil;
|
|
TJvReadThread(FReadThread).TerminateThread;
|
|
FReadThread := nil;
|
|
end;
|
|
FState := psReady;
|
|
CloseProcessHandles;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.WaitThreadTerminated(Sender: TObject);
|
|
begin
|
|
FWaitThread := nil;
|
|
|
|
{ We only fire a TerminateEvent if both the read thread and the wait thread
|
|
have terminated; usually the read thread will terminate before the wait
|
|
thread: }
|
|
|
|
FExitCode := TJvWaitForProcessThread(Sender).FExitCode;
|
|
DoTerminateEvent;
|
|
end;
|
|
|
|
procedure TJvCreateProcess.WndProc(var Msg: TMessage);
|
|
begin
|
|
with Msg do
|
|
if Msg = CM_READ then
|
|
try
|
|
HandleReadEvent;
|
|
except
|
|
{$IFDEF COMPILER6_UP}
|
|
if Assigned(ApplicationHandleException) then
|
|
ApplicationHandleException(Self);
|
|
{$ELSE}
|
|
Application.HandleException(Self);
|
|
{$ENDIF COMPILER6_UP}
|
|
end
|
|
else
|
|
Result := DefWindowProc(Handle, Msg, WParam, LParam);
|
|
end;
|
|
|
|
function TJvCreateProcess.Write(const S: string): Boolean;
|
|
begin
|
|
Result := (FWaitThread is TJvConsoleThread) and
|
|
TJvConsoleThread(FWaitThread).Write(S);
|
|
end;
|
|
|
|
function TJvCreateProcess.WriteLn(const S: string): Boolean;
|
|
begin
|
|
Result := Write(S + sLineBreak);
|
|
end;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
initialization
|
|
RegisterUnitVersion(HInstance, UnitVersioning);
|
|
|
|
finalization
|
|
UnregisterUnitVersion(HInstance);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
end.
|