{ ***************************************************************************** This file is part of LazUtils. See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** Initial Revision : Tue Dec 06 09:00:00 CET 2005 } unit UTF8Process; {$mode objfpc}{$H+} {$IF FPC_FULLVERSION<20701} {$DEFINE UseOldTProcess} {$ENDIF} {$IFNDEF UseOldTProcess} {$IFDEF MSWINDOWS} {$DEFINE UseTProcessW} {$ELSE} {$DEFINE UseTProcessAlias} {$ENDIF} {$ENDIF} interface uses Classes, SysUtils, Process, {$IF defined(UseSeparateTProcessW) or defined(UseTProcessW)} pipes, {$ENDIF} FileUtil, LazFileUtils, LazUTF8, LazUtilsStrConsts; {$IFDEF UseOldTProcess} type { TProcessUTF8 } TProcessUTF8 = class(TProcess) private FApplicationNameUTF8: string; FCommandLineUTF8: string; FConsoleTitleUTF8: string; FCurrentDirectoryUTF8: string; FDesktopUTF8: string; FEnvironmentUTF8: TStrings; FExecutableUTF8: string; FParametersUTF8: TStrings; procedure SetApplicationNameUTF8(const AValue: string); procedure SetCommandLineUTF8(const AValue: string); procedure SetConsoleTitleUTF8(const AValue: string); procedure SetCurrentDirectoryUTF8(const AValue: string); procedure SetDesktopUTF8(const AValue: string); procedure SetEnvironmentUTF8(const AValue: TStrings); procedure SetExecutableUTF8(AValue: string); procedure SetParametersUTF8(AValue: TStrings); procedure UpdateEnvironment; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Execute; override; procedure ParseCmdLine(const CmdLine: string; ReadBackslash: boolean = false); published property ApplicationName: string read FApplicationNameUTF8 write SetApplicationNameUTF8; property CommandLine: string read FCommandLineUTF8 write SetCommandLineUTF8; property ConsoleTitle: string read FConsoleTitleUTF8 write SetConsoleTitleUTF8; property CurrentDirectory: string read FCurrentDirectoryUTF8 write SetCurrentDirectoryUTF8; property Desktop: string read FDesktopUTF8 write SetDesktopUTF8; property Environment: TStrings read FEnvironmentUTF8 write SetEnvironmentUTF8; property Executable: string read FExecutableUTF8 Write SetExecutableUTF8; property Parameters: TStrings read FParametersUTF8 write SetParametersUTF8; end; {$ENDIF} {$IFDEF UseTProcessW} const SNoCommandLine = 'Cannot execute empty command-line'; SErrCannotExecute = 'Failed to execute %s : %d'; type { TProcessUTF8 } TProcessUTF8 = class(TProcess) protected procedure SetProcessHandle(aProcessHandle : THandle); procedure SetThreadHandle(aThreadHandle : THandle); procedure SetProcessID(aProcessID : Integer); public procedure Execute; override; procedure ParseCmdLine(const CmdLine: string; ReadBackslash: boolean = false); end; {$ENDIF} {$IFDEF UseTProcessAlias} type { TProcessUTF8 } TProcessUTF8 = class(TProcess) public procedure ParseCmdLine(const CmdLine: string; ReadBackslash: boolean = false); end; {$ENDIF} procedure RunCmdFromPath(ProgramFilename, CmdLineParameters: string); function FindFilenameOfCmd(ProgramFilename: string): string; function GetSystemThreadCount: integer; // guess number of cores procedure Register; implementation {$IF defined(windows)} uses Windows; {$ELSEIF defined(freebsd) or defined(darwin)} uses ctypes, sysctl; {$ELSEIF defined(linux)} {$linklib c} uses ctypes; {$ENDIF} {$IFDEF Linux} const _SC_NPROCESSORS_ONLN = 83; function sysconf(i: cint): clong; cdecl; external name 'sysconf'; {$ENDIF} function GetSystemThreadCount: integer; // returns a good default for the number of threads on this system {$IF defined(windows)} //returns total number of processors available to system including logical hyperthreaded processors var SystemInfo: SYSTEM_INFO; {$IFnDEF WinCE} i: Integer; ProcessAffinityMask, SystemAffinityMask: DWORD_PTR; Mask: DWORD; {$ENDIF} begin {$IFnDEF WinCE} if GetProcessAffinityMask(GetCurrentProcess, ProcessAffinityMask{%H-}, SystemAffinityMask{%H-}) then begin Result := 0; for i := 0 to 31 do begin Mask := DWord(1) shl i; if (ProcessAffinityMask and Mask)<>0 then inc(Result); end; exit; end; {$ENDIF} //can't get the affinity mask so we just report the total number of processors GetSystemInfo(SystemInfo{%H-}); Result := SystemInfo.dwNumberOfProcessors; end; {$ELSEIF defined(UNTESTEDsolaris)} begin t = sysconf(_SC_NPROC_ONLN); end; {$ELSEIF defined(freebsd) or defined(darwin)} var mib: array[0..1] of cint; len: cint; t: cint; begin mib[0] := CTL_HW; mib[1] := HW_NCPU; len := sizeof(t); {$if FPC_FULLVERSION >= 30101} fpsysctl(@mib, 2, @t, @len, Nil, 0); {$else} fpsysctl(pchar(@mib), 2, @t, @len, Nil, 0); {$endif} Result:=t; end; {$ELSEIF defined(linux)} begin Result:=sysconf(_SC_NPROCESSORS_ONLN); end; {$ELSE} begin Result:=1; end; {$ENDIF} function FindFilenameOfCmd(ProgramFilename: string): string; begin Result:=TrimFilename(ProgramFilename); if not FilenameIsAbsolute(Result) then begin if Pos(PathDelim,Result)>0 then begin // with sub directory => relative to current directory Result:=CleanAndExpandFilename(Result); end else begin // search in PATH Result:=FindDefaultExecutablePath(Result); end; end; if (Result<>'') and not FileExistsUTF8(Result) then Result:=''; end; // Runs a short command which should point to an executable in // the environment PATH // For example: ProgramFilename=ls CmdLineParameters=-l /home // Will locate and execute the file /bin/ls // If the command isn't found, an exception will be raised procedure RunCmdFromPath(ProgramFilename, CmdLineParameters: string); var OldProgramFilename: String; BrowserProcess: TProcessUTF8; begin OldProgramFilename:=ProgramFilename; ProgramFilename:=FindFilenameOfCmd(ProgramFilename); if ProgramFilename='' then raise EFOpenError.Create(Format(lrsProgramFileNotFound, [OldProgramFilename])); if not FileIsExecutable(ProgramFilename) then raise EFOpenError.Create(Format(lrsCanNotExecute, [ProgramFilename])); // run BrowserProcess := TProcessUTF8.Create(nil); try BrowserProcess.InheritHandles:=false; // Encloses the executable with "" if its name has spaces if Pos(' ',ProgramFilename)>0 then ProgramFilename:='"'+ProgramFilename+'"'; {$Push} {$WARN SYMBOL_DEPRECATED OFF} BrowserProcess.CommandLine := ProgramFilename; if CmdLineParameters<>'' then BrowserProcess.CommandLine := BrowserProcess.CommandLine + ' ' + CmdLineParameters; {$Pop} BrowserProcess.Execute; finally BrowserProcess.Free; end; end; procedure Register; begin RegisterComponents('System',[TProcessUTF8]); end; {$IFDEF UseOldTProcess} {$WARN SYMBOL_DEPRECATED OFF} { TProcessUTF8 } procedure TProcessUTF8.SetApplicationNameUTF8(const AValue: string); begin if FApplicationNameUTF8=AValue then exit; FApplicationNameUTF8:=AValue; inherited ApplicationName:=UTF8ToSys(FApplicationNameUTF8); end; procedure TProcessUTF8.SetCommandLineUTF8(const AValue: string); var Src: TStrings; i: Integer; begin if FCommandLineUTF8=AValue then exit; FCommandLineUTF8:=AValue; inherited CommandLine:=UTF8ToSys(FCommandLineUTF8); FExecutableUTF8:=SysToUTF8(inherited Executable); FParametersUTF8.Clear; Src:=inherited Parameters; if Src<>nil then for i:=0 to Src.Count-1 do FParametersUTF8.Add(SysToUTF8(Src[i])); end; procedure TProcessUTF8.SetConsoleTitleUTF8(const AValue: string); begin if FConsoleTitleUTF8=AValue then exit; FConsoleTitleUTF8:=AValue; inherited ConsoleTitle:=UTF8ToSys(FConsoleTitleUTF8); end; procedure TProcessUTF8.SetCurrentDirectoryUTF8(const AValue: string); begin if FCurrentDirectoryUTF8=AValue then exit; FCurrentDirectoryUTF8:=AValue; inherited CurrentDirectory:=UTF8ToSys(FCurrentDirectoryUTF8); end; procedure TProcessUTF8.SetDesktopUTF8(const AValue: string); begin if FDesktopUTF8=AValue then exit; FDesktopUTF8:=AValue; inherited Desktop:=UTF8ToSys(FDesktopUTF8); end; procedure TProcessUTF8.SetEnvironmentUTF8(const AValue: TStrings); begin if (FEnvironmentUTF8=AValue) or ((AValue<>nil) and FEnvironmentUTF8.Equals(AValue)) then exit; FEnvironmentUTF8.Assign(AValue); end; procedure TProcessUTF8.SetExecutableUTF8(AValue: string); begin if FExecutableUTF8=AValue then Exit; FExecutableUTF8:=AValue; inherited Executable:=UTF8ToSys(FExecutableUTF8); end; procedure TProcessUTF8.SetParametersUTF8(AValue: TStrings); begin if (FParametersUTF8=AValue) or ((AValue<>nil) and FParametersUTF8.Equals(AValue)) then exit; FParametersUTF8.Assign(AValue); end; procedure TProcessUTF8.UpdateEnvironment; var sl: TStringList; i: Integer; begin sl:=TStringList.Create; try for i:=0 to FEnvironmentUTF8.Count-1 do sl.Add(UTF8ToSys(FEnvironmentUTF8[i])); inherited Environment:=sl; sl.Clear; for i:=0 to FParametersUTF8.Count-1 do sl.Add(UTF8ToSys(FParametersUTF8[i])); inherited Parameters:=sl; finally sl.Free; end; end; constructor TProcessUTF8.Create(AOwner: TComponent); begin inherited Create(AOwner); FEnvironmentUTF8:=TStringList.Create; FParametersUTF8:=TStringList.Create; end; destructor TProcessUTF8.Destroy; begin FreeAndNil(FEnvironmentUTF8); FreeAndNil(FParametersUTF8); inherited Destroy; end; procedure TProcessUTF8.Execute; begin UpdateEnvironment; inherited Execute; end; {$ENDIF} {$IFDEF UseTProcessW} Const PriorityConstants : Array [TProcessPriority] of Cardinal = (HIGH_PRIORITY_CLASS,IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS,REALTIME_PRIORITY_CLASS); function WStrAsUniquePWideChar(var s: UnicodeString): PWideChar; inline; begin UniqueString(s); if s<>'' then Result:=PWideChar(s) else Result:=nil; end; Function GetStartupFlags (P : TProcessUTF8): Cardinal; begin Result:=0; if poUsePipes in P.Options then Result:=Result or Startf_UseStdHandles; if suoUseShowWindow in P.StartupOptions then Result:=Result or startf_USESHOWWINDOW; if suoUSESIZE in P.StartupOptions then Result:=Result or startf_usesize; if suoUsePosition in P.StartupOptions then Result:=Result or startf_USEPOSITION; if suoUSECOUNTCHARS in P.Startupoptions then Result:=Result or startf_usecountchars; if suoUsefIllAttribute in P.StartupOptions then Result:=Result or startf_USEFILLATTRIBUTE; end; Function GetCreationFlags(P : TProcessUTF8) : Cardinal; begin Result:=CREATE_UNICODE_ENVIRONMENT; if poNoConsole in P.Options then Result:=Result or Detached_Process; if poNewConsole in P.Options then Result:=Result or Create_new_console; if poNewProcessGroup in P.Options then Result:=Result or CREATE_NEW_PROCESS_GROUP; If poRunSuspended in P.Options Then Result:=Result or Create_Suspended; if poDebugProcess in P.Options Then Result:=Result or DEBUG_PROCESS; if poDebugOnlyThisProcess in P.Options Then Result:=Result or DEBUG_ONLY_THIS_PROCESS; if poDefaultErrorMode in P.Options Then Result:=Result or CREATE_DEFAULT_ERROR_MODE; result:=result or PriorityConstants[P.Priority]; end; Function MaybeQuote(Const S : String) : String; begin If (Pos(' ',S)<>0) then Result:='"'+S+'"' else Result:=S; end; Function MaybeQuoteIfNotQuoted(Const S : String) : String; begin If (Pos(' ',S)<>0) and (pos('"',S)=0) then Result:='"'+S+'"' else Result:=S; end; Function StringsToWChars(List : TStrings): pointer; var EnvBlock: UnicodeString; I: Integer; begin EnvBlock := ''; For I:=0 to List.Count-1 do EnvBlock := EnvBlock + UTF8Decode(List[i]) + #0; EnvBlock := EnvBlock + #0; GetMem(Result, Length(EnvBlock)*2); CopyMemory(Result, @EnvBlock[1], Length(EnvBlock)*2); end; Procedure InitProcessAttributes(Out PA : TSecurityAttributes); begin FillChar(PA{%H-},SizeOf(PA),0); PA.nLength := SizeOf(PA); end; Procedure InitThreadAttributes(Out TA : TSecurityAttributes); begin FillChar(TA{%H-},SizeOf(TA),0); TA.nLength := SizeOf(TA); end; Procedure InitStartupInfo(P : TProcessUTF8; Out SI : STARTUPINFOW); Const SWC : Array [TShowWindowOptions] of Cardinal = (0,SW_HIDE,SW_Maximize,SW_Minimize,SW_Restore,SW_Show, SW_ShowDefault,SW_ShowMaximized,SW_ShowMinimized, SW_showMinNOActive,SW_ShowNA,SW_ShowNoActivate,SW_ShowNormal); begin FillChar(SI{%H-},SizeOf(SI),0); SI.dwFlags:=GetStartupFlags(P); if P.ShowWindow<>swoNone then SI.dwFlags:=SI.dwFlags or Startf_UseShowWindow else SI.dwFlags:=SI.dwFlags and not Startf_UseShowWindow; SI.wShowWindow:=SWC[P.ShowWindow]; if (poUsePipes in P.Options) then begin SI.dwFlags:=SI.dwFlags or Startf_UseStdHandles; end; if P.FillAttribute<>0 then begin SI.dwFlags:=SI.dwFlags or Startf_UseFillAttribute; SI.dwFillAttribute:=P.FillAttribute; end; SI.dwXCountChars:=P.WindowColumns; SI.dwYCountChars:=P.WindowRows; SI.dwYsize:=P.WindowHeight; SI.dwXsize:=P.WindowWidth; SI.dwy:=P.WindowTop; SI.dwX:=P.WindowLeft; end; { The handles that are to be passed to the child process must be inheritable. On the other hand, only non-inheritable handles allow the sending of EOF when the write-end is closed. This function is used to duplicate the child process's ends of the handles into inheritable ones, leaving the parent-side handles non-inheritable. } function DuplicateHandleFP(var handle: THandle): Boolean; var oldHandle: THandle; begin oldHandle := handle; Result := DuplicateHandle ( GetCurrentProcess(), oldHandle, GetCurrentProcess(), @handle, 0, true, DUPLICATE_SAME_ACCESS ); if Result then Result := CloseHandle(oldHandle); end; Procedure CreatePipes(Var HI,HO,HE : Thandle; Var SI : TStartupInfoW; CE : Boolean; APipeBufferSize : Cardinal); begin CreatePipeHandles(SI.hStdInput,HI, APipeBufferSize); DuplicateHandleFP(SI.hStdInput); CreatePipeHandles(HO,Si.hStdOutput, APipeBufferSize); DuplicateHandleFP( Si.hStdOutput); if CE then begin CreatePipeHandles(HE,SI.hStdError, APipeBufferSize); DuplicateHandleFP( SI.hStdError); end else begin SI.hStdError:=SI.hStdOutput; HE:=HO; end; end; type TProcessClassTemplate = class(TComponent) private {%H-}FProcessOptions : TProcessOptions; {%H-}FStartupOptions : TStartupOptions; FProcessID : Integer; {%H-}FTerminalProgram: String; {%H-}FThreadID : Integer; FProcessHandle : Thandle; FThreadHandle : Thandle; end; { TProcessUTF8 } procedure TProcessUTF8.SetProcessHandle(aProcessHandle: THandle); var o: TProcessClassTemplate; begin o:=TProcessClassTemplate.Create(nil); PHANDLE(Pointer(Self)+(@o.FProcessHandle-Pointer(o)))^:=aProcessHandle; if aProcessHandle<>ProcessHandle then raise Exception.Create('TProcessUTF8.SetProcessHandle failed'); o.Free; end; procedure TProcessUTF8.SetThreadHandle(aThreadHandle: THandle); var o: TProcessClassTemplate; begin o:=TProcessClassTemplate.Create(nil); PHANDLE(Pointer(Self)+(@o.FThreadHandle-Pointer(o)))^:=aThreadHandle; if aThreadHandle<>ThreadHandle then raise Exception.Create('TProcessUTF8.SetThreadHandle failed'); o.Free; end; procedure TProcessUTF8.SetProcessID(aProcessID: Integer); var o: TProcessClassTemplate; begin o:=TProcessClassTemplate.Create(nil); PHANDLE(Pointer(Self)+(@o.FProcessID-Pointer(o)))^:=aProcessID; if aProcessID<>ProcessID then raise Exception.Create('TProcessUTF8.SetProcessID failed'); o.Free; end; procedure TProcessUTF8.Execute; Var i : Integer; WName,WDir,WCommandLine : UnicodeString; PWName,PWDir,PWCommandLine : PWideChar; FEnv: pointer; FCreationFlags : Cardinal; FProcessAttributes : TSecurityAttributes; FThreadAttributes : TSecurityAttributes; FProcessInformation : TProcessInformation; FStartupInfo : STARTUPINFOW; HI,HO,HE : THandle; Cmd : String; begin WName:=''; WCommandLine:=''; WDir:=''; if (ApplicationName{%H-}='') and (CommandLine{%H-}='') and (Executable='') then Raise EProcess.Create(SNoCommandline); if (ApplicationName{%H-}<>'') then begin WName:=UTF8Decode(ApplicationName{%H-}); WCommandLine:=UTF8Decode(CommandLine{%H-}); end else If (CommandLine{%H-}<>'') then WCommandLine:=UTF8Decode(CommandLine{%H-}) else if (Executable<>'') then begin Cmd:=MaybeQuoteIfNotQuoted(Executable); For I:=0 to Parameters.Count-1 do Cmd:=Cmd+' '+MaybeQuoteIfNotQuoted(Parameters[i]); WCommandLine:=UTF8Decode(Cmd); end; If CurrentDirectory<>'' then WDir:=UTF8Decode(CurrentDirectory); if Environment.Count<>0 then FEnv:=StringsToWChars(Environment) else FEnv:=Nil; Try FCreationFlags:=GetCreationFlags(Self); InitProcessAttributes(FProcessAttributes); InitThreadAttributes(FThreadAttributes); InitStartupInfo(Self,FStartupInfo); If poUsePipes in Options then CreatePipes(HI{%H-},HO{%H-},HE{%H-},FStartupInfo,Not(poStdErrToOutPut in Options), PipeBufferSize); Try // Beware: CreateProcess can alter the strings // Beware: nil is not the same as a pointer to a #0 PWName:=WStrAsUniquePWideChar(WName); PWCommandLine:=WStrAsUniquePWideChar(WCommandLine); PWDir:=WStrAsUniquePWideChar(WDir); If Not CreateProcessW (PWName,PWCommandLine,@FProcessAttributes,@FThreadAttributes, InheritHandles,FCreationFlags,FEnv,PWDir,FStartupInfo, fProcessInformation{%H-}) then Raise EProcess.CreateFmt(SErrCannotExecute,[CommandLine{%H-},GetLastError]); SetProcessHandle(FProcessInformation.hProcess); SetThreadHandle(FProcessInformation.hThread); SetProcessID(FProcessINformation.dwProcessID); Finally if POUsePipes in Options then begin FileClose(FStartupInfo.hStdInput); FileClose(FStartupInfo.hStdOutput); if Not (poStdErrToOutPut in Options) then FileClose(FStartupInfo.hStdError); CreateStreams(HI,HO,HE); end; end; FRunning:=True; Finally If FEnv<>Nil then FreeMem(FEnv); end; if not (csDesigning in ComponentState) and // This would hang the IDE ! (poWaitOnExit in Options) and not (poRunSuspended in Options) then WaitOnExit; end; {$ENDIF} procedure TProcessUTF8.ParseCmdLine(const CmdLine: string; ReadBackslash: boolean); var List: TStringList; begin List:=TStringList.Create; try SplitCmdLineParams( {$IFDEF UseOldTProcess} SysToUTF8(CmdLine), {$ELSE} CmdLine, {$ENDIF} List,ReadBackslash); if List.Count>0 then begin Executable:=List[0]; List.Delete(0); end else begin Executable:=''; end; Parameters.Assign(List); finally List.Free; end; end; end.