1{ 2 ***************************************************************************** 3 This file is part of LazUtils. 4 5 See the file COPYING.modifiedLGPL.txt, included in this distribution, 6 for details about the license. 7 ***************************************************************************** 8 9 Initial Revision : Tue Dec 06 09:00:00 CET 2005 10} 11 12unit UTF8Process; 13 14{$mode objfpc}{$H+} 15 16interface 17 18uses 19 Classes, SysUtils, Process, 20 FileUtil, LazFileUtils, LazUTF8, LazUtilsStrConsts; 21 22 {$IF DEFINED(MSWINDOWS) AND NOT DECLARED(poDetached)} // we need to work around the poNoConsole->poDetached change 23 // more info: issue #32055, #35991; FPC r45228, https://forum.lazarus.freepascal.org/index.php/topic,49631.0 24 {$DEFINE UseTProcessW} 25 {$ENDIF} 26 27{ TProcessUTF8 } 28 29{$IFDEF UseTProcessW} 30{$Optimization -ORDERFIELDS } 31const 32 SNoCommandLine = 'Cannot execute empty command-line'; 33 SErrCannotExecute = 'Failed to execute %s : %d'; 34type 35 TProcessUTF8 = class(TProcess) 36 protected 37 procedure SetProcessHandle(aProcessHandle : THandle); 38 procedure SetThreadHandle(aThreadHandle : THandle); 39 procedure SetProcessID(aProcessID : Integer); 40 public 41 procedure Execute; override; 42 procedure ParseCmdLine(const CmdLine: string; ReadBackslash: boolean = false); 43 end; 44 45{$ELSE} 46 47type 48 TProcessUTF8 = class(TProcess) 49 public 50 procedure ParseCmdLine(const CmdLine: string; ReadBackslash: boolean = false); 51 end; 52{$ENDIF} 53 54procedure RunCmdFromPath(const ProgramFilename, CmdLineParameters: string); 55function FindFilenameOfCmd(ProgramFilename: string): string; 56 57function GetSystemThreadCount: integer; // guess number of cores 58 59procedure Register; 60 61implementation 62 63{$IF defined(windows)} 64uses Windows 65 {$IFDEF UseTProcessW} 66 ,pipes 67 {$ENDIF} 68; 69{$ELSEIF defined(freebsd) or defined(darwin)} 70uses ctypes, sysctl; 71{$ELSEIF defined(linux)} 72{$linklib c} 73uses ctypes; 74{$ENDIF} 75 76{$IFDEF Linux} 77const _SC_NPROCESSORS_ONLN = 83; 78function sysconf(i: cint): clong; cdecl; external name 'sysconf'; 79{$ENDIF} 80 81function GetSystemThreadCount: integer; 82// returns a good default for the number of threads on this system 83{$IF defined(windows)} 84//returns total number of processors available to system including logical hyperthreaded processors 85var 86 SystemInfo: SYSTEM_INFO; 87 {$IFnDEF WinCE} 88 i: Integer; 89 ProcessAffinityMask, SystemAffinityMask: DWORD_PTR; 90 Mask: DWORD; 91 {$ENDIF} 92begin 93 {$IFnDEF WinCE} 94 if GetProcessAffinityMask(GetCurrentProcess, ProcessAffinityMask{%H-}, SystemAffinityMask{%H-}) 95 then begin 96 Result := 0; 97 for i := 0 to 31 do begin 98 Mask := DWord(1) shl i; 99 if (ProcessAffinityMask and Mask)<>0 then 100 inc(Result); 101 end; 102 exit; 103 end; 104 {$ENDIF} 105 //can't get the affinity mask so we just report the total number of processors 106 GetSystemInfo(SystemInfo{%H-}); 107 Result := SystemInfo.dwNumberOfProcessors; 108end; 109{$ELSEIF defined(UNTESTEDsolaris)} 110 begin 111 t = sysconf(_SC_NPROC_ONLN); 112 end; 113{$ELSEIF defined(freebsd) or defined(darwin)} 114var 115 mib: array[0..1] of cint; 116 len: cint; 117 t: cint; 118begin 119 mib[0] := CTL_HW; 120 mib[1] := HW_NCPU; 121 len := sizeof(t); 122 {$if FPC_FULLVERSION >= 30101} 123 fpsysctl(@mib, 2, @t, @len, Nil, 0); 124 {$else} 125 fpsysctl(pchar(@mib), 2, @t, @len, Nil, 0); 126 {$endif} 127 Result:=t; 128end; 129{$ELSEIF defined(linux)} 130 begin 131 Result:=sysconf(_SC_NPROCESSORS_ONLN); 132 end; 133 134{$ELSE} 135 begin 136 Result:=1; 137 end; 138{$ENDIF} 139 140function FindFilenameOfCmd(ProgramFilename: string): string; 141begin 142 Result:=TrimFilename(ProgramFilename); 143 if not FilenameIsAbsolute(Result) then begin 144 if Pos(PathDelim,Result)>0 then begin 145 // with sub directory => relative to current directory 146 Result:=CleanAndExpandFilename(Result); 147 end else begin 148 // search in PATH 149 Result:=FindDefaultExecutablePath(Result); 150 end; 151 end; 152 if (Result<>'') and not FileExistsUTF8(Result) then 153 Result:=''; 154end; 155 156// Runs a short command which should point to an executable in 157// the environment PATH 158// For example: ProgramFilename='ls' CmdLineParameters='-l /home' 159// Will locate and execute the file '/bin/ls' 160// If the command isn't found, an exception will be raised 161procedure RunCmdFromPath(const ProgramFilename, CmdLineParameters: string); 162var 163 NewProgramFilename: String; 164 BrowserProcess: TProcessUTF8; 165begin 166 NewProgramFilename:=FindFilenameOfCmd(ProgramFilename); 167 168 if NewProgramFilename='' then 169 raise EFOpenError.Create(Format(lrsProgramFileNotFound, [ProgramFilename])); 170 if not FileIsExecutable(NewProgramFilename) then 171 raise EFOpenError.Create(Format(lrsCanNotExecute, [NewProgramFilename])); 172 173 // run 174 BrowserProcess := TProcessUTF8.Create(nil); 175 try 176 BrowserProcess.InheritHandles:=false; 177 // Encloses the executable with "" if its name has spaces 178 if Pos(' ',NewProgramFilename)>0 then 179 NewProgramFilename:='"'+NewProgramFilename+'"'; 180 181 {$Push} 182 {$WARN SYMBOL_DEPRECATED OFF} 183 BrowserProcess.CommandLine := NewProgramFilename; 184 if CmdLineParameters<>'' then 185 BrowserProcess.CommandLine := BrowserProcess.CommandLine + ' ' + CmdLineParameters; 186 {$Pop} 187 BrowserProcess.Execute; 188 finally 189 BrowserProcess.Free; 190 end; 191end; 192 193procedure Register; 194begin 195 RegisterComponents('System',[TProcessUTF8]); 196end; 197 198{$IFDEF UseTProcessW} 199Const 200 PriorityConstants : Array [TProcessPriority] of Cardinal = 201 (HIGH_PRIORITY_CLASS,IDLE_PRIORITY_CLASS, 202 NORMAL_PRIORITY_CLASS,REALTIME_PRIORITY_CLASS 203 {$if (FPC_FULLVERSION >= 30200) and not defined(WinCE)} 204 ,BELOW_NORMAL_PRIORITY_CLASS,ABOVE_NORMAL_PRIORITY_CLASS 205 {$endif} 206 ); 207 208function WStrAsUniquePWideChar(var s: UnicodeString): PWideChar; inline; 209begin 210 UniqueString(s); 211 if s<>'' then 212 Result:=PWideChar(s) 213 else 214 Result:=nil; 215end; 216 217Function GetStartupFlags (P : TProcessUTF8): Cardinal; 218 219begin 220 Result:=0; 221 if poUsePipes in P.Options then 222 Result:=Result or Startf_UseStdHandles; 223 if suoUseShowWindow in P.StartupOptions then 224 Result:=Result or startf_USESHOWWINDOW; 225 if suoUSESIZE in P.StartupOptions then 226 Result:=Result or startf_usesize; 227 if suoUsePosition in P.StartupOptions then 228 Result:=Result or startf_USEPOSITION; 229 if suoUSECOUNTCHARS in P.Startupoptions then 230 Result:=Result or startf_usecountchars; 231 if suoUsefIllAttribute in P.StartupOptions then 232 Result:=Result or startf_USEFILLATTRIBUTE; 233end; 234 235Function GetCreationFlags(P : TProcessUTF8) : Cardinal; 236 237begin 238 Result:=CREATE_UNICODE_ENVIRONMENT; 239 {$IF DECLARED(poDetached)} 240 if poNoConsole in P.Options then 241 Result:=Result or CREATE_NO_WINDOW; 242 if poDetached in P.Options then 243 Result:=Result or Detached_Process; 244 {$ELSE} 245 if poNoConsole in P.Options then 246 Result:=Result or Detached_Process; 247 {$ENDIF} 248 if poNewConsole in P.Options then 249 Result:=Result or Create_new_console; 250 if poNewProcessGroup in P.Options then 251 Result:=Result or CREATE_NEW_PROCESS_GROUP; 252 If poRunSuspended in P.Options Then 253 Result:=Result or Create_Suspended; 254 if poDebugProcess in P.Options Then 255 Result:=Result or DEBUG_PROCESS; 256 if poDebugOnlyThisProcess in P.Options Then 257 Result:=Result or DEBUG_ONLY_THIS_PROCESS; 258 if poDefaultErrorMode in P.Options Then 259 Result:=Result or CREATE_DEFAULT_ERROR_MODE; 260 result:=result or PriorityConstants[P.Priority]; 261end; 262 263Function MaybeQuote(Const S : String) : String; 264 265begin 266 If (Pos(' ',S)<>0) then 267 Result:='"'+S+'"' 268 else 269 Result:=S; 270end; 271 272Function MaybeQuoteIfNotQuoted(Const S : String) : String; 273 274begin 275 If (Pos(' ',S)<>0) and (pos('"',S)=0) then 276 Result:='"'+S+'"' 277 else 278 Result:=S; 279end; 280 281Function StringsToWChars(List : TStrings): pointer; 282 283var 284 EnvBlock: UnicodeString; 285 I: Integer; 286 287begin 288 EnvBlock := ''; 289 For I:=0 to List.Count-1 do 290 EnvBlock := EnvBlock + UTF8Decode(List[i]) + #0; 291 EnvBlock := EnvBlock + #0; 292 GetMem(Result, Length(EnvBlock)*2); 293 CopyMemory(Result, @EnvBlock[1], Length(EnvBlock)*2); 294end; 295 296Procedure InitProcessAttributes(Out PA : TSecurityAttributes); 297 298begin 299 FillChar(PA{%H-},SizeOf(PA),0); 300 PA.nLength := SizeOf(PA); 301end; 302 303Procedure InitThreadAttributes(Out TA : TSecurityAttributes); 304 305begin 306 FillChar(TA{%H-},SizeOf(TA),0); 307 TA.nLength := SizeOf(TA); 308end; 309 310Procedure InitStartupInfo(P : TProcessUTF8; Out SI : STARTUPINFOW); 311 312Const 313 SWC : Array [TShowWindowOptions] of Cardinal = 314 (0,SW_HIDE,SW_Maximize,SW_Minimize,SW_Restore,SW_Show, 315 SW_ShowDefault,SW_ShowMaximized,SW_ShowMinimized, 316 SW_showMinNOActive,SW_ShowNA,SW_ShowNoActivate,SW_ShowNormal); 317 318begin 319 FillChar(SI{%H-},SizeOf(SI),0); 320 SI.dwFlags:=GetStartupFlags(P); 321 if P.ShowWindow<>swoNone then 322 SI.dwFlags:=SI.dwFlags or Startf_UseShowWindow 323 else 324 SI.dwFlags:=SI.dwFlags and not Startf_UseShowWindow; 325 SI.wShowWindow:=SWC[P.ShowWindow]; 326 if (poUsePipes in P.Options) then 327 begin 328 SI.dwFlags:=SI.dwFlags or Startf_UseStdHandles; 329 end; 330 if P.FillAttribute<>0 then 331 begin 332 SI.dwFlags:=SI.dwFlags or Startf_UseFillAttribute; 333 SI.dwFillAttribute:=P.FillAttribute; 334 end; 335 SI.dwXCountChars:=P.WindowColumns; 336 SI.dwYCountChars:=P.WindowRows; 337 SI.dwYsize:=P.WindowHeight; 338 SI.dwXsize:=P.WindowWidth; 339 SI.dwy:=P.WindowTop; 340 SI.dwX:=P.WindowLeft; 341end; 342 343{ The handles that are to be passed to the child process must be 344 inheritable. On the other hand, only non-inheritable handles 345 allow the sending of EOF when the write-end is closed. This 346 function is used to duplicate the child process's ends of the 347 handles into inheritable ones, leaving the parent-side handles 348 non-inheritable. 349} 350function DuplicateHandleFP(var handle: THandle): Boolean; 351 352var 353 oldHandle: THandle; 354begin 355 oldHandle := handle; 356 Result := DuplicateHandle 357 ( GetCurrentProcess(), 358 oldHandle, 359 GetCurrentProcess(), 360 @handle, 361 0, 362 true, 363 DUPLICATE_SAME_ACCESS 364 ); 365 if Result then 366 Result := CloseHandle(oldHandle); 367end; 368 369 370Procedure CreatePipes(Var HI,HO,HE : Thandle; Var SI : TStartupInfoW; CE : Boolean; APipeBufferSize : Cardinal); 371 372begin 373 CreatePipeHandles(SI.hStdInput,HI, APipeBufferSize); 374 DuplicateHandleFP(SI.hStdInput); 375 CreatePipeHandles(HO,Si.hStdOutput, APipeBufferSize); 376 DuplicateHandleFP( Si.hStdOutput); 377 if CE then begin 378 CreatePipeHandles(HE,SI.hStdError, APipeBufferSize); 379 DuplicateHandleFP( SI.hStdError); 380 end 381 else 382 begin 383 SI.hStdError:=SI.hStdOutput; 384 HE:=HO; 385 end; 386end; 387 388{ TProcessUTF8 } 389 390type 391 PHandle = ^THandle; 392 393procedure TProcessUTF8.SetProcessHandle(aProcessHandle: THandle); 394var 395 P: PHandle; 396begin 397 P := @Self.ProcessHandle; 398 P^ := aProcessHandle; 399 if aProcessHandle<>ProcessHandle then 400 raise Exception.Create('TProcessUTF8.SetProcessHandle failed'); 401end; 402 403procedure TProcessUTF8.SetThreadHandle(aThreadHandle: THandle); 404var 405 P: PHandle; 406begin 407 P := @Self.ThreadHandle; 408 P^ := aThreadHandle; 409 if aThreadHandle<>ThreadHandle then 410 raise Exception.Create('TProcessUTF8.SetThreadHandle failed'); 411end; 412 413procedure TProcessUTF8.SetProcessID(aProcessID: Integer); 414var 415 P: PInteger; 416begin 417 P := @Self.ProcessID; 418 P^ := aProcessID; 419 if aProcessID<>ProcessID then 420 raise Exception.Create('TProcessUTF8.SetProcessID failed'); 421end; 422 423procedure TProcessUTF8.Execute; 424Var 425 i : Integer; 426 WName,WDir,WCommandLine : UnicodeString; 427 PWName,PWDir,PWCommandLine : PWideChar; 428 FEnv: pointer; 429 FCreationFlags : Cardinal; 430 FProcessAttributes : TSecurityAttributes; 431 FThreadAttributes : TSecurityAttributes; 432 FProcessInformation : TProcessInformation; 433 FStartupInfo : STARTUPINFOW; 434 HI,HO,HE : THandle; 435 Cmd : String; 436 437begin 438 WName:=''; 439 WCommandLine:=''; 440 WDir:=''; 441 442 if (ApplicationName{%H-}='') and (CommandLine{%H-}='') and (Executable='') then 443 Raise EProcess.Create(SNoCommandline); 444 if (ApplicationName{%H-}<>'') then 445 begin 446 WName:=UTF8Decode(ApplicationName{%H-}); 447 WCommandLine:=UTF8Decode(CommandLine{%H-}); 448 end 449 else If (CommandLine{%H-}<>'') then 450 WCommandLine:=UTF8Decode(CommandLine{%H-}) 451 else if (Executable<>'') then 452 begin 453 Cmd:=MaybeQuoteIfNotQuoted(Executable); 454 For I:=0 to Parameters.Count-1 do 455 Cmd:=Cmd+' '+MaybeQuoteIfNotQuoted(Parameters[i]); 456 WCommandLine:=UTF8Decode(Cmd); 457 end; 458 If CurrentDirectory<>'' then 459 WDir:=UTF8Decode(CurrentDirectory); 460 if Environment.Count<>0 then 461 FEnv:=StringsToWChars(Environment) 462 else 463 FEnv:=Nil; 464 Try 465 FCreationFlags:=GetCreationFlags(Self); 466 InitProcessAttributes(FProcessAttributes); 467 InitThreadAttributes(FThreadAttributes); 468 InitStartupInfo(Self,FStartupInfo); 469 If poUsePipes in Options then 470 CreatePipes(HI{%H-},HO{%H-},HE{%H-},FStartupInfo,Not(poStdErrToOutPut in Options), PipeBufferSize); 471 Try 472 // Beware: CreateProcess can alter the strings 473 // Beware: nil is not the same as a pointer to a #0 474 PWName:=WStrAsUniquePWideChar(WName); 475 PWCommandLine:=WStrAsUniquePWideChar(WCommandLine); 476 PWDir:=WStrAsUniquePWideChar(WDir); 477 478 If Not CreateProcessW (PWName,PWCommandLine,@FProcessAttributes,@FThreadAttributes, 479 InheritHandles,FCreationFlags,FEnv,PWDir,FStartupInfo, 480 fProcessInformation{%H-}) then 481 Raise EProcess.CreateFmt(SErrCannotExecute,[CommandLine{%H-},GetLastError]); 482 SetProcessHandle(FProcessInformation.hProcess); 483 SetThreadHandle(FProcessInformation.hThread); 484 SetProcessID(FProcessINformation.dwProcessID); 485 Finally 486 if POUsePipes in Options then 487 begin 488 FileClose(FStartupInfo.hStdInput); 489 FileClose(FStartupInfo.hStdOutput); 490 if Not (poStdErrToOutPut in Options) then 491 FileClose(FStartupInfo.hStdError); 492 CreateStreams(HI,HO,HE); 493 end; 494 end; 495 FRunning:=True; 496 Finally 497 If FEnv<>Nil then 498 FreeMem(FEnv); 499 end; 500 if not (csDesigning in ComponentState) and // This would hang the IDE ! 501 (poWaitOnExit in Options) and 502 not (poRunSuspended in Options) then 503 WaitOnExit; 504end; 505{$ENDIF} 506 507procedure TProcessUTF8.ParseCmdLine(const CmdLine: string; ReadBackslash: boolean); 508var 509 List: TStringList; 510begin 511 List:=TStringList.Create; 512 try 513 SplitCmdLineParams(CmdLine, List, ReadBackslash); 514 if List.Count>0 then begin 515 Executable:=List[0]; 516 List.Delete(0); 517 end else begin 518 Executable:=''; 519 end; 520 Parameters.Assign(List); 521 finally 522 List.Free; 523 end; 524end; 525 526end. 527