yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
96a8781c7
master
1// slang-win-process-util.cpp 2#include "../slang-process-util.h" 3#include "../slang-process.h" 4#include "../slang-string-escape-util.h" 5#include "../slang-string-util.h" 6#include "../slang-string.h" 7#include "slang-com-helper.h" 8 9#ifdef _WIN32 10// TODO: We could try to avoid including this at all, but it would 11// mean trying to hide certain struct layouts, which would add 12// more dynamic allocation. 13#include <windows.h> 14#endif 15 16#include <process.h> 17#include <stdio.h> 18#include <stdlib.h> 19 20#ifndef SLANG_RETURN_FAIL_ON_FALSE 21#define SLANG_RETURN_FAIL_ON_FALSE (x ) \ 22 if (!(x)) \ 23 return SLANG_FAIL; 24#endif 25 26namespace Slang 27{ 28 29// Has behavior very similar to unique_ptr - assignment is a move. 30class WinHandle 31{ 32public : 33/// Detach the encapsulated handle. Returns the handle (which now must be externally handled) 34HANDLE detach () 35 { 36HANDLE handle = m_handle ; 37m_handle = nullptr ; 38return handle ; 39 } 40 41/// Return as a handle 42 operatorHANDLE ()const {return m_handle ; } 43 44/// Assign 45void operator= (HANDLE handle ) 46 { 47setNull (); 48m_handle = handle ; 49 } 50void operator= (WinHandle && rhs ) 51 { 52HANDLE handle = m_handle ; 53m_handle = rhs .m_handle ; 54rhs .m_handle = handle ; 55 } 56 57/// Get ready for writing 58SLANG_FORCE_INLINE HANDLE * writeRef () 59 { 60setNull (); 61return & m_handle ; 62 } 63/// Get for read access 64SLANG_FORCE_INLINE const HANDLE * readRef ()const {return & m_handle ; } 65 66void setNull () 67 { 68if (m_handle ) 69 { 70CloseHandle (m_handle ); 71m_handle = nullptr ; 72 } 73 } 74bool isNull ()const {return m_handle == nullptr ; } 75 76/// Ctor 77WinHandle (HANDLE handle = nullptr ) 78 :m_handle (handle ) 79 { 80 } 81WinHandle (WinHandle && rhs ) 82 :m_handle (rhs .m_handle ) 83 { 84rhs .m_handle = nullptr ; 85 } 86 87/// Dtor 88 ~WinHandle () {setNull (); } 89 90private : 91WinHandle (const WinHandle & )= delete ; 92void operator= (const WinHandle & rhs )= delete ; 93 94HANDLE m_handle ; 95}; 96 97/* A simple Stream implementation of a File HANDLE (or Pipe). Note that currently does not allow 98* getPosition/seek/atEnd */ 99class WinPipeStream :public Stream 100{ 101public : 102typedef WinPipeStream ThisType ; 103 104// Stream 105virtual Int64 getPosition ()SLANG_OVERRIDE {return 0 ; } 106virtual SlangResult seek (SeekOrigin origin ,Int64 offset )SLANG_OVERRIDE 107 { 108SLANG_UNUSED (origin ); 109SLANG_UNUSED (offset ); 110return SLANG_E_NOT_AVAILABLE ; 111 } 112virtual SlangResult read (void * buffer ,size_t length ,size_t & outReadBytes )SLANG_OVERRIDE ; 113virtual SlangResult write (const void * buffer ,size_t length )SLANG_OVERRIDE ; 114virtual bool isEnd ()SLANG_OVERRIDE {return m_streamHandle .isNull (); } 115virtual bool canRead ()SLANG_OVERRIDE 116 { 117return _has (FileAccess ::Read )&& !m_streamHandle .isNull (); 118 } 119virtual bool canWrite ()SLANG_OVERRIDE 120 { 121return _has (FileAccess ::Write )&& !m_streamHandle .isNull (); 122 } 123virtual void close ()SLANG_OVERRIDE ; 124virtual SlangResult flush ()SLANG_OVERRIDE ; 125 126WinPipeStream (HANDLE handle ,FileAccess access ,bool isOwned = true); 127 128 ~WinPipeStream () {close (); } 129 130protected : 131bool _has (FileAccess access )const {return (Index (access )& Index (m_access ))!= 0 ; } 132 133SlangResult _updateState (BOOL res ); 134 135FileAccess m_access = FileAccess ::None ; 136WinHandle m_streamHandle ; 137bool m_isOwned ; 138bool m_isPipe ; 139}; 140 141class WinProcess :public Process 142{ 143public : 144// Process 145virtual bool isTerminated ()SLANG_OVERRIDE ; 146virtual bool waitForTermination (Int timeInMs )SLANG_OVERRIDE ; 147virtual void terminate (int32_t returnCode )SLANG_OVERRIDE ; 148virtual void kill (int32_t returnCode )SLANG_OVERRIDE ; 149 150WinProcess (HANDLE handle ,Stream * const * streams ) 151 :m_processHandle (handle ) 152 { 153for (Index i = 0 ;i < Index (StdStreamType ::CountOf );++ i ) 154 { 155m_streams [i ]= streams [i ]; 156 } 157 } 158 159protected : 160void _hasTerminated (); 161WinHandle m_processHandle ;///< If not set the process has terminated 162}; 163 164/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 165 166WinPipeStream ::WinPipeStream (HANDLE handle ,FileAccess access ,bool isOwned ) 167 :m_streamHandle (handle ),m_access (access ),m_isOwned (isOwned ) 168{ 169 170// On Win32 a HANDLE has to be handled differently if it's a PIPE or FILE, so first determine 171// if it really is a pipe. 172// http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx 173m_isPipe = ::GetFileType (handle )== FILE_TYPE_PIPE ; 174 175if (m_isPipe ) 176 { 177// It might be handy to get information about the handle 178// https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-getnamedpipeinfo 179 180DWORD flags ,outBufferSize ,inBufferSize ,maxInstances ; 181// It appears that by default windows pipe buffer size is 4k. 182if (GetNamedPipeInfo (handle ,& flags ,& outBufferSize ,& inBufferSize ,& maxInstances )) 183 { 184 } 185 } 186} 187 188SlangResult WinPipeStream ::_updateState (BOOL res ) 189{ 190if (res ) 191 { 192return SLANG_OK ; 193 } 194else 195 { 196const auto err = GetLastError (); 197 198if (err == ERROR_BROKEN_PIPE ) 199 { 200m_streamHandle .setNull (); 201return SLANG_OK ; 202 } 203 204SLANG_UNUSED (err ); 205return SLANG_FAIL ; 206 } 207} 208 209SlangResult WinPipeStream ::read (void * buffer ,size_t length ,size_t & outReadBytes ) 210{ 211outReadBytes = 0 ; 212if (!_has (FileAccess ::Read )) 213 { 214return SLANG_E_NOT_AVAILABLE ; 215 } 216 217if (m_streamHandle .isNull ()) 218 { 219return SLANG_OK ; 220 } 221 222DWORD bytesRead = 0 ; 223 224// Check if there is any data, so won't block 225if (m_isPipe ) 226 { 227DWORD pipeBytesRead = 0 ; 228DWORD pipeTotalBytesAvailable = 0 ; 229DWORD pipeRemainingBytes = 0 ; 230 231// Works on anonymous pipes too 232// https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe 233 234SLANG_RETURN_ON_FAIL (_updateState (::PeekNamedPipe ( 235m_streamHandle , 236nullptr , 237DWORD (0 ), 238& pipeBytesRead , 239& pipeTotalBytesAvailable , 240& pipeRemainingBytes ))); 241// If there is nothing to read we are done 242// If we don't do this ReadFile will *block* if there is nothing available 243if (pipeTotalBytesAvailable == 0 ) 244 { 245return SLANG_OK ; 246 } 247 248SLANG_RETURN_ON_FAIL ( 249_updateState (::ReadFile (m_streamHandle ,buffer ,DWORD (length ),& bytesRead ,nullptr ))); 250 } 251else 252 { 253SLANG_RETURN_ON_FAIL ( 254_updateState (::ReadFile (m_streamHandle ,buffer ,DWORD (length ),& bytesRead ,nullptr ))); 255 256// If it's not a pipe, and there is nothing left, then we are done. 257if (length > 0 && bytesRead == 0 ) 258 { 259close (); 260 } 261 } 262 263outReadBytes = size_t (bytesRead ); 264return SLANG_OK ; 265} 266 267SlangResult WinPipeStream ::write (const void * buffer ,size_t length ) 268{ 269if (!_has (FileAccess ::Write )) 270 { 271return SLANG_E_NOT_AVAILABLE ; 272 } 273 274if (m_streamHandle .isNull ()) 275 { 276// Writing to closed stream 277return SLANG_FAIL ; 278 } 279 280DWORD numWritten = 0 ; 281BOOL writeResult = ::WriteFile (m_streamHandle ,buffer ,DWORD (length ),& numWritten ,nullptr ); 282 283if (!writeResult ) 284 { 285auto err = ::GetLastError (); 286 287if (err == ERROR_BROKEN_PIPE ) 288 { 289close (); 290return SLANG_FAIL ; 291 } 292 293SLANG_UNUSED (err ); 294return SLANG_FAIL ; 295 } 296 297if (numWritten != length ) 298 { 299return SLANG_FAIL ; 300 } 301 302return SLANG_OK ; 303} 304 305void WinPipeStream ::close () 306{ 307if (!m_isOwned ) 308 { 309// If we don't own it just detach it 310m_streamHandle .detach (); 311 } 312m_streamHandle .setNull (); 313} 314 315SlangResult WinPipeStream ::flush () 316{ 317if ((Index (m_access )& Index (FileAccess ::Write ))== 0 || m_streamHandle .isNull ()) 318 { 319return SLANG_E_NOT_AVAILABLE ; 320 } 321 322// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers 323if (!::FlushFileBuffers (m_streamHandle )) 324 { 325auto err = GetLastError (); 326SLANG_UNUSED (err ); 327 } 328return SLANG_OK ; 329} 330 331/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 332 333void WinProcess ::_hasTerminated () 334{ 335if (!m_processHandle .isNull ()) 336 { 337// get exit code for process 338// https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess 339 340DWORD childExitCode = 0 ; 341if (::GetExitCodeProcess (m_processHandle ,& childExitCode )) 342 { 343m_returnValue = int32_t (childExitCode ); 344 } 345m_processHandle .setNull (); 346 } 347} 348 349bool WinProcess ::waitForTermination (Int timeInMs ) 350{ 351if (m_processHandle .isNull ()) 352 { 353return true; 354 } 355 356const DWORD timeOutTime = (timeInMs < 0 ) ?INFINITE :DWORD (timeInMs ); 357 358// wait for the process to exit 359// TODO: set a timeout as a safety measure... 360auto res = ::WaitForSingleObject (m_processHandle ,timeOutTime ); 361 362if (res == WAIT_TIMEOUT ) 363 { 364return false; 365 } 366 367_hasTerminated (); 368return true; 369} 370 371bool WinProcess ::isTerminated () 372{ 373return waitForTermination (0 ); 374} 375 376void WinProcess ::terminate (int32_t returnCode ) 377{ 378if (!isTerminated ()) 379 { 380// If it's not terminated, try terminating. 381// Might take time, so use isTerminated to check 382 ::TerminateProcess (m_processHandle ,UINT32 (returnCode )); 383 } 384} 385 386void WinProcess ::kill (int32_t returnCode ) 387{ 388if (!isTerminated ()) 389 { 390// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess 391 ::TerminateProcess (m_processHandle ,UINT32 (returnCode )); 392 393// Just assume it's done and set the return code 394m_returnValue = returnCode ; 395m_processHandle .setNull (); 396 } 397} 398 399/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 400 401/* static */ StringEscapeHandler * Process ::getEscapeHandler () 402{ 403return StringEscapeUtil ::getHandler (StringEscapeUtil ::Style ::Space ); 404} 405 406/* static */ UnownedStringSlice Process ::getExecutableSuffix () 407{ 408return UnownedStringSlice ::fromLiteral (".exe" ); 409} 410 411/* static */ SlangResult Process ::getStdStream (StdStreamType type ,RefPtr < Stream >& out ) 412{ 413switch (type ) 414 { 415case StdStreamType ::In : 416 { 417out = new WinPipeStream (GetStdHandle (STD_INPUT_HANDLE ),FileAccess ::Read , false); 418return SLANG_OK ; 419 } 420case StdStreamType ::Out : 421 { 422out = new WinPipeStream (GetStdHandle (STD_OUTPUT_HANDLE ),FileAccess ::Write , false); 423return SLANG_OK ; 424 } 425case StdStreamType ::ErrorOut : 426 { 427out = new WinPipeStream (GetStdHandle (STD_ERROR_HANDLE ),FileAccess ::Write , false); 428return SLANG_OK ; 429 } 430 } 431 432return SLANG_FAIL ; 433} 434 435/* static */ SlangResult Process ::create ( 436const CommandLine & commandLine , 437Process ::Flags flags , 438RefPtr < Process >& outProcess ) 439{ 440WinHandle childStdOutRead ; 441WinHandle childStdErrRead ; 442WinHandle childStdInWrite ; 443 444WinHandle processHandle ; 445 { 446WinHandle childStdOutWrite ; 447WinHandle childStdErrWrite ; 448WinHandle childStdInRead ; 449 450SECURITY_ATTRIBUTES securityAttributes ; 451securityAttributes .nLength = sizeof (securityAttributes ); 452securityAttributes .lpSecurityDescriptor = nullptr ; 453securityAttributes .bInheritHandle = true; 454 455// 0 means use the 'system default' 456// const DWORD bufferSize = 64 * 1024; 457const DWORD bufferSize = 0 ; 458 459 { 460WinHandle childStdOutReadTmp ; 461WinHandle childStdErrReadTmp ; 462WinHandle childStdInWriteTmp ; 463// create stdout pipe for child process 464SLANG_RETURN_FAIL_ON_FALSE (CreatePipe ( 465childStdOutReadTmp .writeRef (), 466childStdOutWrite .writeRef (), 467& securityAttributes , 468bufferSize )); 469if ((flags & Process ::Flag ::DisableStdErrRedirection )== 0 ) 470 { 471// create stderr pipe for child process 472SLANG_RETURN_FAIL_ON_FALSE (CreatePipe ( 473childStdErrReadTmp .writeRef (), 474childStdErrWrite .writeRef (), 475& securityAttributes , 476bufferSize )); 477 } 478// create stdin pipe for child process 479SLANG_RETURN_FAIL_ON_FALSE (CreatePipe ( 480childStdInRead .writeRef (), 481childStdInWriteTmp .writeRef (), 482& securityAttributes , 483bufferSize )); 484 485const HANDLE currentProcess = GetCurrentProcess (); 486 487// https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle 488 489// create a non-inheritable duplicate of the stdout reader 490SLANG_RETURN_FAIL_ON_FALSE (DuplicateHandle ( 491currentProcess , 492childStdOutReadTmp , 493currentProcess , 494childStdOutRead .writeRef (), 4950 , 496 FALSE, 497DUPLICATE_SAME_ACCESS )); 498// create a non-inheritable duplicate of the stderr reader 499if (childStdErrReadTmp ) 500SLANG_RETURN_FAIL_ON_FALSE (DuplicateHandle ( 501currentProcess , 502childStdErrReadTmp , 503currentProcess , 504childStdErrRead .writeRef (), 5050 , 506 FALSE, 507DUPLICATE_SAME_ACCESS )); 508// create a non-inheritable duplicate of the stdin writer 509SLANG_RETURN_FAIL_ON_FALSE (DuplicateHandle ( 510currentProcess , 511childStdInWriteTmp , 512currentProcess , 513childStdInWrite .writeRef (), 5140 , 515 FALSE, 516DUPLICATE_SAME_ACCESS )); 517 } 518 519// TODO: switch to proper wide-character versions of these... 520STARTUPINFOW startupInfo ; 521ZeroMemory (& startupInfo ,sizeof (startupInfo )); 522startupInfo .cb = sizeof (startupInfo ); 523startupInfo .hStdError = childStdErrWrite ; 524startupInfo .hStdOutput = childStdOutWrite ; 525startupInfo .hStdInput = childStdInRead ; 526startupInfo .dwFlags = STARTF_USESTDHANDLES ; 527 528OSString pathBuffer ; 529LPCWSTR path = nullptr ; 530 531const auto & exe = commandLine .m_executableLocation ; 532if (exe .m_type == ExecutableLocation ::Type ::Path ) 533 { 534// If it 'Path' specified we pass in as the lpApplicationName to limit 535// searching. 536pathBuffer = exe .m_pathOrName .toWString (); 537path = pathBuffer .begin (); 538 } 539 540// Produce the command line string 541String cmdString = commandLine .toString (); 542OSString cmdStringBuffer = cmdString .toWString (); 543 544// Now we can actually get around to starting a process 545PROCESS_INFORMATION processInfo ; 546ZeroMemory (& processInfo ,sizeof (processInfo )); 547 548// https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags 549 550DWORD createFlags = CREATE_NO_WINDOW ; 551 552if (flags & Process ::Flag ::AttachDebugger ) 553 { 554createFlags |=CREATE_SUSPENDED ; 555 } 556 557// From docs: 558// If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string 559// pointed to by lpApplicationName specifies the module to execute, and the null-terminated 560// string pointed to by lpCommandLine specifies the command line. 561 562// JS: 563// Somewhat confusingly this means that even if lpApplicationName is specified, it muse 564// *ALSO* be included as the first whitespace delimited arg must *also* be the (possibly) 565// quoted executable 566 567// https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa 568// `CreateProcess` requires write access to this, for some reason... 569BOOL success = CreateProcessW ( 570path , 571 (LPWSTR )cmdStringBuffer .begin (), 572nullptr , 573nullptr , 574 true, 575createFlags , 576nullptr ,// TODO: allow specifying environment variables? 577nullptr , 578& startupInfo , 579& processInfo ); 580 581if (!success ) 582 { 583DWORD err = GetLastError (); 584SLANG_UNUSED (err ); 585return SLANG_FAIL ; 586 } 587 588if (flags & Process ::Flag ::AttachDebugger ) 589 { 590// Lets see if we can set up to debug 591// https://docs.microsoft.com/en-us/windows/win32/debug/debugging-a-running-process 592 593// DebugActiveProcess(processInfo.dwProcessId); 594 595// Resume the thread 596ResumeThread (processInfo .hThread ); 597 } 598 599// close handles we are now done with 600CloseHandle (processInfo .hThread ); 601 602// Save the process handle 603processHandle = processInfo .hProcess ; 604 } 605 606RefPtr < Stream > streams [Index (StdStreamType ::CountOf )]; 607 608if (childStdErrRead ) 609streams [Index (StdStreamType ::ErrorOut )]= 610new WinPipeStream (childStdErrRead .detach (),FileAccess ::Read ); 611streams [Index (StdStreamType ::Out )]= 612new WinPipeStream (childStdOutRead .detach (),FileAccess ::Read ); 613streams [Index (StdStreamType ::In )]= 614new WinPipeStream (childStdInWrite .detach (),FileAccess ::Write ); 615outProcess = new WinProcess (processHandle .detach (),streams [0 ].readRef ()); 616 617return SLANG_OK ; 618} 619 620/* static */ void Process ::sleepCurrentThread (Int timeInMs ) 621{ 622 ::Sleep (DWORD (timeInMs )); 623} 624 625static uint64_t _getClockFrequency () 626{ 627LARGE_INTEGER timerFrequency ; 628QueryPerformanceFrequency (& timerFrequency ); 629return timerFrequency .QuadPart ; 630} 631 632static const uint64_t g_frequency = _getClockFrequency (); 633 634/* static */ uint64_t Process ::getClockFrequency () 635{ 636return g_frequency ; 637} 638 639/* static */ uint64_t Process ::getClockTick () 640{ 641LARGE_INTEGER counter ; 642QueryPerformanceCounter (& counter ); 643return counter .QuadPart ; 644} 645 646uint32_t Process ::getId () 647{ 648return _getpid (); 649} 650 651 652}// namespace Slang