1 /*
   2  * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include "Platform.h"
  27 
  28 #ifdef WINDOWS
  29 
  30 #include "JavaVirtualMachine.h"
  31 #include "WindowsPlatform.h"
  32 #include "Package.h"
  33 #include "Helpers.h"
  34 #include "PlatformString.h"
  35 #include "Macros.h"
  36 
  37 #include <map>
  38 #include <vector>
  39 #include <regex>
  40 
  41 #define WINDOWS_JPACKAGE_TMP_DIR \
  42         L"\\AppData\\Local\\Java\\JPackage\\tmp"
  43 
  44 
  45 class Registry {
  46 private:
  47     HKEY FKey;
  48     HKEY FOpenKey;
  49     bool FOpen;
  50 
  51 public:
  52     Registry(HKEY Key) {
  53         FOpen = false;
  54         FKey = Key;
  55     }
  56 
  57     ~Registry() {
  58         Close();
  59     }
  60 
  61     void Close() {
  62         if (FOpen == true) {
  63             RegCloseKey(FOpenKey);
  64         }
  65     }
  66 
  67     bool Open(TString SubKey) {
  68         bool result = false;
  69         Close();
  70 
  71         if (RegOpenKeyEx(FKey, SubKey.data(), 0, KEY_READ, &FOpenKey) ==
  72                 ERROR_SUCCESS) {
  73             result = true;
  74         }
  75 
  76         return result;
  77     }
  78 
  79     std::list<TString> GetKeys() {
  80         std::list<TString> result;
  81         DWORD count;
  82 
  83         if (RegQueryInfoKey(FOpenKey, NULL, NULL, NULL, NULL, NULL, NULL,
  84                  &count, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {
  85 
  86             DWORD length = 255;
  87             DynamicBuffer<TCHAR> buffer(length);
  88             if (buffer.GetData() == NULL) {
  89                 return result;
  90             }
  91 
  92             for (unsigned int index = 0; index < count; index++) {
  93                 buffer.Zero();
  94                 DWORD status = RegEnumValue(FOpenKey, index, buffer.GetData(),
  95                                             &length, NULL, NULL, NULL, NULL);
  96 
  97                 while (status == ERROR_MORE_DATA) {
  98                     length = length * 2;
  99                     if (!buffer.Resize(length)) {
 100                         return result;
 101                     }
 102                     status = RegEnumValue(FOpenKey, index, buffer.GetData(),
 103                                           &length, NULL, NULL, NULL, NULL);
 104                 }
 105 
 106                 if (status == ERROR_SUCCESS) {
 107                     TString value = buffer.GetData();
 108                     result.push_back(value);
 109                 }
 110             }
 111         }
 112 
 113         return result;
 114     }
 115 
 116     TString ReadString(TString Name) {
 117         TString result;
 118         DWORD length;
 119         DWORD dwRet;
 120         DynamicBuffer<wchar_t> buffer(0);
 121         length = 0;
 122 
 123         dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL, NULL,
 124                 &length);
 125         if (dwRet == ERROR_MORE_DATA || dwRet == 0) {
 126             if (!buffer.Resize(length + 1)) {
 127                 return result;
 128             }
 129             dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL,
 130                     (LPBYTE)buffer.GetData(), &length);
 131             result = buffer.GetData();
 132         }
 133 
 134         return result;
 135     }
 136 };
 137 
 138 WindowsPlatform::WindowsPlatform(void) : Platform(), GenericPlatform() {
 139     FMainThread = ::GetCurrentThreadId();
 140 }
 141 
 142 WindowsPlatform::~WindowsPlatform(void) {
 143 }
 144 
 145 TCHAR* WindowsPlatform::ConvertStringToFileSystemString(TCHAR* Source,
 146         bool &release) {
 147     // Not Implemented.
 148     return NULL;
 149 }
 150 
 151 TCHAR* WindowsPlatform::ConvertFileSystemStringToString(TCHAR* Source,
 152         bool &release) {
 153     // Not Implemented.
 154     return NULL;
 155 }
 156 
 157 void WindowsPlatform::SetCurrentDirectory(TString Value) {
 158     _wchdir(Value.data());
 159 }
 160 
 161 TString WindowsPlatform::GetPackageRootDirectory() {
 162     TString filename = GetModuleFileName();
 163     return FilePath::ExtractFilePath(filename);
 164 }
 165 
 166 TString WindowsPlatform::GetAppDataDirectory() {
 167     TString result;
 168     TCHAR path[MAX_PATH];
 169 
 170     if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path) == S_OK) {
 171         result = path;
 172     }
 173 
 174     return result;
 175 }
 176 
 177 void WindowsPlatform::ShowMessage(TString title, TString description) {
 178     MessageBox(NULL, description.data(),
 179             !title.empty() ? title.data() : description.data(),
 180             MB_ICONERROR | MB_OK);
 181 }
 182 
 183 void WindowsPlatform::ShowMessage(TString description) {
 184     TString appname = GetModuleFileName();
 185     appname = FilePath::ExtractFileName(appname);
 186     MessageBox(NULL, description.data(), appname.data(), MB_ICONERROR | MB_OK);
 187 }
 188 
 189 MessageResponse WindowsPlatform::ShowResponseMessage(TString title,
 190         TString description) {
 191     MessageResponse result = mrCancel;
 192 
 193     if (::MessageBox(NULL, description.data(), title.data(), MB_OKCANCEL) ==
 194             IDOK) {
 195         result = mrOK;
 196     }
 197 
 198     return result;
 199 }
 200 
 201 TString WindowsPlatform::GetBundledJVMLibraryFileName(TString RuntimePath) {
 202     TString result = FilePath::IncludeTrailingSeparator(RuntimePath) +
 203         _T("jre\\bin\\jli.dll");
 204 
 205     if (FilePath::FileExists(result) == false) {
 206         result = FilePath::IncludeTrailingSeparator(RuntimePath) +
 207             _T("bin\\jli.dll");
 208     }
 209 
 210     return result;
 211 }
 212 
 213 ISectionalPropertyContainer* WindowsPlatform::GetConfigFile(TString FileName) {
 214     IniFile *result = new IniFile();
 215     if (result == NULL) {
 216         return NULL;
 217     }
 218 
 219     if (result->LoadFromFile(FileName) == false) {
 220         // New property file format was not found,
 221         // attempt to load old property file format.
 222         Helpers::LoadOldConfigFile(FileName, result);
 223     }
 224 
 225     return result;
 226 }
 227 
 228 TString WindowsPlatform::GetModuleFileName() {
 229     TString result;
 230     DynamicBuffer<wchar_t> buffer(MAX_PATH);
 231     if (buffer.GetData() == NULL) {
 232         return result;
 233     }
 234 
 235     ::GetModuleFileName(NULL, buffer.GetData(),
 236             static_cast<DWORD>(buffer.GetSize()));
 237 
 238     while (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
 239         if (!buffer.Resize(buffer.GetSize() * 2)) {
 240             return result;
 241         }
 242         ::GetModuleFileName(NULL, buffer.GetData(),
 243                 static_cast<DWORD>(buffer.GetSize()));
 244     }
 245 
 246     result = buffer.GetData();
 247     return result;
 248 }
 249 
 250 Module WindowsPlatform::LoadLibrary(TString FileName) {
 251     return ::LoadLibrary(FileName.data());
 252 }
 253 
 254 void WindowsPlatform::FreeLibrary(Module AModule) {
 255     ::FreeLibrary((HMODULE)AModule);
 256 }
 257 
 258 Procedure WindowsPlatform::GetProcAddress(Module AModule,
 259         std::string MethodName) {
 260     return ::GetProcAddress((HMODULE)AModule, MethodName.c_str());
 261 }
 262 
 263 bool WindowsPlatform::IsMainThread() {
 264     bool result = (FMainThread == ::GetCurrentThreadId());
 265     return result;
 266 }
 267 
 268 TString WindowsPlatform::GetTempDirectory() {
 269     TString result;
 270     PWSTR userDir = 0;
 271 
 272     if (SUCCEEDED(SHGetKnownFolderPath(
 273                     FOLDERID_Profile,
 274                     0,
 275                     NULL,
 276                     &userDir))) {
 277         result = userDir;
 278         result += WINDOWS_JPACKAGE_TMP_DIR;
 279         CoTaskMemFree(userDir);
 280     }
 281 
 282     return result;
 283 }
 284 
 285 static BOOL CALLBACK enumWindows(HWND winHandle, LPARAM lParam) {
 286     DWORD pid = (DWORD)lParam, wPid = 0;
 287     GetWindowThreadProcessId(winHandle, &wPid);
 288     if (pid == wPid)  {
 289         SetForegroundWindow(winHandle);
 290         return FALSE;
 291     }
 292     return TRUE;
 293 }
 294 
 295 TPlatformNumber WindowsPlatform::GetMemorySize() {
 296     SYSTEM_INFO si;
 297     GetSystemInfo(&si);
 298     size_t result = (size_t)si.lpMaximumApplicationAddress;
 299     result = result / 1048576; // Convert from bytes to megabytes.
 300     return result;
 301 }
 302 
 303 std::vector<TString> WindowsPlatform::GetLibraryImports(
 304        const TString FileName) {
 305  std::vector<TString> result;
 306     WindowsLibrary library(FileName);
 307     result = library.GetImports();
 308  return result;
 309 }
 310 
 311 std::vector<TString> FilterList(std::vector<TString> &Items,
 312         std::wregex Pattern) {
 313     std::vector<TString> result;
 314 
 315     for (std::vector<TString>::iterator it = Items.begin();
 316             it != Items.end(); ++it) {
 317         TString item = *it;
 318         std::wsmatch match;
 319 
 320         if (std::regex_search(item, match, Pattern)) {
 321             result.push_back(item);
 322         }
 323     }
 324     return result;
 325 }
 326 
 327 std::vector<TString> WindowsPlatform::FilterOutRuntimeDependenciesForPlatform(
 328         std::vector<TString> Imports) {
 329     std::vector<TString> result;
 330     Package& package = Package::GetInstance();
 331     Macros& macros = Macros::GetInstance();
 332     TString runtimeDir = macros.ExpandMacros(package.GetJVMRuntimeDirectory());
 333     std::vector<TString> filelist = FilterList(Imports,
 334             std::wregex(_T("MSVCR.*.DLL"), std::regex_constants::icase));
 335 
 336     for (std::vector<TString>::iterator it = filelist.begin();
 337             it != filelist.end(); ++it) {
 338         TString filename = *it;
 339         TString msvcr100FileName = FilePath::IncludeTrailingSeparator(
 340                 runtimeDir) + _T("jre\\bin\\") + filename;
 341 
 342         if (FilePath::FileExists(msvcr100FileName) == true) {
 343             result.push_back(msvcr100FileName);
 344             break;
 345         }
 346         else {
 347             msvcr100FileName = FilePath::IncludeTrailingSeparator(runtimeDir)
 348                     + _T("bin\\") + filename;
 349 
 350             if (FilePath::FileExists(msvcr100FileName) == true) {
 351                 result.push_back(msvcr100FileName);
 352                 break;
 353             }
 354         }
 355     }
 356 
 357  return result;
 358 }
 359 
 360 Process* WindowsPlatform::CreateProcess() {
 361     return new WindowsProcess();
 362 }
 363 
 364 #ifdef DEBUG
 365 bool WindowsPlatform::IsNativeDebuggerPresent() {
 366     bool result = false;
 367 
 368     if (IsDebuggerPresent() == TRUE) {
 369         result = true;
 370     }
 371 
 372     return result;
 373 }
 374 
 375 int WindowsPlatform::GetProcessID() {
 376     int pid = GetProcessId(GetCurrentProcess());
 377     return pid;
 378 }
 379 #endif //DEBUG
 380 
 381 
 382 FileHandle::FileHandle(std::wstring FileName) {
 383     FHandle = ::CreateFile(FileName.data(), GENERIC_READ, FILE_SHARE_READ,
 384             NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
 385 }
 386 
 387 FileHandle::~FileHandle() {
 388     if (IsValid() == true) {
 389         ::CloseHandle(FHandle);
 390     }
 391 }
 392 
 393 bool FileHandle::IsValid() {
 394     return FHandle != INVALID_HANDLE_VALUE;
 395 }
 396 
 397 HANDLE FileHandle::GetHandle() {
 398     return FHandle;
 399 }
 400 
 401 FileMappingHandle::FileMappingHandle(HANDLE FileHandle) {
 402     FHandle = ::CreateFileMapping(FileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
 403 }
 404 
 405 bool FileMappingHandle::IsValid() {
 406     return FHandle != NULL;
 407 }
 408 
 409 FileMappingHandle::~FileMappingHandle() {
 410     if (IsValid() == true) {
 411         ::CloseHandle(FHandle);
 412     }
 413 }
 414 
 415 HANDLE FileMappingHandle::GetHandle() {
 416     return FHandle;
 417 }
 418 
 419 FileData::FileData(HANDLE Handle) {
 420     FBaseAddress = ::MapViewOfFile(Handle, FILE_MAP_READ, 0, 0, 0);
 421 }
 422 
 423 FileData::~FileData() {
 424     if (IsValid() == true) {
 425         ::UnmapViewOfFile(FBaseAddress);
 426     }
 427 }
 428 
 429 bool FileData::IsValid() {
 430     return FBaseAddress != NULL;
 431 }
 432 
 433 LPVOID FileData::GetBaseAddress() {
 434     return FBaseAddress;
 435 }
 436 
 437 
 438 WindowsLibrary::WindowsLibrary(std::wstring FileName) {
 439     FFileName = FileName;
 440 }
 441 
 442 std::vector<TString> WindowsLibrary::GetImports() {
 443     std::vector<TString> result;
 444     FileHandle library(FFileName);
 445 
 446     if (library.IsValid() == true) {
 447         FileMappingHandle mapping(library.GetHandle());
 448 
 449         if (mapping.IsValid() == true) {
 450             FileData fileData(mapping.GetHandle());
 451 
 452             if (fileData.IsValid() == true) {
 453                 PIMAGE_DOS_HEADER dosHeader =
 454                         (PIMAGE_DOS_HEADER)fileData.GetBaseAddress();
 455                 PIMAGE_FILE_HEADER pImgFileHdr =
 456                         (PIMAGE_FILE_HEADER)fileData.GetBaseAddress();
 457                 if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
 458                     result = DumpPEFile(dosHeader);
 459                 }
 460             }
 461         }
 462     }
 463 
 464     return result;
 465 }
 466 
 467 // Given an RVA, look up the section header that encloses it and return a
 468 // pointer to its IMAGE_SECTION_HEADER
 469 PIMAGE_SECTION_HEADER WindowsLibrary::GetEnclosingSectionHeader(DWORD rva,
 470                                                 PIMAGE_NT_HEADERS pNTHeader) {
 471     PIMAGE_SECTION_HEADER result = 0;
 472     PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader);
 473 
 474     for (unsigned index = 0; index < pNTHeader->FileHeader.NumberOfSections;
 475             index++, section++) {
 476         // Is the RVA is within this section?
 477         if ((rva >= section->VirtualAddress) &&
 478             (rva < (section->VirtualAddress + section->Misc.VirtualSize))) {
 479             result = section;
 480         }
 481     }
 482 
 483     return result;
 484 }
 485 
 486 LPVOID WindowsLibrary::GetPtrFromRVA(DWORD rva, PIMAGE_NT_HEADERS pNTHeader,
 487         DWORD imageBase) {
 488     LPVOID result = 0;
 489     PIMAGE_SECTION_HEADER pSectionHdr = GetEnclosingSectionHeader(rva,
 490             pNTHeader);
 491 
 492     if (pSectionHdr != NULL) {
 493         INT delta = (INT)(
 494                 pSectionHdr->VirtualAddress-pSectionHdr->PointerToRawData);
 495         DWORD_PTR dwp = (DWORD_PTR) (imageBase + rva - delta);
 496         result = reinterpret_cast<LPVOID>(dwp); // VS2017 - FIXME
 497     }
 498 
 499     return result;
 500 }
 501 
 502 std::vector<TString> WindowsLibrary::GetImportsSection(DWORD base,
 503         PIMAGE_NT_HEADERS pNTHeader) {
 504     std::vector<TString> result;
 505 
 506     // Look up where the imports section is located. Normally in
 507     // the .idata section,
 508     // but not necessarily so. Therefore, grab the RVA from the data dir.
 509     DWORD importsStartRVA = pNTHeader->OptionalHeader.DataDirectory[
 510             IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
 511 
 512     if (importsStartRVA != NULL) {
 513         // Get the IMAGE_SECTION_HEADER that contains the imports. This is
 514         // usually the .idata section, but doesn't have to be.
 515         PIMAGE_SECTION_HEADER pSection =
 516                 GetEnclosingSectionHeader(importsStartRVA, pNTHeader);
 517 
 518         if (pSection != NULL) {
 519             PIMAGE_IMPORT_DESCRIPTOR importDesc =
 520                     (PIMAGE_IMPORT_DESCRIPTOR)GetPtrFromRVA(
 521                     importsStartRVA, pNTHeader,base);
 522 
 523             if (importDesc != NULL) {
 524                 while (true)
 525                 {
 526                     // See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR
 527                     if ((importDesc->TimeDateStamp == 0) &&
 528                             (importDesc->Name == 0)) {
 529                         break;
 530                     }
 531 
 532                     std::string filename = (char*)GetPtrFromRVA(
 533                             importDesc->Name, pNTHeader, base);
 534                     result.push_back(PlatformString(filename));
 535                     importDesc++;   // advance to next IMAGE_IMPORT_DESCRIPTOR
 536                 }
 537             }
 538         }
 539     }
 540 
 541     return result;
 542 }
 543 
 544 std::vector<TString> WindowsLibrary::DumpPEFile(PIMAGE_DOS_HEADER dosHeader) {
 545     std::vector<TString> result;
 546     // all of this is VS2017 - FIXME
 547     DWORD_PTR dwDosHeaders = reinterpret_cast<DWORD_PTR>(dosHeader);
 548     DWORD_PTR dwPIHeaders = dwDosHeaders + (DWORD)(dosHeader->e_lfanew);
 549 
 550     PIMAGE_NT_HEADERS pNTHeader =
 551                       reinterpret_cast<PIMAGE_NT_HEADERS>(dwPIHeaders);
 552 
 553     // Verify that the e_lfanew field gave us a reasonable
 554     // pointer and the PE signature.
 555     // TODO: To really fix JDK-8131321 this condition needs to be changed.
 556     // There is a matching change
 557     // in JavaVirtualMachine.cpp that also needs to be changed.
 558     if (pNTHeader->Signature == IMAGE_NT_SIGNATURE) {
 559         DWORD base = (DWORD)(dwDosHeaders);
 560         result = GetImportsSection(base, pNTHeader);
 561     }
 562 
 563     return result;
 564 }
 565 
 566 #include <TlHelp32.h>
 567 
 568 WindowsJob::WindowsJob() {
 569     FHandle = NULL;
 570 }
 571 
 572 WindowsJob::~WindowsJob() {
 573     if (FHandle != NULL) {
 574         CloseHandle(FHandle);
 575     }
 576 }
 577 
 578 HANDLE WindowsJob::GetHandle() {
 579     if (FHandle == NULL) {
 580         FHandle = CreateJobObject(NULL, NULL); // GLOBAL
 581 
 582         if (FHandle == NULL)
 583         {
 584             ::MessageBox( 0, _T("Could not create job object"),
 585                     _T("TEST"), MB_OK);
 586         }
 587         else
 588         {
 589             JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
 590 
 591             // Configure all child processes associated with
 592             // the job to terminate when the
 593             jeli.BasicLimitInformation.LimitFlags =
 594                     JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
 595             if (0 == SetInformationJobObject(FHandle,
 596                     JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) {
 597                 ::MessageBox( 0, _T("Could not SetInformationJobObject"),
 598                         _T("TEST"), MB_OK);
 599             }
 600         }
 601     }
 602 
 603     return FHandle;
 604 }
 605 
 606 // Initialize static member of WindowsProcess
 607 WindowsJob WindowsProcess::FJob;
 608 
 609 WindowsProcess::WindowsProcess() : Process() {
 610     FRunning = false;
 611 }
 612 
 613 WindowsProcess::~WindowsProcess() {
 614     Terminate();
 615 }
 616 
 617 void WindowsProcess::Cleanup() {
 618     CloseHandle(FProcessInfo.hProcess);
 619     CloseHandle(FProcessInfo.hThread);
 620 }
 621 
 622 bool WindowsProcess::IsRunning() {
 623     bool result = false;
 624 
 625     HANDLE handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
 626     if (handle == INVALID_HANDLE_VALUE) {
 627         return false;
 628     }
 629 
 630     PROCESSENTRY32 process = { 0 };
 631     process.dwSize = sizeof(process);
 632 
 633     if (::Process32First(handle, &process)) {
 634         do {
 635             if (process.th32ProcessID == FProcessInfo.dwProcessId) {
 636                 result = true;
 637                 break;
 638             }
 639         }
 640         while (::Process32Next(handle, &process));
 641     }
 642 
 643     CloseHandle(handle);
 644 
 645     return result;
 646 }
 647 
 648 bool WindowsProcess::Terminate() {
 649     bool result = false;
 650 
 651     if (IsRunning() == true && FRunning == true) {
 652         FRunning = false;
 653     }
 654 
 655     return result;
 656 }
 657 
 658 bool WindowsProcess::Execute(const TString Application,
 659         const std::vector<TString> Arguments, bool AWait) {
 660     bool result = false;
 661 
 662     if (FRunning == false) {
 663         FRunning = true;
 664 
 665         STARTUPINFO startupInfo;
 666         ZeroMemory(&startupInfo, sizeof(startupInfo));
 667         startupInfo.cb = sizeof(startupInfo);
 668         ZeroMemory(&FProcessInfo, sizeof(FProcessInfo));
 669 
 670         TString command = Application;
 671 
 672         for (std::vector<TString>::const_iterator iterator = Arguments.begin();
 673                 iterator != Arguments.end(); iterator++) {
 674             command += TString(_T(" ")) + *iterator;
 675         }
 676 
 677         if (::CreateProcess(Application.data(), (wchar_t*)command.data(), NULL,
 678             NULL, FALSE, 0, NULL, NULL, &startupInfo, &FProcessInfo) == FALSE) {
 679             TString message = PlatformString::Format(
 680                     _T("Error: Unable to create process %s"),
 681                      Application.data());
 682             throw Exception(message);
 683         }
 684         else {
 685             if (FJob.GetHandle() != NULL) {
 686                 if (::AssignProcessToJobObject(FJob.GetHandle(),
 687                          FProcessInfo.hProcess) == 0) {
 688                     // Failed to assign process to job. It doesn't prevent
 689                     // anything from continuing so continue.
 690                 }
 691             }
 692 
 693             // Wait until child process exits.
 694             if (AWait == true) {
 695                 Wait();
 696                 // Close process and thread handles.
 697                 Cleanup();
 698             }
 699         }
 700     }
 701 
 702     return result;
 703 }
 704 
 705 bool WindowsProcess::Wait() {
 706     bool result = false;
 707 
 708     WaitForSingleObject(FProcessInfo.hProcess, INFINITE);
 709     return result;
 710 }
 711 
 712 TProcessID WindowsProcess::GetProcessID() {
 713     return FProcessInfo.dwProcessId;
 714 }
 715 
 716 bool WindowsProcess::ReadOutput() {
 717     bool result = false;
 718     // TODO implement
 719     return result;
 720 }
 721 
 722 void WindowsProcess::SetInput(TString Value) {
 723     // TODO implement
 724 }
 725 
 726 std::list<TString> WindowsProcess::GetOutput() {
 727     ReadOutput();
 728     return Process::GetOutput();
 729 }
 730 
 731 #endif // WINDOWS