Add AppWaitForSingleObject/MultipleObjects, ThreadWaitForCompletion.
Sean Hall committed
Jun 29, 2022 at 10:28 UTC
7cca75c8e95f129a21c33f1f4568e90e9e397f9d
26 files changed
+380
-149
src/burn/engine/apply.cpp
+12
-14
@@ -2333,6 +2333,8 @@ static HRESULT DoExecuteAction(
2333
2334
HRESULT hr = S_OK;
2335
HANDLE rghWait[2] = { };
2336
+ DWORD dwSignaledIndex = 0;
2337
+ DWORD dwExitCode = 0;
2338
BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE;
2339
BOOL fRetry = FALSE;
2340
BOOL fStopWusaService = FALSE;
@@ -2356,27 +2358,23 @@ static HRESULT DoExecuteAction(
2358
// wait for cache sync-point
2359
rghWait[0] = pExecuteAction->waitCachePackage.pPackage->hCacheEvent;
2360
rghWait[1] = pContext->pApplyContext->hCacheThread;
2359
- switch (::WaitForMultipleObjects(rghWait[1] ? 2 : 1, rghWait, FALSE, INFINITE))
2361
+
2362
+ hr = AppWaitForMultipleObjects(rghWait[1] ? 2 : 1, rghWait, FALSE, INFINITE, &dwSignaledIndex);
2363
+ ExitOnFailure(hr, "Failed to wait for cache check-point.");
2364
+
2365
+ switch (dwSignaledIndex)
2366
{
2361
- case WAIT_OBJECT_0:
2367
+ case 0:
2368
break;
2363
-
2364
- case WAIT_OBJECT_0 + 1:
2365
- if (!::GetExitCodeThread(pContext->pApplyContext->hCacheThread, (DWORD*)&hr))
2369
+ case 1:
2370
+ if (!::GetExitCodeThread(pContext->pApplyContext->hCacheThread, &dwExitCode))
2371
{
2372
ExitWithLastError(hr, "Failed to get cache thread exit code.");
2373
}
2374
2370
- if (SUCCEEDED(hr))
2371
- {
2372
- hr = E_UNEXPECTED;
2373
- }
2374
- ExitOnFailure(hr, "Cache thread exited unexpectedly.");
2375
-
2376
- case WAIT_FAILED: __fallthrough;
2377
- default:
2378
- ExitWithLastError(hr, "Failed to wait for cache check-point.");
2375
+ ExitWithRootFailure(hr, E_UNEXPECTED, "Cache thread exited unexpectedly with exit code: %u.", dwExitCode);
2376
}
2377
+
2378
break;
2379
2380
case BURN_EXECUTE_ACTION_TYPE_RELATED_BUNDLE:
src/burn/engine/cabextract.cpp
+18
-24
@@ -262,10 +262,8 @@ extern "C" HRESULT CabExtractClose(
262
}
263
264
// wait for thread to terminate
265
- if (WAIT_OBJECT_0 != ::WaitForSingleObject(pContext->Cabinet.hThread, INFINITE))
266
- {
267
- ExitWithLastError(hr, "Failed to wait for thread to terminate.");
268
- }
265
+ hr = AppWaitForSingleObject(pContext->Cabinet.hThread, INFINITE);
266
+ ExitOnFailure(hr, "Failed to wait for thread to terminate.");
267
}
268
269
LExit:
@@ -306,29 +304,31 @@ static HRESULT WaitForOperation(
304
{
305
HRESULT hr = S_OK;
306
HANDLE rghWait[2] = { };
307
+ DWORD dwSignaledIndex = 0;
308
309
// wait for operation complete event
310
rghWait[0] = pContext->Cabinet.hOperationCompleteEvent;
311
rghWait[1] = pContext->Cabinet.hThread;
313
- switch (::WaitForMultipleObjects(countof(rghWait), rghWait, FALSE, INFINITE))
312
+
313
+ hr = AppWaitForMultipleObjects(countof(rghWait), rghWait, FALSE, INFINITE, &dwSignaledIndex);
314
+ ExitOnFailure(hr, "Failed to wait for operation complete event.");
315
+
316
+ switch (dwSignaledIndex)
317
{
315
- case WAIT_OBJECT_0:
318
+ case 0:
319
if (!::ResetEvent(pContext->Cabinet.hOperationCompleteEvent))
320
{
321
ExitWithLastError(hr, "Failed to reset operation complete event.");
322
}
320
- break;
323
322
- case WAIT_OBJECT_0 + 1:
324
+ break;
325
+ case 1:
326
if (!::GetExitCodeThread(pContext->Cabinet.hThread, (DWORD*)&hr))
327
{
328
ExitWithLastError(hr, "Failed to get extraction thread exit code.");
329
}
327
- ExitFunction();
330
329
- case WAIT_FAILED: __fallthrough;
330
- default:
331
- ExitWithLastError(hr, "Failed to wait for operation complete event.");
331
+ ExitFunction();
332
}
333
334
// clear operation
@@ -430,10 +430,8 @@ static DWORD WINAPI ExtractThreadProc(
430
}
431
432
// wait for begin operation event
433
- if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE))
434
- {
435
- ExitWithLastError(hr, "Failed to wait for begin operation event.");
436
- }
433
+ hr = AppWaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE);
434
+ ExitOnFailure(hr, "Failed to wait for begin operation event.");
435
436
if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent))
437
{
@@ -517,10 +515,8 @@ static INT_PTR CopyFileCallback(
515
}
516
517
// wait for begin operation event
520
- if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE))
521
- {
522
- ExitWithLastError(hr, "Failed to wait for begin operation event.");
523
- }
518
+ hr = AppWaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE);
519
+ ExitOnFailure(hr, "Failed to wait for begin operation event.");
520
521
if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent))
522
{
@@ -552,10 +548,8 @@ static INT_PTR CopyFileCallback(
548
}
549
550
// wait for begin operation event
555
- if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE))
556
- {
557
- ExitWithLastError(hr, "Failed to wait for begin operation event.");
558
- }
551
+ hr = AppWaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE);
552
+ ExitOnFailure(hr, "Failed to wait for begin operation event.");
553
554
if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent))
555
{
src/burn/engine/core.cpp
+28
-26
@@ -12,6 +12,9 @@ struct BURN_CACHE_THREAD_CONTEXT
12
};
13
14
15
+static PFN_PROCWAITFORCOMPLETION vpfnProcWaitForCompletion = ProcWaitForCompletion;
16
+
17
+
18
// internal function declarations
19
20
static HRESULT CoreRecreateCommandLine(
@@ -65,9 +68,6 @@ static HRESULT DetectPackagePayloadsCached(
68
static DWORD WINAPI CacheThreadProc(
69
__in LPVOID lpThreadParameter
70
);
68
-static HRESULT WaitForCacheThread(
69
- __in HANDLE hCacheThread
70
- );
71
static void LogPackages(
72
__in_opt const BURN_PACKAGE* pUpgradeBundlePackage,
73
__in_opt const BURN_PACKAGE* pForwardCompatibleBundlePackage,
@@ -636,6 +636,7 @@ extern "C" HRESULT CoreApply(
636
BURN_APPLY_CONTEXT applyContext = { };
637
BOOL fDeleteApplyCs = FALSE;
638
BURN_CACHE_THREAD_CONTEXT cacheThreadContext = { };
639
+ DWORD dwCacheExitCode = 0;
640
BOOL fRollbackCache = FALSE;
641
DWORD dwPhaseCount = 0;
642
BOOTSTRAPPER_APPLYCOMPLETE_ACTION applyCompleteAction = BOOTSTRAPPER_APPLYCOMPLETE_ACTION_NONE;
@@ -744,7 +745,10 @@ extern "C" HRESULT CoreApply(
745
// If we're not caching in parallel, wait for the cache thread to terminate.
746
if (!pEngineState->fParallelCacheAndExecute)
747
{
747
- hr = WaitForCacheThread(applyContext.hCacheThread);
748
+ hr = ThrdWaitForCompletion(applyContext.hCacheThread, INFINITE, &dwCacheExitCode);
749
+ ExitOnFailure(hr, "Failed to wait for cache thread before execute.");
750
+
751
+ hr = (HRESULT)dwCacheExitCode;
752
ExitOnFailure(hr, "Failed while caching, aborting execution.");
753
754
ReleaseHandle(applyContext.hCacheThread);
@@ -761,10 +765,12 @@ extern "C" HRESULT CoreApply(
765
// Wait for cache thread to terminate, this should return immediately unless we're waiting for layout to complete.
766
if (applyContext.hCacheThread)
767
{
764
- HRESULT hrCached = WaitForCacheThread(applyContext.hCacheThread);
768
+ HRESULT hrCached = ThrdWaitForCompletion(applyContext.hCacheThread, INFINITE, &dwCacheExitCode);
769
+ ExitOnFailure(hrCached, "Failed to wait for cache thread after execute.");
770
+
771
if (SUCCEEDED(hr))
772
{
767
- hr = hrCached;
773
+ hr = (HRESULT)dwCacheExitCode;
774
}
775
}
776
@@ -1940,6 +1946,22 @@ LExit:
1946
return hr;
1947
}
1948
1949
+extern "C" void CoreFunctionOverride(
1950
+ __in_opt PFN_PROCWAITFORCOMPLETION pfnProcWaitForCompletion
1951
+ )
1952
+{
1953
+ vpfnProcWaitForCompletion = pfnProcWaitForCompletion;
1954
+}
1955
+
1956
+extern "C" HRESULT DAPI CoreWaitForProcCompletion(
1957
+ __in HANDLE hProcess,
1958
+ __in DWORD dwTimeout,
1959
+ __out DWORD* pdwReturnCode
1960
+ )
1961
+{
1962
+ return vpfnProcWaitForCompletion(hProcess, dwTimeout, pdwReturnCode);
1963
+}
1964
+
1965
// internal helper functions
1966
1967
static HRESULT AppendEscapedArgumentToCommandLine(
@@ -2268,26 +2290,6 @@ LExit:
2290
return (DWORD)hr;
2291
}
2292
2271
-static HRESULT WaitForCacheThread(
2272
- __in HANDLE hCacheThread
2273
- )
2274
-{
2275
- HRESULT hr = S_OK;
2276
-
2277
- if (WAIT_OBJECT_0 != ::WaitForSingleObject(hCacheThread, INFINITE))
2278
- {
2279
- ExitWithLastError(hr, "Failed to wait for cache thread to terminate.");
2280
- }
2281
-
2282
- if (!::GetExitCodeThread(hCacheThread, (DWORD*)&hr))
2283
- {
2284
- ExitWithLastError(hr, "Failed to get cache thread exit code.");
2285
- }
2286
-
2287
-LExit:
2288
- return hr;
2289
-}
2290
-
2293
static void LogPackages(
2294
__in_opt const BURN_PACKAGE* pUpgradeBundlePackage,
2295
__in_opt const BURN_PACKAGE* pForwardCompatibleBundlePackage,
src/burn/engine/core.h
+14
@@ -174,6 +174,12 @@ typedef struct _BURN_APPLY_CONTEXT
174
DWORD dwCacheCheckpoint;
175
} BURN_APPLY_CONTEXT;
176
177
+typedef HRESULT (DAPI *PFN_PROCWAITFORCOMPLETION)(
178
+ __in HANDLE hProcess,
179
+ __in DWORD dwTimeout,
180
+ __out DWORD* pReturnCode
181
+ );
182
+
183
184
// function declarations
185
@@ -280,6 +286,14 @@ HRESULT CoreParseCommandLine(
286
__inout HANDLE* phSectionFile,
287
__inout HANDLE* phSourceEngineFile
288
);
289
+void CoreFunctionOverride(
290
+ __in_opt PFN_PROCWAITFORCOMPLETION pfnProcWaitForCompletion
291
+ );
292
+HRESULT DAPI CoreWaitForProcCompletion(
293
+ __in HANDLE hProcess,
294
+ __in DWORD dwTimeout,
295
+ __out_opt DWORD* pdwReturnCode
296
+ );
297
298
#if defined(__cplusplus)
299
}
src/burn/engine/elevation.cpp
+2
-9
@@ -1687,15 +1687,8 @@ static HRESULT WaitForElevatedChildCacheThread(
1687
HRESULT hr = S_OK;
1688
DWORD dwExitCode = ERROR_SUCCESS;
1689
1690
- if (WAIT_OBJECT_0 != ::WaitForSingleObject(hCacheThread, BURN_TIMEOUT))
1691
- {
1692
- ExitWithLastError(hr, "Failed to wait for cache thread to terminate.");
1693
- }
1694
-
1695
- if (!::GetExitCodeThread(hCacheThread, &dwExitCode))
1696
- {
1697
- ExitWithLastError(hr, "Failed to get cache thread exit code.");
1698
- }
1690
+ hr = ThrdWaitForCompletion(hCacheThread, BURN_TIMEOUT, &dwExitCode);
1691
+ ExitOnFailure(hr, "Failed to wait for cache thread to complete.");
1692
1693
AssertSz(dwExitCode == dwExpectedExitCode, "Cache thread should have exited with the expected exit code.");
1694
src/burn/engine/netfxchainer.cpp
+18
-13
@@ -338,7 +338,8 @@ extern "C" HRESULT NetFxRunChainer(
338
)
339
{
340
HRESULT hr = S_OK;
341
- DWORD er = 0;
341
+ DWORD dwSignaledIndex = 0;
342
+ BOOL fTimedOut = 0;
343
WCHAR wzGuid[GUID_STRING_LENGTH];
344
LPWSTR sczEventName = NULL;
345
LPWSTR sczSectionName = NULL;
@@ -381,9 +382,17 @@ extern "C" HRESULT NetFxRunChainer(
382
383
for (;;)
384
{
384
- er = ::WaitForMultipleObjects(2, handles, FALSE, 100);
385
- if (WAIT_OBJECT_0 == er)
385
+ hr = AppWaitForMultipleObjects(2, handles, FALSE, 100, &dwSignaledIndex);
386
+ ExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for netfx chainer process to complete");
387
+
388
+ if (fTimedOut)
389
+ {
390
+ continue;
391
+ }
392
+
393
+ switch (dwSignaledIndex)
394
{
395
+ case 0:
396
// Process has exited
397
*pdwExitCode = NetFxGetResult(pNetfxChainer, &hrInternalError);
398
if (E_PENDING == *pdwExitCode)
@@ -396,21 +405,17 @@ extern "C" HRESULT NetFxRunChainer(
405
else if (FAILED(hrInternalError))
406
{
407
// push internal error message
399
- OnNetFxError(pNetfxChainer, hrInternalError, pfnGenericMessageHandler, pvContext);
408
+ hr = OnNetFxError(pNetfxChainer, hrInternalError, pfnGenericMessageHandler, pvContext);
409
ExitOnFailure(hr, "Failed to send internal error message from netfx chainer.");
401
- }
410
+ }
411
403
- break;
404
- }
405
- else if (WAIT_OBJECT_0 + 1 == er)
406
- {
412
+ ExitFunction();
413
+ case 1:
414
// Chainee has notified us of a change.
415
hr = ProcessNetFxMessage(pNetfxChainer, pfnGenericMessageHandler, pvContext);
416
ExitOnFailure(hr, "Failed to process netfx chainer message.");
410
- }
411
- else if (WAIT_FAILED == er)
412
- {
413
- ExitWithLastError(hr, "Failed to wait for netfx chainer process to complete");
417
+
418
+ break;
419
}
420
}
421
src/burn/engine/pipe.cpp
+16
-18
@@ -422,6 +422,7 @@ extern "C" HRESULT PipeTerminateChildProcess(
422
HRESULT hr = S_OK;
423
BYTE* pbData = NULL;
424
SIZE_T cbData = 0;
425
+ BOOL fTimedOut = FALSE;
426
427
// Prepare the exit message.
428
hr = BuffWriteNumber(&pbData, &cbData, dwParentExitCode);
@@ -443,31 +444,28 @@ extern "C" HRESULT PipeTerminateChildProcess(
444
// If we were able to get a handle to the other process, wait for it to exit.
445
if (pConnection->hProcess)
446
{
446
- if (WAIT_FAILED == ::WaitForSingleObject(pConnection->hProcess, PIPE_WAIT_FOR_CONNECTION * PIPE_RETRY_FOR_CONNECTION))
447
- {
448
- ExitWithLastError(hr, "Failed to wait for child process exit.");
449
- }
447
+ hr = AppWaitForSingleObject(pConnection->hProcess, PIPE_WAIT_FOR_CONNECTION * PIPE_RETRY_FOR_CONNECTION);
448
+ ExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for child process exit.");
449
+
450
+ AssertSz(!fTimedOut, "Timed out while waiting for child process to exit.");
451
+ }
452
453
#ifdef DEBUG
454
+ if (pConnection->hProcess && !fTimedOut)
455
+ {
456
DWORD dwChildExitCode = 0;
453
- DWORD dwErrorCode = ERROR_SUCCESS;
454
- BOOL fReturnedExitCode = ::GetExitCodeProcess(pConnection->hProcess, &dwChildExitCode);
455
- if (!fReturnedExitCode)
456
- {
457
- dwErrorCode = ::GetLastError(); // if the other process is elevated and we are not, then we'll get ERROR_ACCESS_DENIED.
457
+ HRESULT hrDebug = S_OK;
458
459
- // The unit test use a thread instead of a process so try to get the exit code from
460
- // the thread because we failed to get it from the process.
461
- if (ERROR_INVALID_HANDLE == dwErrorCode)
462
- {
463
- fReturnedExitCode = ::GetExitCodeThread(pConnection->hProcess, &dwChildExitCode);
464
- }
459
+ hrDebug = CoreWaitForProcCompletion(pConnection->hProcess, 0, &dwChildExitCode);
460
+ if (E_ACCESSDENIED != hrDebug) // if the other process is elevated and we are not, then we'll get ERROR_ACCESS_DENIED.
461
+ {
462
+ TraceError(hrDebug, "Failed to wait for child process completion.");
463
}
466
- AssertSz((fReturnedExitCode && dwChildExitCode == dwParentExitCode) ||
467
- (!fReturnedExitCode && ERROR_ACCESS_DENIED == dwErrorCode),
464
+
465
+ AssertSz(E_ACCESSDENIED == hrDebug || dwChildExitCode == dwParentExitCode,
466
"Child elevated process did not return matching exit code to parent process.");
469
-#endif
467
}
468
+#endif
469
470
LExit:
471
return hr;
src/burn/engine/precomp.h
+1
@@ -46,6 +46,7 @@
46
#include <srputil.h>
47
#include <strutil.h>
48
#include <svcutil.h>
49
+#include <thrdutil.h>
50
#include <userutil.h>
51
#include <wiutil.h>
52
#include <wuautil.h>
src/burn/test/BurnUnitTest/ElevationTest.cpp
+1
-2
@@ -54,7 +54,6 @@ namespace Bootstrapper
54
HRESULT hr = S_OK;
55
BURN_ENGINE_STATE engineState = { };
56
BURN_PIPE_CONNECTION* pConnection = &engineState.companionConnection;
57
- HANDLE hEvent = NULL;
57
DWORD dwResult = S_OK;
58
59
engineState.sczBundleEngineWorkingPath = L"tests\\ignore\\this\\path\\to\\burn.exe";
@@ -62,6 +61,7 @@ namespace Bootstrapper
61
try
62
{
63
ShelFunctionOverride(ElevateTest_ShellExecuteExW);
64
+ CoreFunctionOverride(ThrdWaitForCompletion);
65
66
PipeConnectionInitialize(pConnection);
67
@@ -87,7 +87,6 @@ namespace Bootstrapper
87
finally
88
{
89
PipeConnectionUninitialize(pConnection);
90
- ReleaseHandle(hEvent);
90
}
91
}
92
};
src/burn/test/BurnUnitTest/precomp.h
+1
@@ -28,6 +28,7 @@
28
#include <resrutil.h>
29
#include <shelutil.h>
30
#include <strutil.h>
31
+#include <thrdutil.h>
32
#include <wiutil.h>
33
#include <xmlutil.h>
34
#include <dictutil.h>
src/libs/dutil/WixToolset.DUtil/apputil.cpp
+68
@@ -313,6 +313,74 @@ LExit:
313
return hr;
314
}
315
316
+DAPI_(HRESULT) AppWaitForSingleObject(
317
+ __in HANDLE hHandle,
318
+ __in DWORD dwMilliseconds
319
+ )
320
+{
321
+ HRESULT hr = S_OK;
322
+ DWORD dwResult = 0;
323
+
324
+ dwResult = ::WaitForSingleObject(hHandle, dwMilliseconds);
325
+ if (WAIT_TIMEOUT == dwResult)
326
+ {
327
+ ExitFunction1(hr = HRESULT_FROM_WIN32(dwResult));
328
+ }
329
+ else if (WAIT_ABANDONED == dwResult)
330
+ {
331
+ AppExitOnWin32Error(dwResult, hr, "Abandoned wait for single object.");
332
+ }
333
+ else if (WAIT_OBJECT_0 != dwResult)
334
+ {
335
+ AssertSz(WAIT_FAILED == dwResult, "Unexpected return code from WaitForSingleObject.");
336
+ AppExitWithLastError(hr, "Failed to wait for single object.");
337
+ }
338
+
339
+LExit:
340
+ return hr;
341
+}
342
+
343
+DAPI_(HRESULT) AppWaitForMultipleObjects(
344
+ __in DWORD dwCount,
345
+ __in const HANDLE* rghHandles,
346
+ __in BOOL fWaitAll,
347
+ __in DWORD dwMilliseconds,
348
+ __out_opt DWORD* pdwSignaledIndex
349
+ )
350
+{
351
+ HRESULT hr = S_OK;
352
+ DWORD dwResult = 0;
353
+ DWORD dwSignaledIndex = dwCount;
354
+
355
+ dwResult = ::WaitForMultipleObjects(dwCount, rghHandles, fWaitAll, dwMilliseconds);
356
+ if (WAIT_TIMEOUT == dwResult)
357
+ {
358
+ ExitFunction1(hr = HRESULT_FROM_WIN32(dwResult));
359
+ }
360
+ else if (WAIT_ABANDONED_0 <= dwResult && (WAIT_ABANDONED_0 + dwCount) > dwResult)
361
+ {
362
+ dwSignaledIndex = dwResult - WAIT_ABANDONED_0;
363
+ AppExitOnWin32Error(dwResult, hr, "Abandoned wait for multiple objects, index: %u.", dwSignaledIndex);
364
+ }
365
+ else if (WAIT_OBJECT_0 <= dwResult && (WAIT_OBJECT_0 + dwCount) > dwResult)
366
+ {
367
+ dwSignaledIndex = dwResult - WAIT_OBJECT_0;
368
+ }
369
+ else
370
+ {
371
+ AssertSz(WAIT_FAILED == dwResult, "Unexpected return code from WaitForMultipleObjects.");
372
+ AppExitWithLastError(hr, "Failed to wait for multiple objects.");
373
+ }
374
+
375
+LExit:
376
+ if (pdwSignaledIndex)
377
+ {
378
+ *pdwSignaledIndex = dwSignaledIndex;
379
+ }
380
+
381
+ return hr;
382
+}
383
+
384
static HRESULT EscapeCommandLineArgument(
385
__in_z LPCWSTR wzArgument,
386
__out_z LPWSTR* psczEscaped
src/libs/dutil/WixToolset.DUtil/dutil.vcxproj
+2
@@ -104,6 +104,7 @@
104
<ClCompile Include="strutil.cpp" />
105
<ClCompile Include="svcutil.cpp" />
106
<ClCompile Include="thmutil.cpp" />
107
+ <ClCompile Include="thrdutil.cpp" />
108
<ClCompile Include="timeutil.cpp" />
109
<ClCompile Include="uncutil.cpp" />
110
<ClCompile Include="uriutil.cpp" />
@@ -164,6 +165,7 @@
165
<ClInclude Include="inc\strutil.h" />
166
<ClInclude Include="inc\svcutil.h" />
167
<ClInclude Include="inc\thmutil.h" />
168
+ <ClInclude Include="inc\thrdutil.h" />
169
<ClInclude Include="inc\timeutil.h" />
170
<ClInclude Include="inc\uriutil.h" />
171
<ClInclude Include="inc\userutil.h" />
src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters
+6
@@ -162,6 +162,9 @@
162
<ClCompile Include="thmutil.cpp">
163
<Filter>Source Files</Filter>
164
</ClCompile>
165
+ <ClCompile Include="thrdutil.cpp">
166
+ <Filter>Source Files</Filter>
167
+ </ClCompile>
168
<ClCompile Include="timeutil.cpp">
169
<Filter>Source Files</Filter>
170
</ClCompile>
@@ -344,6 +347,9 @@
347
<ClInclude Include="inc\thmutil.h">
348
<Filter>Header Files</Filter>
349
</ClInclude>
350
+ <ClInclude Include="inc\thrdutil.h">
351
+ <Filter>Header Files</Filter>
352
+ </ClInclude>
353
<ClInclude Include="inc\timeutil.h">
354
<Filter>Header Files</Filter>
355
</ClInclude>
src/libs/dutil/WixToolset.DUtil/inc/apputil.h
+21
@@ -84,6 +84,27 @@ HRESULT DAPI AppEscapeCommandLineArgumentFormattedArgs(
84
__in va_list args
85
);
86
87
+/********************************************************************
88
+AppWaitForSingleObject - wrapper for ::WaitForSingleObject.
89
+
90
+********************************************************************/
91
+HRESULT DAPI AppWaitForSingleObject(
92
+ __in HANDLE hHandle,
93
+ __in DWORD dwMilliseconds
94
+ );
95
+
96
+/********************************************************************
97
+AppWaitForMultipleObjects - wrapper for ::WaitForMultipleObjects.
98
+
99
+********************************************************************/
100
+HRESULT DAPI AppWaitForMultipleObjects(
101
+ __in DWORD dwCount,
102
+ __in const HANDLE* rghHandles,
103
+ __in BOOL fWaitAll,
104
+ __in DWORD dwMilliseconds,
105
+ __out_opt DWORD* pdwSignaledIndex
106
+ );
107
+
108
#ifdef __cplusplus
109
}
110
#endif
src/libs/dutil/WixToolset.DUtil/inc/dutil.h
+2
@@ -130,6 +130,7 @@ void DAPI Dutil_RootFailure(__in_z LPCSTR szFile, __in int iLine, __in HRESULT h
130
#define ExitOnWin32ErrorSource(d, e, x, s, ...) if (ERROR_SUCCESS != e) { x = HRESULT_FROM_WIN32(e); if (!FAILED(x)) { x = E_FAIL; } Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
131
#define ExitOnOptionalXmlQueryFailureSource(d, x, b, s, ...) { { if (S_FALSE == x || E_NOTFOUND == x) { b = FALSE; x = S_OK; } else { b = SUCCEEDED(x); } }; ExitOnRootFailureSource(d, x, s, __VA_ARGS__); }
132
#define ExitOnRequiredXmlQueryFailureSource(d, x, s, ...) { if (S_FALSE == x) { x = E_NOTFOUND; } ExitOnRootFailureSource(d, x, s, __VA_ARGS__); }
133
+#define ExitOnWaitObjectFailureSource(d, x, b, s, ...) { { if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == x) { b = TRUE; x = S_OK; } else { b = FALSE; } }; ExitOnFailureSource(d, x, s, __VA_ARGS__); }
134
135
#define ExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
136
#define ExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
@@ -145,6 +146,7 @@ void DAPI Dutil_RootFailure(__in_z LPCSTR szFile, __in int iLine, __in HRESULT h
146
#define ExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DEFAULT, e, x, s, __VA_ARGS__)
147
#define ExitOnOptionalXmlQueryFailure(x, b, s, ...) ExitOnOptionalXmlQueryFailureSource(DUTIL_SOURCE_DEFAULT, x, b, s, __VA_ARGS__)
148
#define ExitOnRequiredXmlQueryFailure(x, s, ...) ExitOnRequiredXmlQueryFailureSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
149
+#define ExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_DEFAULT, x, b, s, __VA_ARGS__)
150
151
// release macros
152
#define ReleaseObject(x) if (x) { x->Release(); }
src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h
+1
@@ -63,6 +63,7 @@ typedef enum DUTIL_SOURCE
63
DUTIL_SOURCE_VERUTIL,
64
DUTIL_SOURCE_WNDUTIL,
65
DUTIL_SOURCE_ENVUTIL,
66
+ DUTIL_SOURCE_THRDUTIL,
67
68
DUTIL_SOURCE_EXTERNAL = 256,
69
} DUTIL_SOURCE;
src/libs/dutil/WixToolset.DUtil/inc/procutil.h
+1
-1
@@ -58,7 +58,7 @@ HRESULT DAPI ProcExecute(
58
HRESULT DAPI ProcWaitForCompletion(
59
__in HANDLE hProcess,
60
__in DWORD dwTimeout,
61
- __out DWORD *pReturnCode
61
+ __out_opt DWORD* pdwReturnCode
62
);
63
HRESULT DAPI ProcWaitForIds(
64
__in_ecount(cProcessIds) const DWORD* pdwProcessIds,
src/libs/dutil/WixToolset.DUtil/inc/thrdutil.h
new
+22
@@ -0,0 +1,22 @@
1
+#pragma once
2
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
+
4
+
5
+#ifdef __cplusplus
6
+extern "C" {
7
+#endif
8
+
9
+/********************************************************************
10
+ ThrdWaitForCompletion - waits for thread to complete and gets return code.
11
+
12
+ *******************************************************************/
13
+HRESULT DAPI ThrdWaitForCompletion(
14
+ __in HANDLE hThread,
15
+ __in DWORD dwTimeout,
16
+ __out_opt DWORD* pdwReturnCode
17
+ );
18
+
19
+#ifdef __cplusplus
20
+}
21
+#endif
22
+
src/libs/dutil/WixToolset.DUtil/monutil.cpp
+16
-18
@@ -16,6 +16,7 @@
16
#define MonExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_MONUTIL, p, x, s, __VA_ARGS__)
17
#define MonExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_MONUTIL, e, x, s, __VA_ARGS__)
18
#define MonExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_MONUTIL, g, x, s, __VA_ARGS__)
19
+#define MonExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_MONUTIL, x, b, s, __VA_ARGS__)
20
21
const int MON_THREAD_GROWTH = 5;
22
const int MON_ARRAY_GROWTH = 40;
@@ -1101,11 +1102,12 @@ static DWORD WINAPI WaiterThread(
1102
{
1103
HRESULT hr = S_OK;
1104
HRESULT hrTemp = S_OK;
1104
- DWORD dwRet = 0;
1105
BOOL fAgain = FALSE;
1106
BOOL fContinue = TRUE;
1107
BOOL fNotify = FALSE;
1108
BOOL fRet = FALSE;
1109
+ BOOL fTimedOut = FALSE;
1110
+ DWORD dwSignaledIndex = 0;
1111
MSG msg = { };
1112
MON_ADD_MESSAGE *pAddMessage = NULL;
1113
MON_REMOVE_MESSAGE *pRemoveMessage = NULL;
@@ -1128,13 +1130,14 @@ static DWORD WINAPI WaiterThread(
1130
1131
do
1132
{
1131
- dwRet = ::WaitForMultipleObjects(pWaiterContext->cHandles - pWaiterContext->cRequestsFailing, pWaiterContext->rgHandles, FALSE, pWaiterContext->cRequestsPending > 0 ? dwWait : INFINITE);
1133
+ hr = AppWaitForMultipleObjects(pWaiterContext->cHandles - pWaiterContext->cRequestsFailing, pWaiterContext->rgHandles, FALSE, pWaiterContext->cRequestsPending > 0 ? dwWait : INFINITE, &dwSignaledIndex);
1134
+ MonExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for multiple objects.");
1135
1136
uCurrentTime = ::GetTickCount();
1137
uDeltaInMs = uCurrentTime - uLastTimeInMs;
1138
uLastTimeInMs = uCurrentTime;
1139
1137
- if (WAIT_OBJECT_0 == dwRet)
1140
+ if (!fTimedOut && 0 == dwSignaledIndex)
1141
{
1142
do
1143
{
@@ -1391,10 +1394,10 @@ static DWORD WINAPI WaiterThread(
1394
}
1395
} while (fAgain);
1396
}
1394
- else if (dwRet > WAIT_OBJECT_0 && dwRet - WAIT_OBJECT_0 < pWaiterContext->cHandles)
1397
+ else if (!fTimedOut)
1398
{
1399
// OK a handle fired - only notify if it's the actual target, and not just some parent waiting for the target child to exist
1397
- dwRequestIndex = dwRet - WAIT_OBJECT_0 - 1;
1400
+ dwRequestIndex = dwSignaledIndex - 1;
1401
fNotify = (pWaiterContext->rgRequests[dwRequestIndex].dwPathHierarchyIndex == pWaiterContext->rgRequests[dwRequestIndex].cPathHierarchy - 1);
1402
1403
// Initiate re-waits before we notify callback, to ensure we don't miss a single update
@@ -1426,10 +1429,6 @@ static DWORD WINAPI WaiterThread(
1429
}
1430
}
1431
}
1429
- else if (WAIT_TIMEOUT != dwRet)
1430
- {
1431
- MonExitWithLastError(hr, "Failed to wait for multiple objects with return code %u", dwRet);
1432
- }
1432
1433
// OK, now that we've checked all triggered handles (resetting silence period timers appropriately), check for any pending notifications that we can finally fire
1434
// And set dwWait appropriately so we awaken at the right time to fire the next pending notification (in case no further writes occur during that time)
@@ -1726,10 +1725,10 @@ static LRESULT CALLBACK MonWndProc(
1725
DEV_BROADCAST_HANDLE *pHandle = NULL;
1726
DEV_BROADCAST_VOLUME *pVolume = NULL;
1727
DWORD dwUnitMask = 0;
1729
- DWORD er = ERROR_SUCCESS;
1728
WCHAR chDrive = L'\0';
1729
BOOL fArrival = FALSE;
1730
BOOL fReturnTrue = FALSE;
1731
+ BOOL fTimedOut = FALSE;
1732
CREATESTRUCT *pCreateStruct = NULL;
1733
MON_WAITER_CONTEXT *pWaiterContext = NULL;
1734
MON_STRUCT *pm = NULL;
@@ -1821,24 +1820,23 @@ static LRESULT CALLBACK MonWndProc(
1820
}
1821
}
1822
1824
- er = ::WaitForSingleObject(pm->internalWait.hWait, MON_THREAD_WAIT_REMOVE_DEVICE);
1823
+ hr = AppWaitForSingleObject(pm->internalWait.hWait, MON_THREAD_WAIT_REMOVE_DEVICE);
1824
+ MonExitOnWaitObjectFailure(hr, fTimedOut, "WaitForSingleObject failed with non-timeout reason while waiting for response from waiter thread");
1825
+
1826
// Make sure any waiter thread processing really old messages can immediately know that we're no longer waiting for a response
1826
- if (WAIT_OBJECT_0 == er)
1827
+ if (!fTimedOut)
1828
{
1829
// If the response ID matches what we sent, we actually got a valid reply!
1830
if (pm->internalWait.dwReceiveIteration != pm->internalWait.dwSendIteration)
1831
{
1831
- TraceError(HRESULT_FROM_WIN32(er), "Waiter thread received wrong ID reply");
1832
+ TraceError(E_UNEXPECTED, "Waiter thread received wrong ID reply");
1833
}
1834
}
1834
- else if (WAIT_TIMEOUT == er)
1835
- {
1836
- TraceError(HRESULT_FROM_WIN32(er), "No response from any waiter thread for query remove message");
1837
- }
1835
else
1836
{
1840
- MonExitWithLastError(hr, "WaitForSingleObject failed with non-timeout reason while waiting for response from waiter thread");
1837
+ TraceError(HRESULT_FROM_WIN32(WAIT_TIMEOUT), "No response from any waiter thread for query remove message");
1838
}
1839
+
1840
++pm->internalWait.dwSendIteration;
1841
}
1842
}
src/libs/dutil/WixToolset.DUtil/precomp.h
+1
@@ -90,6 +90,7 @@
90
#include "timeutil.h"
91
#include "wndutil.h"
92
#include "thmutil.h"
93
+#include "thrdutil.h"
94
#include "uncutil.h"
95
#include "uriutil.h"
96
#include "userutil.h"
src/libs/dutil/WixToolset.DUtil/procutil.cpp
+18
-24
@@ -17,6 +17,7 @@
17
#define ProcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__)
18
#define ProcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PROCUTIL, e, x, s, __VA_ARGS__)
19
#define ProcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PROCUTIL, g, x, s, __VA_ARGS__)
20
+#define ProcExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_PROCUTIL, x, b, s, __VA_ARGS__)
21
22
23
// private functions
@@ -403,30 +404,25 @@ LExit:
404
extern "C" HRESULT DAPI ProcWaitForCompletion(
405
__in HANDLE hProcess,
406
__in DWORD dwTimeout,
406
- __out DWORD *pReturnCode
407
+ __out_opt DWORD* pdwReturnCode
408
)
409
{
410
HRESULT hr = S_OK;
410
- DWORD er = ERROR_SUCCESS;
411
+ BOOL fTimedOut = FALSE;
412
412
- // Wait for everything to finish
413
- er = ::WaitForSingleObject(hProcess, dwTimeout);
414
- if (WAIT_FAILED == er)
415
- {
416
- ProcExitWithLastError(hr, "Failed to wait for process to complete.");
417
- }
418
- else if (WAIT_TIMEOUT == er)
413
+ // Wait for everything to finish.
414
+ hr = AppWaitForSingleObject(hProcess, dwTimeout);
415
+ ProcExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for process to complete.");
416
+
417
+ if (fTimedOut)
418
{
420
- ExitFunction1(hr = HRESULT_FROM_WIN32(er));
419
+ hr = HRESULT_FROM_WIN32(WAIT_TIMEOUT);
420
}
422
-
423
- if (!::GetExitCodeProcess(hProcess, &er))
421
+ else if (pdwReturnCode && !::GetExitCodeProcess(hProcess, pdwReturnCode))
422
{
423
ProcExitWithLastError(hr, "Failed to get process return code.");
424
}
425
428
- *pReturnCode = er;
429
-
426
LExit:
427
return hr;
428
}
@@ -442,10 +438,10 @@ extern "C" HRESULT DAPI ProcWaitForIds(
438
)
439
{
440
HRESULT hr = S_OK;
445
- DWORD er = ERROR_SUCCESS;
441
HANDLE hProcess = NULL;
447
- HANDLE * rghProcesses = NULL;
442
+ HANDLE* rghProcesses = NULL;
443
DWORD cProcesses = 0;
444
+ BOOL fTimedOut = FALSE;
445
446
rghProcesses = static_cast<HANDLE*>(MemAlloc(sizeof(DWORD) * cProcessIds, TRUE));
447
ProcExitOnNull(rgdwProcessIds, hr, E_OUTOFMEMORY, "Failed to allocate array for process ID Handles.");
@@ -459,16 +455,14 @@ extern "C" HRESULT DAPI ProcWaitForIds(
455
}
456
}
457
462
- er = ::WaitForMultipleObjects(cProcesses, rghProcesses, TRUE, dwMilliseconds);
463
- if (WAIT_FAILED == er)
464
- {
465
- ProcExitWithLastError(hr, "Failed to wait for process to complete.");
466
- }
467
- else if (WAIT_TIMEOUT == er)
458
+ hr = AppWaitForMultipleObjects(cProcesses, rghProcesses, TRUE, dwMilliseconds, NULL);
459
+ ProcExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for processes to complete.");
460
+
461
+ if (fTimedOut)
462
{
469
- ProcExitOnWin32Error(er, hr, "Timed out while waiting for process to complete.");
463
+ ProcExitWithRootFailure(hr, HRESULT_FROM_WIN32(WAIT_TIMEOUT), "Timed out while waiting for processes to complete.");
464
}
471
-
465
+
466
LExit:
467
if (rghProcesses)
468
{
src/libs/dutil/WixToolset.DUtil/thrdutil.cpp
new
+46
@@ -0,0 +1,46 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+#include "precomp.h"
4
+
5
+
6
+// Exit macros
7
+#define ThrdExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__)
8
+#define ThrdExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__)
9
+#define ThrdExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__)
10
+#define ThrdExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__)
11
+#define ThrdExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__)
12
+#define ThrdExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_THRDUTIL, x, e, s, __VA_ARGS__)
13
+#define ThrdExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__)
14
+#define ThrdExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_THRDUTIL, p, x, e, s, __VA_ARGS__)
15
+#define ThrdExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_THRDUTIL, p, x, s, __VA_ARGS__)
16
+#define ThrdExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_THRDUTIL, p, x, e, s, __VA_ARGS__)
17
+#define ThrdExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_THRDUTIL, p, x, s, __VA_ARGS__)
18
+#define ThrdExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_THRDUTIL, e, x, s, __VA_ARGS__)
19
+#define ThrdExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_THRDUTIL, g, x, s, __VA_ARGS__)
20
+#define ThrdExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_THRDUTIL, x, b, s, __VA_ARGS__)
21
+
22
+DAPI_(HRESULT) ThrdWaitForCompletion(
23
+ __in HANDLE hThread,
24
+ __in DWORD dwTimeout,
25
+ __out_opt DWORD *pdwReturnCode
26
+ )
27
+{
28
+ HRESULT hr = S_OK;
29
+ BOOL fTimedOut = FALSE;
30
+
31
+ // Wait for everything to finish.
32
+ hr = AppWaitForSingleObject(hThread, dwTimeout);
33
+ ThrdExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for thread to complete.");
34
+
35
+ if (fTimedOut)
36
+ {
37
+ hr = HRESULT_FROM_WIN32(WAIT_TIMEOUT);
38
+ }
39
+ else if (pdwReturnCode && !::GetExitCodeThread(hThread, pdwReturnCode))
40
+ {
41
+ ThrdExitWithLastError(hr, "Failed to get thread return code.");
42
+ }
43
+
44
+LExit:
45
+ return hr;
46
+}
src/libs/dutil/test/DUtilUnitTest/AppUtilTests.cpp
new
+60
@@ -0,0 +1,60 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+#include "precomp.h"
4
+
5
+namespace DutilTests
6
+{
7
+ using namespace System;
8
+ using namespace Xunit;
9
+ using namespace WixBuildTools::TestSupport;
10
+
11
+ public ref class AppUtil
12
+ {
13
+ public:
14
+ [Fact]
15
+ void WaitForMultipleObjectsTest()
16
+ {
17
+ HRESULT hr = S_OK;
18
+ HANDLE hOne = NULL;
19
+ HANDLE hTwo = NULL;
20
+ HANDLE rghHandles[2] = { };
21
+ DWORD dwSignaledIndex = 0;
22
+
23
+ try
24
+ {
25
+ hOne = ::CreateEventW(NULL, TRUE, FALSE, NULL);
26
+ if (!hOne)
27
+ {
28
+ hr = HRESULT_FROM_WIN32(::GetLastError());
29
+ NativeAssert::Succeeded(FAILED(hr) ? hr : E_FAIL, "Failed to create event.");
30
+ }
31
+
32
+ hTwo = ::CreateEventW(NULL, TRUE, TRUE, NULL);
33
+ if (!hTwo)
34
+ {
35
+ hr = HRESULT_FROM_WIN32(::GetLastError());
36
+ NativeAssert::Succeeded(FAILED(hr) ? hr : E_FAIL, "Failed to create event.");
37
+ }
38
+
39
+ rghHandles[0] = hOne;
40
+ rghHandles[1] = hTwo;
41
+
42
+ hr = AppWaitForMultipleObjects(countof(rghHandles), rghHandles, FALSE, 0, &dwSignaledIndex);
43
+ NativeAssert::Succeeded(hr, "Failed to wait for multiple objects.");
44
+ Assert::Equal<DWORD>(1, dwSignaledIndex);
45
+
46
+ rghHandles[0] = hTwo;
47
+ rghHandles[1] = hOne;
48
+
49
+ hr = AppWaitForMultipleObjects(countof(rghHandles), rghHandles, FALSE, 0, &dwSignaledIndex);
50
+ NativeAssert::Succeeded(hr, "Failed to wait for multiple objects.");
51
+ Assert::Equal<DWORD>(0, dwSignaledIndex);
52
+ }
53
+ finally
54
+ {
55
+ ReleaseHandle(hOne);
56
+ ReleaseHandle(hTwo);
57
+ }
58
+ }
59
+ };
60
+}
src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj
+1
@@ -44,6 +44,7 @@
44
</PropertyGroup>
45
46
<ItemGroup>
47
+ <ClCompile Include="AppUtilTests.cpp" />
48
<ClCompile Include="ApupUtilTests.cpp" />
49
<ClCompile Include="AssemblyInfo.cpp" />
50
<ClCompile Include="DictUtilTest.cpp" />
src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters
+3
@@ -15,6 +15,9 @@
15
</Filter>
16
</ItemGroup>
17
<ItemGroup>
18
+ <ClCompile Include="AppUtilTests.cpp">
19
+ <Filter>Source Files</Filter>
20
+ </ClCompile>
21
<ClCompile Include="ApupUtilTests.cpp">
22
<Filter>Source Files</Filter>
23
</ClCompile>
src/libs/dutil/test/DUtilUnitTest/precomp.h
+1
@@ -13,6 +13,7 @@
13
#include <dutil.h>
14
15
#include <verutil.h>
16
+#include <apputil.h>
17
#include <atomutil.h>
18
#include <dictutil.h>
19
#include <dirutil.h>