From 7cca75c8e95f129a21c33f1f4568e90e9e397f9d Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 29 Jun 2022 10:28:53 -0500 Subject: Add AppWaitForSingleObject/MultipleObjects, ThreadWaitForCompletion. --- src/burn/engine/apply.cpp | 26 ++++----- src/burn/engine/cabextract.cpp | 42 ++++++------- src/burn/engine/core.cpp | 54 ++++++++--------- src/burn/engine/core.h | 14 +++++ src/burn/engine/elevation.cpp | 11 +--- src/burn/engine/netfxchainer.cpp | 31 +++++----- src/burn/engine/pipe.cpp | 34 +++++------ src/burn/engine/precomp.h | 1 + src/burn/test/BurnUnitTest/ElevationTest.cpp | 3 +- src/burn/test/BurnUnitTest/precomp.h | 1 + src/libs/dutil/WixToolset.DUtil/apputil.cpp | 68 ++++++++++++++++++++++ src/libs/dutil/WixToolset.DUtil/dutil.vcxproj | 2 + .../dutil/WixToolset.DUtil/dutil.vcxproj.filters | 6 ++ src/libs/dutil/WixToolset.DUtil/inc/apputil.h | 21 +++++++ src/libs/dutil/WixToolset.DUtil/inc/dutil.h | 2 + src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h | 1 + src/libs/dutil/WixToolset.DUtil/inc/procutil.h | 2 +- src/libs/dutil/WixToolset.DUtil/inc/thrdutil.h | 22 +++++++ src/libs/dutil/WixToolset.DUtil/monutil.cpp | 34 +++++------ src/libs/dutil/WixToolset.DUtil/precomp.h | 1 + src/libs/dutil/WixToolset.DUtil/procutil.cpp | 42 ++++++------- src/libs/dutil/WixToolset.DUtil/thrdutil.cpp | 46 +++++++++++++++ src/libs/dutil/test/DUtilUnitTest/AppUtilTests.cpp | 60 +++++++++++++++++++ .../dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj | 1 + .../DUtilUnitTest/DUtilUnitTest.vcxproj.filters | 3 + src/libs/dutil/test/DUtilUnitTest/precomp.h | 1 + 26 files changed, 380 insertions(+), 149 deletions(-) create mode 100644 src/libs/dutil/WixToolset.DUtil/inc/thrdutil.h create mode 100644 src/libs/dutil/WixToolset.DUtil/thrdutil.cpp create mode 100644 src/libs/dutil/test/DUtilUnitTest/AppUtilTests.cpp diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp index e5b978f1..dd99a5cd 100644 --- a/src/burn/engine/apply.cpp +++ b/src/burn/engine/apply.cpp @@ -2333,6 +2333,8 @@ static HRESULT DoExecuteAction( HRESULT hr = S_OK; HANDLE rghWait[2] = { }; + DWORD dwSignaledIndex = 0; + DWORD dwExitCode = 0; BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; BOOL fRetry = FALSE; BOOL fStopWusaService = FALSE; @@ -2356,27 +2358,23 @@ static HRESULT DoExecuteAction( // wait for cache sync-point rghWait[0] = pExecuteAction->waitCachePackage.pPackage->hCacheEvent; rghWait[1] = pContext->pApplyContext->hCacheThread; - switch (::WaitForMultipleObjects(rghWait[1] ? 2 : 1, rghWait, FALSE, INFINITE)) + + hr = AppWaitForMultipleObjects(rghWait[1] ? 2 : 1, rghWait, FALSE, INFINITE, &dwSignaledIndex); + ExitOnFailure(hr, "Failed to wait for cache check-point."); + + switch (dwSignaledIndex) { - case WAIT_OBJECT_0: + case 0: break; - - case WAIT_OBJECT_0 + 1: - if (!::GetExitCodeThread(pContext->pApplyContext->hCacheThread, (DWORD*)&hr)) + case 1: + if (!::GetExitCodeThread(pContext->pApplyContext->hCacheThread, &dwExitCode)) { ExitWithLastError(hr, "Failed to get cache thread exit code."); } - if (SUCCEEDED(hr)) - { - hr = E_UNEXPECTED; - } - ExitOnFailure(hr, "Cache thread exited unexpectedly."); - - case WAIT_FAILED: __fallthrough; - default: - ExitWithLastError(hr, "Failed to wait for cache check-point."); + ExitWithRootFailure(hr, E_UNEXPECTED, "Cache thread exited unexpectedly with exit code: %u.", dwExitCode); } + break; case BURN_EXECUTE_ACTION_TYPE_RELATED_BUNDLE: diff --git a/src/burn/engine/cabextract.cpp b/src/burn/engine/cabextract.cpp index 5a02ff8a..56146a39 100644 --- a/src/burn/engine/cabextract.cpp +++ b/src/burn/engine/cabextract.cpp @@ -262,10 +262,8 @@ extern "C" HRESULT CabExtractClose( } // wait for thread to terminate - if (WAIT_OBJECT_0 != ::WaitForSingleObject(pContext->Cabinet.hThread, INFINITE)) - { - ExitWithLastError(hr, "Failed to wait for thread to terminate."); - } + hr = AppWaitForSingleObject(pContext->Cabinet.hThread, INFINITE); + ExitOnFailure(hr, "Failed to wait for thread to terminate."); } LExit: @@ -306,29 +304,31 @@ static HRESULT WaitForOperation( { HRESULT hr = S_OK; HANDLE rghWait[2] = { }; + DWORD dwSignaledIndex = 0; // wait for operation complete event rghWait[0] = pContext->Cabinet.hOperationCompleteEvent; rghWait[1] = pContext->Cabinet.hThread; - switch (::WaitForMultipleObjects(countof(rghWait), rghWait, FALSE, INFINITE)) + + hr = AppWaitForMultipleObjects(countof(rghWait), rghWait, FALSE, INFINITE, &dwSignaledIndex); + ExitOnFailure(hr, "Failed to wait for operation complete event."); + + switch (dwSignaledIndex) { - case WAIT_OBJECT_0: + case 0: if (!::ResetEvent(pContext->Cabinet.hOperationCompleteEvent)) { ExitWithLastError(hr, "Failed to reset operation complete event."); } - break; - case WAIT_OBJECT_0 + 1: + break; + case 1: if (!::GetExitCodeThread(pContext->Cabinet.hThread, (DWORD*)&hr)) { ExitWithLastError(hr, "Failed to get extraction thread exit code."); } - ExitFunction(); - case WAIT_FAILED: __fallthrough; - default: - ExitWithLastError(hr, "Failed to wait for operation complete event."); + ExitFunction(); } // clear operation @@ -430,10 +430,8 @@ static DWORD WINAPI ExtractThreadProc( } // wait for begin operation event - if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE)) - { - ExitWithLastError(hr, "Failed to wait for begin operation event."); - } + hr = AppWaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE); + ExitOnFailure(hr, "Failed to wait for begin operation event."); if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent)) { @@ -517,10 +515,8 @@ static INT_PTR CopyFileCallback( } // wait for begin operation event - if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE)) - { - ExitWithLastError(hr, "Failed to wait for begin operation event."); - } + hr = AppWaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE); + ExitOnFailure(hr, "Failed to wait for begin operation event."); if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent)) { @@ -552,10 +548,8 @@ static INT_PTR CopyFileCallback( } // wait for begin operation event - if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE)) - { - ExitWithLastError(hr, "Failed to wait for begin operation event."); - } + hr = AppWaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE); + ExitOnFailure(hr, "Failed to wait for begin operation event."); if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent)) { diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp index c1e3a12c..716e5af1 100644 --- a/src/burn/engine/core.cpp +++ b/src/burn/engine/core.cpp @@ -12,6 +12,9 @@ struct BURN_CACHE_THREAD_CONTEXT }; +static PFN_PROCWAITFORCOMPLETION vpfnProcWaitForCompletion = ProcWaitForCompletion; + + // internal function declarations static HRESULT CoreRecreateCommandLine( @@ -65,9 +68,6 @@ static HRESULT DetectPackagePayloadsCached( static DWORD WINAPI CacheThreadProc( __in LPVOID lpThreadParameter ); -static HRESULT WaitForCacheThread( - __in HANDLE hCacheThread - ); static void LogPackages( __in_opt const BURN_PACKAGE* pUpgradeBundlePackage, __in_opt const BURN_PACKAGE* pForwardCompatibleBundlePackage, @@ -636,6 +636,7 @@ extern "C" HRESULT CoreApply( BURN_APPLY_CONTEXT applyContext = { }; BOOL fDeleteApplyCs = FALSE; BURN_CACHE_THREAD_CONTEXT cacheThreadContext = { }; + DWORD dwCacheExitCode = 0; BOOL fRollbackCache = FALSE; DWORD dwPhaseCount = 0; BOOTSTRAPPER_APPLYCOMPLETE_ACTION applyCompleteAction = BOOTSTRAPPER_APPLYCOMPLETE_ACTION_NONE; @@ -744,7 +745,10 @@ extern "C" HRESULT CoreApply( // If we're not caching in parallel, wait for the cache thread to terminate. if (!pEngineState->fParallelCacheAndExecute) { - hr = WaitForCacheThread(applyContext.hCacheThread); + hr = ThrdWaitForCompletion(applyContext.hCacheThread, INFINITE, &dwCacheExitCode); + ExitOnFailure(hr, "Failed to wait for cache thread before execute."); + + hr = (HRESULT)dwCacheExitCode; ExitOnFailure(hr, "Failed while caching, aborting execution."); ReleaseHandle(applyContext.hCacheThread); @@ -761,10 +765,12 @@ extern "C" HRESULT CoreApply( // Wait for cache thread to terminate, this should return immediately unless we're waiting for layout to complete. if (applyContext.hCacheThread) { - HRESULT hrCached = WaitForCacheThread(applyContext.hCacheThread); + HRESULT hrCached = ThrdWaitForCompletion(applyContext.hCacheThread, INFINITE, &dwCacheExitCode); + ExitOnFailure(hrCached, "Failed to wait for cache thread after execute."); + if (SUCCEEDED(hr)) { - hr = hrCached; + hr = (HRESULT)dwCacheExitCode; } } @@ -1940,6 +1946,22 @@ LExit: return hr; } +extern "C" void CoreFunctionOverride( + __in_opt PFN_PROCWAITFORCOMPLETION pfnProcWaitForCompletion + ) +{ + vpfnProcWaitForCompletion = pfnProcWaitForCompletion; +} + +extern "C" HRESULT DAPI CoreWaitForProcCompletion( + __in HANDLE hProcess, + __in DWORD dwTimeout, + __out DWORD* pdwReturnCode + ) +{ + return vpfnProcWaitForCompletion(hProcess, dwTimeout, pdwReturnCode); +} + // internal helper functions static HRESULT AppendEscapedArgumentToCommandLine( @@ -2268,26 +2290,6 @@ LExit: return (DWORD)hr; } -static HRESULT WaitForCacheThread( - __in HANDLE hCacheThread - ) -{ - HRESULT hr = S_OK; - - if (WAIT_OBJECT_0 != ::WaitForSingleObject(hCacheThread, INFINITE)) - { - ExitWithLastError(hr, "Failed to wait for cache thread to terminate."); - } - - if (!::GetExitCodeThread(hCacheThread, (DWORD*)&hr)) - { - ExitWithLastError(hr, "Failed to get cache thread exit code."); - } - -LExit: - return hr; -} - static void LogPackages( __in_opt const BURN_PACKAGE* pUpgradeBundlePackage, __in_opt const BURN_PACKAGE* pForwardCompatibleBundlePackage, diff --git a/src/burn/engine/core.h b/src/burn/engine/core.h index 28d7ea78..14dbabcc 100644 --- a/src/burn/engine/core.h +++ b/src/burn/engine/core.h @@ -174,6 +174,12 @@ typedef struct _BURN_APPLY_CONTEXT DWORD dwCacheCheckpoint; } BURN_APPLY_CONTEXT; +typedef HRESULT (DAPI *PFN_PROCWAITFORCOMPLETION)( + __in HANDLE hProcess, + __in DWORD dwTimeout, + __out DWORD* pReturnCode + ); + // function declarations @@ -280,6 +286,14 @@ HRESULT CoreParseCommandLine( __inout HANDLE* phSectionFile, __inout HANDLE* phSourceEngineFile ); +void CoreFunctionOverride( + __in_opt PFN_PROCWAITFORCOMPLETION pfnProcWaitForCompletion + ); +HRESULT DAPI CoreWaitForProcCompletion( + __in HANDLE hProcess, + __in DWORD dwTimeout, + __out_opt DWORD* pdwReturnCode + ); #if defined(__cplusplus) } diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp index 4a5be8ec..e165a257 100644 --- a/src/burn/engine/elevation.cpp +++ b/src/burn/engine/elevation.cpp @@ -1687,15 +1687,8 @@ static HRESULT WaitForElevatedChildCacheThread( HRESULT hr = S_OK; DWORD dwExitCode = ERROR_SUCCESS; - if (WAIT_OBJECT_0 != ::WaitForSingleObject(hCacheThread, BURN_TIMEOUT)) - { - ExitWithLastError(hr, "Failed to wait for cache thread to terminate."); - } - - if (!::GetExitCodeThread(hCacheThread, &dwExitCode)) - { - ExitWithLastError(hr, "Failed to get cache thread exit code."); - } + hr = ThrdWaitForCompletion(hCacheThread, BURN_TIMEOUT, &dwExitCode); + ExitOnFailure(hr, "Failed to wait for cache thread to complete."); AssertSz(dwExitCode == dwExpectedExitCode, "Cache thread should have exited with the expected exit code."); diff --git a/src/burn/engine/netfxchainer.cpp b/src/burn/engine/netfxchainer.cpp index 6f223eed..68ddb093 100644 --- a/src/burn/engine/netfxchainer.cpp +++ b/src/burn/engine/netfxchainer.cpp @@ -338,7 +338,8 @@ extern "C" HRESULT NetFxRunChainer( ) { HRESULT hr = S_OK; - DWORD er = 0; + DWORD dwSignaledIndex = 0; + BOOL fTimedOut = 0; WCHAR wzGuid[GUID_STRING_LENGTH]; LPWSTR sczEventName = NULL; LPWSTR sczSectionName = NULL; @@ -381,9 +382,17 @@ extern "C" HRESULT NetFxRunChainer( for (;;) { - er = ::WaitForMultipleObjects(2, handles, FALSE, 100); - if (WAIT_OBJECT_0 == er) + hr = AppWaitForMultipleObjects(2, handles, FALSE, 100, &dwSignaledIndex); + ExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for netfx chainer process to complete"); + + if (fTimedOut) + { + continue; + } + + switch (dwSignaledIndex) { + case 0: // Process has exited *pdwExitCode = NetFxGetResult(pNetfxChainer, &hrInternalError); if (E_PENDING == *pdwExitCode) @@ -396,21 +405,17 @@ extern "C" HRESULT NetFxRunChainer( else if (FAILED(hrInternalError)) { // push internal error message - OnNetFxError(pNetfxChainer, hrInternalError, pfnGenericMessageHandler, pvContext); + hr = OnNetFxError(pNetfxChainer, hrInternalError, pfnGenericMessageHandler, pvContext); ExitOnFailure(hr, "Failed to send internal error message from netfx chainer."); - } + } - break; - } - else if (WAIT_OBJECT_0 + 1 == er) - { + ExitFunction(); + case 1: // Chainee has notified us of a change. hr = ProcessNetFxMessage(pNetfxChainer, pfnGenericMessageHandler, pvContext); ExitOnFailure(hr, "Failed to process netfx chainer message."); - } - else if (WAIT_FAILED == er) - { - ExitWithLastError(hr, "Failed to wait for netfx chainer process to complete"); + + break; } } diff --git a/src/burn/engine/pipe.cpp b/src/burn/engine/pipe.cpp index e6c9905b..9529ef40 100644 --- a/src/burn/engine/pipe.cpp +++ b/src/burn/engine/pipe.cpp @@ -422,6 +422,7 @@ extern "C" HRESULT PipeTerminateChildProcess( HRESULT hr = S_OK; BYTE* pbData = NULL; SIZE_T cbData = 0; + BOOL fTimedOut = FALSE; // Prepare the exit message. hr = BuffWriteNumber(&pbData, &cbData, dwParentExitCode); @@ -443,31 +444,28 @@ extern "C" HRESULT PipeTerminateChildProcess( // If we were able to get a handle to the other process, wait for it to exit. if (pConnection->hProcess) { - if (WAIT_FAILED == ::WaitForSingleObject(pConnection->hProcess, PIPE_WAIT_FOR_CONNECTION * PIPE_RETRY_FOR_CONNECTION)) - { - ExitWithLastError(hr, "Failed to wait for child process exit."); - } + hr = AppWaitForSingleObject(pConnection->hProcess, PIPE_WAIT_FOR_CONNECTION * PIPE_RETRY_FOR_CONNECTION); + ExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for child process exit."); + + AssertSz(!fTimedOut, "Timed out while waiting for child process to exit."); + } #ifdef DEBUG + if (pConnection->hProcess && !fTimedOut) + { DWORD dwChildExitCode = 0; - DWORD dwErrorCode = ERROR_SUCCESS; - BOOL fReturnedExitCode = ::GetExitCodeProcess(pConnection->hProcess, &dwChildExitCode); - if (!fReturnedExitCode) - { - dwErrorCode = ::GetLastError(); // if the other process is elevated and we are not, then we'll get ERROR_ACCESS_DENIED. + HRESULT hrDebug = S_OK; - // The unit test use a thread instead of a process so try to get the exit code from - // the thread because we failed to get it from the process. - if (ERROR_INVALID_HANDLE == dwErrorCode) - { - fReturnedExitCode = ::GetExitCodeThread(pConnection->hProcess, &dwChildExitCode); - } + hrDebug = CoreWaitForProcCompletion(pConnection->hProcess, 0, &dwChildExitCode); + if (E_ACCESSDENIED != hrDebug) // if the other process is elevated and we are not, then we'll get ERROR_ACCESS_DENIED. + { + TraceError(hrDebug, "Failed to wait for child process completion."); } - AssertSz((fReturnedExitCode && dwChildExitCode == dwParentExitCode) || - (!fReturnedExitCode && ERROR_ACCESS_DENIED == dwErrorCode), + + AssertSz(E_ACCESSDENIED == hrDebug || dwChildExitCode == dwParentExitCode, "Child elevated process did not return matching exit code to parent process."); -#endif } +#endif LExit: return hr; diff --git a/src/burn/engine/precomp.h b/src/burn/engine/precomp.h index bc7046f6..e2d1b5cd 100644 --- a/src/burn/engine/precomp.h +++ b/src/burn/engine/precomp.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include diff --git a/src/burn/test/BurnUnitTest/ElevationTest.cpp b/src/burn/test/BurnUnitTest/ElevationTest.cpp index 97d76b7d..ce34b4b1 100644 --- a/src/burn/test/BurnUnitTest/ElevationTest.cpp +++ b/src/burn/test/BurnUnitTest/ElevationTest.cpp @@ -54,7 +54,6 @@ namespace Bootstrapper HRESULT hr = S_OK; BURN_ENGINE_STATE engineState = { }; BURN_PIPE_CONNECTION* pConnection = &engineState.companionConnection; - HANDLE hEvent = NULL; DWORD dwResult = S_OK; engineState.sczBundleEngineWorkingPath = L"tests\\ignore\\this\\path\\to\\burn.exe"; @@ -62,6 +61,7 @@ namespace Bootstrapper try { ShelFunctionOverride(ElevateTest_ShellExecuteExW); + CoreFunctionOverride(ThrdWaitForCompletion); PipeConnectionInitialize(pConnection); @@ -87,7 +87,6 @@ namespace Bootstrapper finally { PipeConnectionUninitialize(pConnection); - ReleaseHandle(hEvent); } } }; diff --git a/src/burn/test/BurnUnitTest/precomp.h b/src/burn/test/BurnUnitTest/precomp.h index 92c8c4c0..69c62cb2 100644 --- a/src/burn/test/BurnUnitTest/precomp.h +++ b/src/burn/test/BurnUnitTest/precomp.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/src/libs/dutil/WixToolset.DUtil/apputil.cpp b/src/libs/dutil/WixToolset.DUtil/apputil.cpp index 9e75082a..42f589dc 100644 --- a/src/libs/dutil/WixToolset.DUtil/apputil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/apputil.cpp @@ -313,6 +313,74 @@ LExit: return hr; } +DAPI_(HRESULT) AppWaitForSingleObject( + __in HANDLE hHandle, + __in DWORD dwMilliseconds + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + + dwResult = ::WaitForSingleObject(hHandle, dwMilliseconds); + if (WAIT_TIMEOUT == dwResult) + { + ExitFunction1(hr = HRESULT_FROM_WIN32(dwResult)); + } + else if (WAIT_ABANDONED == dwResult) + { + AppExitOnWin32Error(dwResult, hr, "Abandoned wait for single object."); + } + else if (WAIT_OBJECT_0 != dwResult) + { + AssertSz(WAIT_FAILED == dwResult, "Unexpected return code from WaitForSingleObject."); + AppExitWithLastError(hr, "Failed to wait for single object."); + } + +LExit: + return hr; +} + +DAPI_(HRESULT) AppWaitForMultipleObjects( + __in DWORD dwCount, + __in const HANDLE* rghHandles, + __in BOOL fWaitAll, + __in DWORD dwMilliseconds, + __out_opt DWORD* pdwSignaledIndex + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + DWORD dwSignaledIndex = dwCount; + + dwResult = ::WaitForMultipleObjects(dwCount, rghHandles, fWaitAll, dwMilliseconds); + if (WAIT_TIMEOUT == dwResult) + { + ExitFunction1(hr = HRESULT_FROM_WIN32(dwResult)); + } + else if (WAIT_ABANDONED_0 <= dwResult && (WAIT_ABANDONED_0 + dwCount) > dwResult) + { + dwSignaledIndex = dwResult - WAIT_ABANDONED_0; + AppExitOnWin32Error(dwResult, hr, "Abandoned wait for multiple objects, index: %u.", dwSignaledIndex); + } + else if (WAIT_OBJECT_0 <= dwResult && (WAIT_OBJECT_0 + dwCount) > dwResult) + { + dwSignaledIndex = dwResult - WAIT_OBJECT_0; + } + else + { + AssertSz(WAIT_FAILED == dwResult, "Unexpected return code from WaitForMultipleObjects."); + AppExitWithLastError(hr, "Failed to wait for multiple objects."); + } + +LExit: + if (pdwSignaledIndex) + { + *pdwSignaledIndex = dwSignaledIndex; + } + + return hr; +} + static HRESULT EscapeCommandLineArgument( __in_z LPCWSTR wzArgument, __out_z LPWSTR* psczEscaped diff --git a/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj index 3e3c42b7..c84b98fe 100644 --- a/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj +++ b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj @@ -104,6 +104,7 @@ + @@ -164,6 +165,7 @@ + diff --git a/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters index 07698f9e..efadef6f 100644 --- a/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters +++ b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters @@ -162,6 +162,9 @@ Source Files + + Source Files + Source Files @@ -344,6 +347,9 @@ Header Files + + Header Files + Header Files diff --git a/src/libs/dutil/WixToolset.DUtil/inc/apputil.h b/src/libs/dutil/WixToolset.DUtil/inc/apputil.h index 95a98e73..e2812ee4 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/apputil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/apputil.h @@ -84,6 +84,27 @@ HRESULT DAPI AppEscapeCommandLineArgumentFormattedArgs( __in va_list args ); +/******************************************************************** +AppWaitForSingleObject - wrapper for ::WaitForSingleObject. + +********************************************************************/ +HRESULT DAPI AppWaitForSingleObject( + __in HANDLE hHandle, + __in DWORD dwMilliseconds + ); + +/******************************************************************** +AppWaitForMultipleObjects - wrapper for ::WaitForMultipleObjects. + +********************************************************************/ +HRESULT DAPI AppWaitForMultipleObjects( + __in DWORD dwCount, + __in const HANDLE* rghHandles, + __in BOOL fWaitAll, + __in DWORD dwMilliseconds, + __out_opt DWORD* pdwSignaledIndex + ); + #ifdef __cplusplus } #endif diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dutil.h b/src/libs/dutil/WixToolset.DUtil/inc/dutil.h index 6f099f35..2db64812 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/dutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/dutil.h @@ -130,6 +130,7 @@ void DAPI Dutil_RootFailure(__in_z LPCSTR szFile, __in int iLine, __in HRESULT h #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; } #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__); } #define ExitOnRequiredXmlQueryFailureSource(d, x, s, ...) { if (S_FALSE == x) { x = E_NOTFOUND; } ExitOnRootFailureSource(d, x, s, __VA_ARGS__); } +#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__); } #define ExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__) #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 #define ExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DEFAULT, e, x, s, __VA_ARGS__) #define ExitOnOptionalXmlQueryFailure(x, b, s, ...) ExitOnOptionalXmlQueryFailureSource(DUTIL_SOURCE_DEFAULT, x, b, s, __VA_ARGS__) #define ExitOnRequiredXmlQueryFailure(x, s, ...) ExitOnRequiredXmlQueryFailureSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__) +#define ExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_DEFAULT, x, b, s, __VA_ARGS__) // release macros #define ReleaseObject(x) if (x) { x->Release(); } diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h b/src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h index f1dd5d1a..664c21e5 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h @@ -63,6 +63,7 @@ typedef enum DUTIL_SOURCE DUTIL_SOURCE_VERUTIL, DUTIL_SOURCE_WNDUTIL, DUTIL_SOURCE_ENVUTIL, + DUTIL_SOURCE_THRDUTIL, DUTIL_SOURCE_EXTERNAL = 256, } DUTIL_SOURCE; diff --git a/src/libs/dutil/WixToolset.DUtil/inc/procutil.h b/src/libs/dutil/WixToolset.DUtil/inc/procutil.h index d5ab9242..d61d91b5 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/procutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/procutil.h @@ -58,7 +58,7 @@ HRESULT DAPI ProcExecute( HRESULT DAPI ProcWaitForCompletion( __in HANDLE hProcess, __in DWORD dwTimeout, - __out DWORD *pReturnCode + __out_opt DWORD* pdwReturnCode ); HRESULT DAPI ProcWaitForIds( __in_ecount(cProcessIds) const DWORD* pdwProcessIds, diff --git a/src/libs/dutil/WixToolset.DUtil/inc/thrdutil.h b/src/libs/dutil/WixToolset.DUtil/inc/thrdutil.h new file mode 100644 index 00000000..47e159a1 --- /dev/null +++ b/src/libs/dutil/WixToolset.DUtil/inc/thrdutil.h @@ -0,0 +1,22 @@ +#pragma once +// 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. + + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************** + ThrdWaitForCompletion - waits for thread to complete and gets return code. + + *******************************************************************/ +HRESULT DAPI ThrdWaitForCompletion( + __in HANDLE hThread, + __in DWORD dwTimeout, + __out_opt DWORD* pdwReturnCode + ); + +#ifdef __cplusplus +} +#endif + diff --git a/src/libs/dutil/WixToolset.DUtil/monutil.cpp b/src/libs/dutil/WixToolset.DUtil/monutil.cpp index 6ad75b56..10954164 100644 --- a/src/libs/dutil/WixToolset.DUtil/monutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/monutil.cpp @@ -16,6 +16,7 @@ #define MonExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_MONUTIL, p, x, s, __VA_ARGS__) #define MonExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_MONUTIL, e, x, s, __VA_ARGS__) #define MonExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_MONUTIL, g, x, s, __VA_ARGS__) +#define MonExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_MONUTIL, x, b, s, __VA_ARGS__) const int MON_THREAD_GROWTH = 5; const int MON_ARRAY_GROWTH = 40; @@ -1101,11 +1102,12 @@ static DWORD WINAPI WaiterThread( { HRESULT hr = S_OK; HRESULT hrTemp = S_OK; - DWORD dwRet = 0; BOOL fAgain = FALSE; BOOL fContinue = TRUE; BOOL fNotify = FALSE; BOOL fRet = FALSE; + BOOL fTimedOut = FALSE; + DWORD dwSignaledIndex = 0; MSG msg = { }; MON_ADD_MESSAGE *pAddMessage = NULL; MON_REMOVE_MESSAGE *pRemoveMessage = NULL; @@ -1128,13 +1130,14 @@ static DWORD WINAPI WaiterThread( do { - dwRet = ::WaitForMultipleObjects(pWaiterContext->cHandles - pWaiterContext->cRequestsFailing, pWaiterContext->rgHandles, FALSE, pWaiterContext->cRequestsPending > 0 ? dwWait : INFINITE); + hr = AppWaitForMultipleObjects(pWaiterContext->cHandles - pWaiterContext->cRequestsFailing, pWaiterContext->rgHandles, FALSE, pWaiterContext->cRequestsPending > 0 ? dwWait : INFINITE, &dwSignaledIndex); + MonExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for multiple objects."); uCurrentTime = ::GetTickCount(); uDeltaInMs = uCurrentTime - uLastTimeInMs; uLastTimeInMs = uCurrentTime; - if (WAIT_OBJECT_0 == dwRet) + if (!fTimedOut && 0 == dwSignaledIndex) { do { @@ -1391,10 +1394,10 @@ static DWORD WINAPI WaiterThread( } } while (fAgain); } - else if (dwRet > WAIT_OBJECT_0 && dwRet - WAIT_OBJECT_0 < pWaiterContext->cHandles) + else if (!fTimedOut) { // OK a handle fired - only notify if it's the actual target, and not just some parent waiting for the target child to exist - dwRequestIndex = dwRet - WAIT_OBJECT_0 - 1; + dwRequestIndex = dwSignaledIndex - 1; fNotify = (pWaiterContext->rgRequests[dwRequestIndex].dwPathHierarchyIndex == pWaiterContext->rgRequests[dwRequestIndex].cPathHierarchy - 1); // Initiate re-waits before we notify callback, to ensure we don't miss a single update @@ -1426,10 +1429,6 @@ static DWORD WINAPI WaiterThread( } } } - else if (WAIT_TIMEOUT != dwRet) - { - MonExitWithLastError(hr, "Failed to wait for multiple objects with return code %u", dwRet); - } // OK, now that we've checked all triggered handles (resetting silence period timers appropriately), check for any pending notifications that we can finally fire // 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( DEV_BROADCAST_HANDLE *pHandle = NULL; DEV_BROADCAST_VOLUME *pVolume = NULL; DWORD dwUnitMask = 0; - DWORD er = ERROR_SUCCESS; WCHAR chDrive = L'\0'; BOOL fArrival = FALSE; BOOL fReturnTrue = FALSE; + BOOL fTimedOut = FALSE; CREATESTRUCT *pCreateStruct = NULL; MON_WAITER_CONTEXT *pWaiterContext = NULL; MON_STRUCT *pm = NULL; @@ -1821,24 +1820,23 @@ static LRESULT CALLBACK MonWndProc( } } - er = ::WaitForSingleObject(pm->internalWait.hWait, MON_THREAD_WAIT_REMOVE_DEVICE); + hr = AppWaitForSingleObject(pm->internalWait.hWait, MON_THREAD_WAIT_REMOVE_DEVICE); + MonExitOnWaitObjectFailure(hr, fTimedOut, "WaitForSingleObject failed with non-timeout reason while waiting for response from waiter thread"); + // Make sure any waiter thread processing really old messages can immediately know that we're no longer waiting for a response - if (WAIT_OBJECT_0 == er) + if (!fTimedOut) { // If the response ID matches what we sent, we actually got a valid reply! if (pm->internalWait.dwReceiveIteration != pm->internalWait.dwSendIteration) { - TraceError(HRESULT_FROM_WIN32(er), "Waiter thread received wrong ID reply"); + TraceError(E_UNEXPECTED, "Waiter thread received wrong ID reply"); } } - else if (WAIT_TIMEOUT == er) - { - TraceError(HRESULT_FROM_WIN32(er), "No response from any waiter thread for query remove message"); - } else { - MonExitWithLastError(hr, "WaitForSingleObject failed with non-timeout reason while waiting for response from waiter thread"); + TraceError(HRESULT_FROM_WIN32(WAIT_TIMEOUT), "No response from any waiter thread for query remove message"); } + ++pm->internalWait.dwSendIteration; } } diff --git a/src/libs/dutil/WixToolset.DUtil/precomp.h b/src/libs/dutil/WixToolset.DUtil/precomp.h index c9e4f74a..b628d271 100644 --- a/src/libs/dutil/WixToolset.DUtil/precomp.h +++ b/src/libs/dutil/WixToolset.DUtil/precomp.h @@ -90,6 +90,7 @@ #include "timeutil.h" #include "wndutil.h" #include "thmutil.h" +#include "thrdutil.h" #include "uncutil.h" #include "uriutil.h" #include "userutil.h" diff --git a/src/libs/dutil/WixToolset.DUtil/procutil.cpp b/src/libs/dutil/WixToolset.DUtil/procutil.cpp index 340a0cda..29f575ae 100644 --- a/src/libs/dutil/WixToolset.DUtil/procutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/procutil.cpp @@ -17,6 +17,7 @@ #define ProcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__) #define ProcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PROCUTIL, e, x, s, __VA_ARGS__) #define ProcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PROCUTIL, g, x, s, __VA_ARGS__) +#define ProcExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_PROCUTIL, x, b, s, __VA_ARGS__) // private functions @@ -403,30 +404,25 @@ LExit: extern "C" HRESULT DAPI ProcWaitForCompletion( __in HANDLE hProcess, __in DWORD dwTimeout, - __out DWORD *pReturnCode + __out_opt DWORD* pdwReturnCode ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; + BOOL fTimedOut = FALSE; - // Wait for everything to finish - er = ::WaitForSingleObject(hProcess, dwTimeout); - if (WAIT_FAILED == er) - { - ProcExitWithLastError(hr, "Failed to wait for process to complete."); - } - else if (WAIT_TIMEOUT == er) + // Wait for everything to finish. + hr = AppWaitForSingleObject(hProcess, dwTimeout); + ProcExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for process to complete."); + + if (fTimedOut) { - ExitFunction1(hr = HRESULT_FROM_WIN32(er)); + hr = HRESULT_FROM_WIN32(WAIT_TIMEOUT); } - - if (!::GetExitCodeProcess(hProcess, &er)) + else if (pdwReturnCode && !::GetExitCodeProcess(hProcess, pdwReturnCode)) { ProcExitWithLastError(hr, "Failed to get process return code."); } - *pReturnCode = er; - LExit: return hr; } @@ -442,10 +438,10 @@ extern "C" HRESULT DAPI ProcWaitForIds( ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; HANDLE hProcess = NULL; - HANDLE * rghProcesses = NULL; + HANDLE* rghProcesses = NULL; DWORD cProcesses = 0; + BOOL fTimedOut = FALSE; rghProcesses = static_cast(MemAlloc(sizeof(DWORD) * cProcessIds, TRUE)); ProcExitOnNull(rgdwProcessIds, hr, E_OUTOFMEMORY, "Failed to allocate array for process ID Handles."); @@ -459,16 +455,14 @@ extern "C" HRESULT DAPI ProcWaitForIds( } } - er = ::WaitForMultipleObjects(cProcesses, rghProcesses, TRUE, dwMilliseconds); - if (WAIT_FAILED == er) - { - ProcExitWithLastError(hr, "Failed to wait for process to complete."); - } - else if (WAIT_TIMEOUT == er) + hr = AppWaitForMultipleObjects(cProcesses, rghProcesses, TRUE, dwMilliseconds, NULL); + ProcExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for processes to complete."); + + if (fTimedOut) { - ProcExitOnWin32Error(er, hr, "Timed out while waiting for process to complete."); + ProcExitWithRootFailure(hr, HRESULT_FROM_WIN32(WAIT_TIMEOUT), "Timed out while waiting for processes to complete."); } - + LExit: if (rghProcesses) { diff --git a/src/libs/dutil/WixToolset.DUtil/thrdutil.cpp b/src/libs/dutil/WixToolset.DUtil/thrdutil.cpp new file mode 100644 index 00000000..a8933a48 --- /dev/null +++ b/src/libs/dutil/WixToolset.DUtil/thrdutil.cpp @@ -0,0 +1,46 @@ +// 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. + +#include "precomp.h" + + +// Exit macros +#define ThrdExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__) +#define ThrdExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__) +#define ThrdExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__) +#define ThrdExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__) +#define ThrdExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__) +#define ThrdExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_THRDUTIL, x, e, s, __VA_ARGS__) +#define ThrdExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_THRDUTIL, x, s, __VA_ARGS__) +#define ThrdExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_THRDUTIL, p, x, e, s, __VA_ARGS__) +#define ThrdExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_THRDUTIL, p, x, s, __VA_ARGS__) +#define ThrdExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_THRDUTIL, p, x, e, s, __VA_ARGS__) +#define ThrdExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_THRDUTIL, p, x, s, __VA_ARGS__) +#define ThrdExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_THRDUTIL, e, x, s, __VA_ARGS__) +#define ThrdExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_THRDUTIL, g, x, s, __VA_ARGS__) +#define ThrdExitOnWaitObjectFailure(x, b, s, ...) ExitOnWaitObjectFailureSource(DUTIL_SOURCE_THRDUTIL, x, b, s, __VA_ARGS__) + +DAPI_(HRESULT) ThrdWaitForCompletion( + __in HANDLE hThread, + __in DWORD dwTimeout, + __out_opt DWORD *pdwReturnCode + ) +{ + HRESULT hr = S_OK; + BOOL fTimedOut = FALSE; + + // Wait for everything to finish. + hr = AppWaitForSingleObject(hThread, dwTimeout); + ThrdExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for thread to complete."); + + if (fTimedOut) + { + hr = HRESULT_FROM_WIN32(WAIT_TIMEOUT); + } + else if (pdwReturnCode && !::GetExitCodeThread(hThread, pdwReturnCode)) + { + ThrdExitWithLastError(hr, "Failed to get thread return code."); + } + +LExit: + return hr; +} diff --git a/src/libs/dutil/test/DUtilUnitTest/AppUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/AppUtilTests.cpp new file mode 100644 index 00000000..e8c23469 --- /dev/null +++ b/src/libs/dutil/test/DUtilUnitTest/AppUtilTests.cpp @@ -0,0 +1,60 @@ +// 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. + +#include "precomp.h" + +namespace DutilTests +{ + using namespace System; + using namespace Xunit; + using namespace WixBuildTools::TestSupport; + + public ref class AppUtil + { + public: + [Fact] + void WaitForMultipleObjectsTest() + { + HRESULT hr = S_OK; + HANDLE hOne = NULL; + HANDLE hTwo = NULL; + HANDLE rghHandles[2] = { }; + DWORD dwSignaledIndex = 0; + + try + { + hOne = ::CreateEventW(NULL, TRUE, FALSE, NULL); + if (!hOne) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + NativeAssert::Succeeded(FAILED(hr) ? hr : E_FAIL, "Failed to create event."); + } + + hTwo = ::CreateEventW(NULL, TRUE, TRUE, NULL); + if (!hTwo) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + NativeAssert::Succeeded(FAILED(hr) ? hr : E_FAIL, "Failed to create event."); + } + + rghHandles[0] = hOne; + rghHandles[1] = hTwo; + + hr = AppWaitForMultipleObjects(countof(rghHandles), rghHandles, FALSE, 0, &dwSignaledIndex); + NativeAssert::Succeeded(hr, "Failed to wait for multiple objects."); + Assert::Equal(1, dwSignaledIndex); + + rghHandles[0] = hTwo; + rghHandles[1] = hOne; + + hr = AppWaitForMultipleObjects(countof(rghHandles), rghHandles, FALSE, 0, &dwSignaledIndex); + NativeAssert::Succeeded(hr, "Failed to wait for multiple objects."); + Assert::Equal(0, dwSignaledIndex); + } + finally + { + ReleaseHandle(hOne); + ReleaseHandle(hTwo); + } + } + }; +} diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj index a1f13239..210f50f5 100644 --- a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj +++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj @@ -44,6 +44,7 @@ + diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters index cb0c8a73..f1d9c307 100644 --- a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters +++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters @@ -15,6 +15,9 @@ + + Source Files + Source Files diff --git a/src/libs/dutil/test/DUtilUnitTest/precomp.h b/src/libs/dutil/test/DUtilUnitTest/precomp.h index ac57cdd4..a5542774 100644 --- a/src/libs/dutil/test/DUtilUnitTest/precomp.h +++ b/src/libs/dutil/test/DUtilUnitTest/precomp.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include -- cgit v1.2.3-55-g6feb