// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // #include "common.h" #include "hosting.h" #include "mscoree.h" #include "mscoreepriv.h" #include "corhost.h" #include "threads.h" #if defined(FEATURE_CLICKONCE) #include "isolationpriv.h" #include "shlwapi.h" #endif #define countof(x) (sizeof(x) / sizeof(x[0])) //Copied from winbase.h #ifndef STARTF_TITLEISAPPID #define STARTF_TITLEISAPPID 0x00001000 #endif #ifndef STARTF_PREVENTPINNING #define STARTF_PREVENTPINNING 0x00002000 #endif //Flags encoded in the first parameter of CorLaunchApplication. #define MASK_NOTPINNABLE 0x80000000 #define MASK_HOSTTYPE 0x00000003 #define MASK_DONT_SHOW_INSTALL_DIALOG 0x00000100 #ifdef _DEBUG // This function adds a static annotation read by SCAN to indicate HOST_CALLS. Its // purpose is to be called from the BEGIN_SO_TOLERANT_CODE_CALLING_HOST macro, to // effectively mark all functions that use BEGIN_SO_TOLERANT_CODE_CALLING_HOST as being // HOST_CALLS. If you hit a SCAN violation that references AddHostCallsStaticMarker, then // you have a function marked as HOST_NOCALLS that eventually calls into a function that // uses BEGIN_SO_TOLERANT_CODE_CALLING_HOST. DEBUG_NOINLINE void AddHostCallsStaticMarker() { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_CANNOT_TAKE_LOCK; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_SO_TOLERANT; STATIC_CONTRACT_HOST_CALLS; METHOD_CANNOT_BE_FOLDED_DEBUG; } #endif //_DEBUG // // memory management functions // // global debug only tracking utilities #ifdef _DEBUG static const LONG MaxGlobalAllocCount = 8; class GlobalAllocStore { public: static void AddAlloc (LPVOID p) { LIMITED_METHOD_CONTRACT; if (!p) { return; } if (m_Disabled) { return; } //InterlockedIncrement (&numMemWriter); //if (CheckMemFree) { // goto Return; //} //m_Count is number of allocation we've ever tried, it's OK to be bigger than //size of m_Alloc[] InterlockedIncrement (&m_Count); //this is by no means an accurate record of heap allocation. //the algorithm used here can't guarantee an allocation is saved in //m_Alloc[] even there's enough free space. However this is only used //for debugging purpose and most importantly, m_Count is accurate. for (size_t n = 0; n < countof(m_Alloc); n ++) { if (m_Alloc[n] == 0) { if (InterlockedCompareExchangeT(&m_Alloc[n],p,0) == 0) { return; } } } //InterlockedDecrement (&numMemWriter); } //this is called in non-host case where we don't care the free after //alloc store is disabled static BOOL RemoveAlloc (LPVOID p) { LIMITED_METHOD_CONTRACT; if (m_Disabled) { return TRUE; } //decrement the counter even we might not find the allocation //in m_Alloc. Because it's possible for an allocation not to be saved //in the array InterlockedDecrement (&m_Count); // Binary search for (size_t n = 0; n < countof(m_Alloc); n ++) { if (m_Alloc[n] == p) { m_Alloc[n] = 0; return TRUE; } } return FALSE; } //this is called in host case where if the store is disabled, we want to //guarantee we don't try to free anything the host doesn't know about static void ValidateFree(LPVOID p) { LIMITED_METHOD_CONTRACT; if (p == 0) { return; } if (m_Disabled) { for (size_t n = 0; n < countof(m_Alloc); n ++) { //there could be miss, because an allocation might not be saved //in the array if (m_Alloc[n] == p) { _ASSERTE (!"Free a memory that host interface does not know"); return; } } } } static void Validate() { LIMITED_METHOD_CONTRACT; if (m_Count > MaxGlobalAllocCount) { _ASSERTE (!"Using too many memory allocator before Host Interface is set up"); } //while (numMemWriter != 0) { // Sleep(5); //} //qsort (GlobalMemAddr, (MemAllocCount>MaxAllocCount)?MaxAllocCount:MemAllocCount, sizeof(LPVOID), MemAddrCompare); } static void Disable () { LIMITED_METHOD_CONTRACT; if (!m_Disabled) { // Let all threads know InterlockedIncrement((LONG*)&m_Disabled); } } private: static BOOL m_Disabled; static LPVOID m_Alloc[MaxGlobalAllocCount]; //m_Count is number of allocation we tried, it's legal to be bigger than //size of m_Alloc[] static LONG m_Count; // static LONG numMemWriter = 0; }; // used from corhost.cpp void ValidateHostInterface() { WRAPPER_NO_CONTRACT; GlobalAllocStore::Validate(); GlobalAllocStore::Disable(); } void DisableGlobalAllocStore () { WRAPPER_NO_CONTRACT; GlobalAllocStore::Disable(); } LPVOID GlobalAllocStore::m_Alloc[MaxGlobalAllocCount]; LONG GlobalAllocStore::m_Count = 0; BOOL GlobalAllocStore::m_Disabled = FALSE; #endif #if defined(_DEBUG) && !defined(FEATURE_CORECLR) // The helper thread can't call regular new / delete b/c of interop-debugging deadlocks. // It must use the (InteropSafe) heap from debugger.h, you also can't allocate normally // when we have any other thread hard-suspended. // Telesto doesn't support interop-debugging, so this won't be an issue. void AssertAllocationAllowed(); #endif HANDLE g_ExecutableHeapHandle = NULL; #undef VirtualAlloc LPVOID EEVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; #ifdef FAILPOINTS_ENABLED if (RFS_HashStack ()) return NULL; #endif #if defined(_DEBUG) && !defined(FEATURE_CORECLR) AssertAllocationAllowed(); #endif #ifdef _DEBUG if (g_fEEStarted) { _ASSERTE (!EEAllocationDisallowed()); } _ASSERTE (lpAddress || (dwSize % g_SystemInfo.dwAllocationGranularity) == 0); #endif #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMemoryManager *pMM = CorHost2::GetHostMemoryManager(); if (pMM) { LPVOID pMem; EMemoryCriticalLevel eLevel = eTaskCritical; if (!g_fEEStarted) { eLevel = eProcessCritical; } else { Thread *pThread = GetThread(); if (pThread && pThread->HasLockInCurrentDomain()) { if (GetAppDomain()->IsDefaultDomain()) { eLevel = eProcessCritical; } else { eLevel = eAppDomainCritical; } } } HRESULT hr = S_OK; BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); hr = pMM->VirtualAlloc (lpAddress, dwSize, flAllocationType, flProtect, eLevel, &pMem); END_SO_TOLERANT_CODE_CALLING_HOST; if(hr != S_OK) { STRESS_LOG_OOM_STACK(dwSize); } return (hr == S_OK) ? pMem : NULL; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { LPVOID p = NULL; #ifdef _DEBUG { DEBUG_ONLY_REGION(); if (lpAddress == NULL && (flAllocationType & MEM_RESERVE) != 0 && PEDecoder::GetForceRelocs()) { #ifdef _WIN64 // Try to allocate memory all over the place when we are stressing relocations on _WIN64. // This will make sure that we generate jump stubs correctly among other things. static BYTE* ptr = (BYTE*)0x234560000; ptr += 0x123450000; // Wrap around if (ptr < (BYTE *)BOT_MEMORY || ptr > (BYTE *)TOP_MEMORY) { // Make sure to keep the alignment of the ptr so that we are not // trying the same places over and over again ptr = (BYTE*)BOT_MEMORY + (((SIZE_T)ptr) & 0xFFFFFFFF); } p = ::VirtualAlloc(ptr, dwSize, flAllocationType, flProtect); #else // Allocate memory top to bottom to stress ngen fixups with LARGEADDRESSAWARE support. p = ::VirtualAlloc(lpAddress, dwSize, flAllocationType | MEM_TOP_DOWN, flProtect); #endif // _WIN64 } } #endif // _DEBUG // Fall back to the default method if the forced relocation failed if (p == NULL) { p = ::VirtualAlloc (lpAddress, dwSize, flAllocationType, flProtect); } #ifdef _DEBUG GlobalAllocStore::AddAlloc (p); #endif if(p == NULL){ STRESS_LOG_OOM_STACK(dwSize); } return p; } } #define VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect) Dont_Use_VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect) #undef VirtualFree BOOL EEVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; BOOL retVal = FALSE; #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMemoryManager *pMM = CorHost2::GetHostMemoryManager(); if (pMM) { #ifdef _DEBUG GlobalAllocStore::ValidateFree(lpAddress); #endif BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); retVal = pMM->VirtualFree (lpAddress, dwSize, dwFreeType) == S_OK; END_SO_TOLERANT_CODE_CALLING_HOST; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { #ifdef _DEBUG GlobalAllocStore::RemoveAlloc (lpAddress); #endif retVal = (BOOL)(BYTE)::VirtualFree (lpAddress, dwSize, dwFreeType); } return retVal; } #define VirtualFree(lpAddress, dwSize, dwFreeType) Dont_Use_VirtualFree(lpAddress, dwSize, dwFreeType) #undef VirtualQuery SIZE_T EEVirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMemoryManager *pMM = CorHost2::GetHostMemoryManager(); if (pMM) { SIZE_T result; HRESULT hr = S_OK; BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); hr = pMM->VirtualQuery((void*)lpAddress, lpBuffer, dwLength, &result); END_SO_TOLERANT_CODE_CALLING_HOST; if (FAILED(hr)) return 0; return result; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { return ::VirtualQuery(lpAddress, lpBuffer, dwLength); } } #define VirtualQuery(lpAddress, lpBuffer, dwLength) Dont_Use_VirtualQuery(lpAddress, lpBuffer, dwLength) #undef VirtualProtect BOOL EEVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMemoryManager *pMM = CorHost2::GetHostMemoryManager(); if (pMM) { BOOL result = FALSE; BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); result = pMM->VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect) == S_OK; END_SO_TOLERANT_CODE_CALLING_HOST; return result; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { return ::VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); } } #define VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect) Dont_Use_VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect) #undef GetProcessHeap HANDLE EEGetProcessHeap() { // Note: this can be called a little early for real contracts, so we use static contracts instead. STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_SO_TOLERANT; #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMemoryManager *pMM = CorHost2::GetHostMemoryManager(); if (pMM) { return (HANDLE)1; // pretending we return an handle is ok because handles are ignored by the hosting api } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { return GetProcessHeap(); } } #define GetProcessHeap() Dont_Use_GetProcessHeap() #undef HeapCreate HANDLE EEHeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; #ifndef FEATURE_PAL #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMalloc *pHM = CorHost2::GetHostMalloc(); if (pHM) { return NULL; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { return ::HeapCreate(flOptions, dwInitialSize, dwMaximumSize); } #else // !FEATURE_PAL return NULL; #endif // !FEATURE_PAL } #define HeapCreate(flOptions, dwInitialSize, dwMaximumSize) Dont_Use_HeapCreate(flOptions, dwInitialSize, dwMaximumSize) #undef HeapDestroy BOOL EEHeapDestroy(HANDLE hHeap) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; #ifndef FEATURE_PAL #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMalloc *pHM = CorHost2::GetHostMalloc(); if (pHM) { return TRUE; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { return ::HeapDestroy(hHeap); } #else // !FEATURE_PAL UNREACHABLE(); #endif // !FEATURE_PAL } #define HeapDestroy(hHeap) Dont_Use_HeapDestroy(hHeap) #ifdef _DEBUG #ifdef _TARGET_X86_ #define OS_HEAP_ALIGN 8 #else #define OS_HEAP_ALIGN 16 #endif #endif #ifdef FEATURE_INCLUDE_ALL_INTERFACES LPVOID EEHeapAllocHosted(IHostMalloc * pHM, SIZE_T dwBytes) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_SO_INTOLERANT; Thread * pThread = GetThreadNULLOk(); EMemoryCriticalLevel eLevel = eTaskCritical; if (!g_fEEStarted) { eLevel = eProcessCritical; } else { if (pThread && pThread->HasLockInCurrentDomain()) { if (GetAppDomain()->IsDefaultDomain()) { eLevel = eProcessCritical; } else { eLevel = eAppDomainCritical; } } } LPVOID pMem = NULL; HRESULT hr = S_OK; { CantAllocHolder caHolder; BEGIN_SO_TOLERANT_CODE_CALLING_HOST(pThread); hr = pHM->Alloc(dwBytes, eLevel, &pMem); END_SO_TOLERANT_CODE_CALLING_HOST; } if(hr != S_OK //under OOM, we might not be able to get Execution Engine and can't access stress log && GetExecutionEngine () // If we have not created StressLog ring buffer, we should not try to use it. // StressLog is going to do a memory allocation. We may enter an endless loop. && ClrFlsGetValue(TlsIdx_StressLog) != NULL ) { STRESS_LOG_OOM_STACK(dwBytes); } return (hr == S_OK) ? pMem : NULL; } #endif // FEATURE_INCLUDE_ALL_INTERFACES #undef HeapAlloc LPVOID EEHeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_SO_INTOLERANT; #ifdef FAILPOINTS_ENABLED if (RFS_HashStack ()) return NULL; #endif #if defined(_DEBUG) && !defined(FEATURE_CORECLR) AssertAllocationAllowed(); #endif #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMalloc *pHM = CorHost2::GetHostMalloc(); // TODO: implement hosted executable heap if (pHM && hHeap != g_ExecutableHeapHandle) { return EEHeapAllocHosted(pHM, dwBytes); } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { LPVOID p = NULL; #ifdef _DEBUG // Store the heap handle to detect heap contamination p = ::HeapAlloc (hHeap, dwFlags, dwBytes + OS_HEAP_ALIGN); if(p) { *((HANDLE*)p) = hHeap; p = (BYTE*)p + OS_HEAP_ALIGN; } GlobalAllocStore::AddAlloc (p); #else p = ::HeapAlloc (hHeap, dwFlags, dwBytes); #endif if(p == NULL //under OOM, we might not be able to get Execution Engine and can't access stress log && GetExecutionEngine () // If we have not created StressLog ring buffer, we should not try to use it. // StressLog is going to do a memory allocation. We may enter an endless loop. && ClrFlsGetValue(TlsIdx_StressLog) != NULL ) { STRESS_LOG_OOM_STACK(dwBytes); } return p; } } #define HeapAlloc(hHeap, dwFlags, dwBytes) Dont_Use_HeapAlloc(hHeap, dwFlags, dwBytes) LPVOID EEHeapAllocInProcessHeap(DWORD dwFlags, SIZE_T dwBytes) { WRAPPER_NO_CONTRACT; STATIC_CONTRACT_SO_TOLERANT; static HANDLE ProcessHeap = NULL; // We need to guarentee a very small stack consumption in allocating. And we can't allow // an SO to happen while calling into the host. This will force a hard SO which is OK because // we shouldn't ever get this close inside the EE in SO-intolerant code, so this should // only fail if we call directly in from outside the EE, such as the JIT. MINIMAL_STACK_PROBE_CHECK_THREAD(GetThread()); if (ProcessHeap == NULL) ProcessHeap = EEGetProcessHeap(); return EEHeapAlloc(ProcessHeap,dwFlags,dwBytes); } #undef HeapFree BOOL EEHeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_SO_TOLERANT; // @todo - Need a backout validation here. CONTRACT_VIOLATION(SOToleranceViolation); #if defined(_DEBUG) && !defined(FEATURE_CORECLR) AssertAllocationAllowed(); #endif BOOL retVal = FALSE; #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMalloc *pHM = CorHost2::GetHostMalloc(); // TODO: implement hosted executable heap if (pHM && hHeap != g_ExecutableHeapHandle) { if (lpMem == NULL) { retVal = TRUE; } #ifdef _DEBUG GlobalAllocStore::ValidateFree(lpMem); #endif BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); retVal = pHM->Free(lpMem) == S_OK; END_SO_TOLERANT_CODE_CALLING_HOST; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { #ifdef _DEBUG GlobalAllocStore::RemoveAlloc (lpMem); // Check the heap handle to detect heap contamination lpMem = (BYTE*)lpMem - OS_HEAP_ALIGN; HANDLE storedHeapHandle = *((HANDLE*)lpMem); if(storedHeapHandle != hHeap) _ASSERTE(!"Heap contamination detected! HeapFree was called on a heap other than the one that memory was allocated from.\n" "Possible cause: you used new (executable) to allocate the memory, but didn't use DeleteExecutable() to free it."); #endif // DON'T REMOVE THIS SEEMINGLY USELESS CAST // // On AMD64 the OS HeapFree calls RtlFreeHeap which returns a 1byte // BOOLEAN, HeapFree then doesn't correctly clean the return value // so the other 3 bytes which come back can be junk and in that case // this return value can never be false. retVal = (BOOL)(BYTE)::HeapFree (hHeap, dwFlags, lpMem); } return retVal; } #define HeapFree(hHeap, dwFlags, lpMem) Dont_Use_HeapFree(hHeap, dwFlags, lpMem) BOOL EEHeapFreeInProcessHeap(DWORD dwFlags, LPVOID lpMem) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; MODE_ANY; } CONTRACTL_END; // Take a look at comment in EEHeapFree and EEHeapAllocInProcessHeap, obviously someone // needs to take a little time to think more about this code. //CONTRACT_VIOLATION(SOToleranceViolation); static HANDLE ProcessHeap = NULL; if (ProcessHeap == NULL) ProcessHeap = EEGetProcessHeap(); return EEHeapFree(ProcessHeap,dwFlags,lpMem); } #undef HeapValidate BOOL EEHeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; #ifndef FEATURE_PAL #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostMalloc *pHM = CorHost2::GetHostMalloc(); if (pHM) { return TRUE; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { return ::HeapValidate(hHeap, dwFlags, lpMem); } #else // !FEATURE_PAL return TRUE; #endif // !FEATURE_PAL } #define HeapValidate(hHeap, dwFlags, lpMem) Dont_Use_HeapValidate(hHeap, dwFlags, lpMem) HANDLE EEGetProcessExecutableHeap() { // Note: this can be called a little early for real contracts, so we use static contracts instead. STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; #ifdef FEATURE_CORECLR #ifndef FEATURE_PAL // // Create the executable heap lazily // #undef HeapCreate #undef HeapDestroy if (g_ExecutableHeapHandle == NULL) { HANDLE ExecutableHeapHandle = HeapCreate( HEAP_CREATE_ENABLE_EXECUTE, // heap allocation attributes 0, // initial heap size 0 // maximum heap size; 0 == growable ); if (ExecutableHeapHandle == NULL) return NULL; HANDLE ExistingValue = InterlockedCompareExchangeT(&g_ExecutableHeapHandle, ExecutableHeapHandle, NULL); if (ExistingValue != NULL) { HeapDestroy(ExecutableHeapHandle); } } #define HeapCreate(flOptions, dwInitialSize, dwMaximumSize) Dont_Use_HeapCreate(flOptions, dwInitialSize, dwMaximumSize) #define HeapDestroy(hHeap) Dont_Use_HeapDestroy(hHeap) #else // !FEATURE_PAL UNREACHABLE(); #endif // !FEATURE_PAL #else // FEATURE_CORECLR // // Use process executable heap created by the shim // if (g_ExecutableHeapHandle == NULL) { extern HANDLE GetProcessExecutableHeap(); g_ExecutableHeapHandle = GetProcessExecutableHeap(); } #endif // FEATURE_CORECLR // TODO: implement hosted executable heap return g_ExecutableHeapHandle; } #undef SleepEx #undef Sleep DWORD EESleepEx(DWORD dwMilliseconds, BOOL bAlertable) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SO_TOLERANT; } CONTRACTL_END; DWORD res; #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostTaskManager *provider = CorHost2::GetHostTaskManager(); if ((provider != NULL)){ DWORD option = 0; if (bAlertable) { option = WAIT_ALERTABLE; } HRESULT hr; BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); hr = provider->Sleep(dwMilliseconds, option); END_SO_TOLERANT_CODE_CALLING_HOST; if (hr == S_OK) { res = WAIT_OBJECT_0; } else if (hr == HOST_E_INTERRUPTED) { _ASSERTE(bAlertable); Thread *pThread = GetThread(); if (pThread) { pThread->UserInterruptAPC(APC_Code); } res = WAIT_IO_COMPLETION; } else { _ASSERTE (!"Unknown return from host Sleep\n"); res = WAIT_OBJECT_0; } } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { res = ::SleepEx(dwMilliseconds, bAlertable); } return res; } #define SleepEx(dwMilliseconds,bAlertable) \ Dont_Use_SleepEx(dwMilliseconds,bAlertable) #define Sleep(a) Dont_Use_Sleep(a) // non-zero return value if this function causes the OS to switch to another thread // See file:spinlock.h#SwitchToThreadSpinning for an explanation of dwSwitchCount BOOL __SwitchToThread (DWORD dwSleepMSec, DWORD dwSwitchCount) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SO_TOLERANT; } CONTRACTL_END; return __DangerousSwitchToThread(dwSleepMSec, dwSwitchCount, FALSE); } #undef SleepEx BOOL __DangerousSwitchToThread (DWORD dwSleepMSec, DWORD dwSwitchCount, BOOL goThroughOS) { // If you sleep for a long time, the thread should be in Preemptive GC mode. CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SO_TOLERANT; PRECONDITION(dwSleepMSec < 10000 || GetThread() == NULL || !GetThread()->PreemptiveGCDisabled()); } CONTRACTL_END; if (CLRTaskHosted()) { Thread *pThread = GetThread(); if (pThread && pThread->HasThreadState(Thread::TS_YieldRequested)) { pThread->ResetThreadState(Thread::TS_YieldRequested); } } if (dwSleepMSec > 0) { // when called with goThroughOS make sure to not call into the host. This function // may be called from GetRuntimeFunctionCallback() which is called by the OS to determine // the personality routine when it needs to unwind managed code off the stack. when this // happens in the context of an SO we want to avoid calling into the host if (goThroughOS) ::SleepEx(dwSleepMSec, FALSE); else ClrSleepEx(dwSleepMSec,FALSE); return TRUE; } // In deciding when to insert sleeps, we wait until we have been spinning // for a long time and then always sleep. The former is to let short perf-critical // __SwitchToThread loops avoid context switches. The latter is to ensure // that if many threads are spinning waiting for a lower-priority thread // to run that they will eventually all be asleep at the same time. // // The specific values are derived from the NDP 2.0 SP1 fix: it waits for // 8 million cycles of __SwitchToThread calls where each takes ~300-500, // which means we should wait in the neighborhood of 25000 calls. // // As of early 2011, ARM CPUs are much slower, so we need a lower threshold. // The following two values appear to yield roughly equivalent spin times // on their respective platforms. // #ifdef _TARGET_ARM_ #define SLEEP_START_THRESHOLD (5 * 1024) #else #define SLEEP_START_THRESHOLD (32 * 1024) #endif _ASSERTE(CALLER_LIMITS_SPINNING < SLEEP_START_THRESHOLD); if (dwSwitchCount >= SLEEP_START_THRESHOLD) { if (goThroughOS) ::SleepEx(1, FALSE); else ClrSleepEx(1, FALSE); } #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostTaskManager *provider = CorHost2::GetHostTaskManager(); if ((provider != NULL) && (goThroughOS == FALSE)) { DWORD option = 0; HRESULT hr; BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); hr = provider->SwitchToTask(option); END_SO_TOLERANT_CODE_CALLING_HOST; return hr == S_OK; } else #endif // FEATURE_INCLUDE_ALL_INTERFACES { return SwitchToThread(); } } #define SleepEx(dwMilliseconds,bAlertable) \ Dont_Use_SleepEx(dwMilliseconds,bAlertable) // Locking routines supplied by the EE to the other DLLs of the CLR. In a _DEBUG // build of the EE, we poison the Crst as a poor man's attempt to do some argument // validation. #define POISON_BITS 3 static inline CRITSEC_COOKIE CrstToCookie(Crst * pCrst) { LIMITED_METHOD_CONTRACT; _ASSERTE((((uintptr_t) pCrst) & POISON_BITS) == 0); #ifdef _DEBUG if (pCrst) { pCrst = (Crst *) (((uintptr_t) pCrst) | POISON_BITS); } #endif return (CRITSEC_COOKIE) pCrst; } static inline Crst *CookieToCrst(CRITSEC_COOKIE cookie) { LIMITED_METHOD_CONTRACT; _ASSERTE((((uintptr_t) cookie) & POISON_BITS) == POISON_BITS); #ifdef _DEBUG cookie = (CRITSEC_COOKIE) (((uintptr_t) cookie) & ~POISON_BITS); #endif return (Crst *) cookie; } CRITSEC_COOKIE EECreateCriticalSection(CrstType crstType, CrstFlags flags) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; CRITSEC_COOKIE ret = NULL; EX_TRY { // This may be controversial, but seems like the correct discipline. If the // EE has called out to any other DLL of the CLR in cooperative mode, we // arbitrarily force lock acquisition to occur in preemptive mode. See our // treatment of AcquireLock below. //_ASSERTE((flags & (CRST_UNSAFE_COOPGC | CRST_UNSAFE_ANYMODE)) == 0); ret = CrstToCookie(new Crst(crstType, flags)); } EX_CATCH { } EX_END_CATCH(SwallowAllExceptions); // Note: we'll return NULL if the create fails. That's a true NULL, not a poisoned NULL. return ret; } void EEDeleteCriticalSection(CRITSEC_COOKIE cookie) { CONTRACTL { NOTHROW; WRAPPER(GC_NOTRIGGER); SO_TOLERANT; } CONTRACTL_END; VALIDATE_BACKOUT_STACK_CONSUMPTION; Crst *pCrst = CookieToCrst(cookie); _ASSERTE(pCrst); delete pCrst; } DEBUG_NOINLINE void EEEnterCriticalSection(CRITSEC_COOKIE cookie) { // Entering a critical section has many different contracts // depending on the flags used to initialize the critical section. // See CrstBase::Enter() for the actual contract. It's much too // complex to repeat here. CONTRACTL { WRAPPER(THROWS); WRAPPER(GC_TRIGGERS); SO_INTOLERANT; } CONTRACTL_END; ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; Crst *pCrst = CookieToCrst(cookie); _ASSERTE(pCrst); pCrst->Enter(); } DEBUG_NOINLINE void EELeaveCriticalSection(CRITSEC_COOKIE cookie) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_INTOLERANT; } CONTRACTL_END; ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; Crst *pCrst = CookieToCrst(cookie); _ASSERTE(pCrst); pCrst->Leave(); } LPVOID EETlsGetValue(DWORD slot) { STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_MODE_ANY; STATIC_CONTRACT_CANNOT_TAKE_LOCK; STATIC_CONTRACT_SO_TOLERANT; // // @todo: we don't want TlsGetValue to throw, but CheckThreadState throws right now. Either modify // CheckThreadState to not throw, or catch any exception and just return NULL. // //CONTRACT_VIOLATION(ThrowsViolation); SCAN_IGNORE_THROW; void **pTlsData = CExecutionEngine::CheckThreadState(slot, FALSE); if (pTlsData) return pTlsData[slot]; else return NULL; } BOOL EETlsCheckValue(DWORD slot, LPVOID * pValue) { STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_MODE_ANY; STATIC_CONTRACT_SO_TOLERANT; // // @todo: we don't want TlsGetValue to throw, but CheckThreadState throws right now. Either modify // CheckThreadState to not throw, or catch any exception and just return NULL. // //CONTRACT_VIOLATION(ThrowsViolation); SCAN_IGNORE_THROW; void **pTlsData = CExecutionEngine::CheckThreadState(slot, FALSE); if (pTlsData) { *pValue = pTlsData[slot]; return TRUE; } return FALSE; } VOID EETlsSetValue(DWORD slot, LPVOID pData) { STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_THROWS; STATIC_CONTRACT_MODE_ANY; STATIC_CONTRACT_SO_TOLERANT; void **pTlsData = CExecutionEngine::CheckThreadState(slot); if (pTlsData) // Yes, CheckThreadState(slot, TRUE) can return NULL now. { pTlsData[slot] = pData; } } BOOL EEAllocationDisallowed() { WRAPPER_NO_CONTRACT; #ifdef _DEBUG // On Debug build we make sure that a thread is not going to do memory allocation // after it suspends another thread, since the another thread may be suspended while // having OS Heap lock. return !Thread::Debug_AllowCallout(); #else return FALSE; #endif } #ifdef FEATURE_CLICKONCE HRESULT GetApplicationManifest (LPCWSTR pwzAppFullName, DWORD dwManifestPaths, LPCWSTR *ppwzManifestPaths, __out_z __deref_out_opt LPWSTR *ppwzApplicationFolderPath, __out_z __deref_out_opt LPWSTR *ppszKeyForm, ICMS **ppApplicationManifest) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pwzAppFullName)); PRECONDITION(CheckPointer(ppwzManifestPaths, NULL_OK)); PRECONDITION(CheckPointer(ppApplicationManifest)); } CONTRACTL_END; ReleaseHolder pStore(NULL); ReleaseHolder pAppIdAuth(NULL); ReleaseHolder pDefinitionIdentity(NULL); ReleaseHolder pEnumDefinitionIdentity(NULL); ReleaseHolder pDeploymentDefinitionIdentity(NULL); ReleaseHolder pApplicationDefinitionIdentity(NULL); ReleaseHolder pSubscriptionIdentity(NULL); ReleaseHolder pSubscriptionAppId(NULL); ReleaseHolder TempFetched(NULL); HRESULT hr = S_OK; // Maybe this is not an installed application. Grab the manifest path if specified and parse the manifest. if (dwManifestPaths > 0) { if (dwManifestPaths < 2) goto ErrExit; hr = ParseManifest(ppwzManifestPaths[1], NULL, __uuidof(ICMS), &TempFetched); if (TempFetched == NULL) { goto ErrExit; } IfFailGo(TempFetched->QueryInterface(__uuidof(ICMS), (void**) ppApplicationManifest)); TempFetched.Release(); // Set the application directory to be the location of the application manifest. if (ppwzApplicationFolderPath) { LPCWSTR pszSlash; if (((pszSlash = wcsrchr(ppwzManifestPaths[1], W('\\'))) != NULL) || ((pszSlash = wcsrchr(ppwzManifestPaths[1], W('/'))) != NULL)) { DWORD cchDirectory = (DWORD) (pszSlash - ppwzManifestPaths[1] + 1); *ppwzApplicationFolderPath = (LPWSTR) CoTaskMemAlloc(2 * (cchDirectory + 1)); if (*ppwzApplicationFolderPath == NULL) { hr = E_OUTOFMEMORY; goto ErrExit; } memcpy(*ppwzApplicationFolderPath, ppwzManifestPaths[1], 2 * cchDirectory); (*ppwzApplicationFolderPath)[cchDirectory] = W('\0'); } } goto ErrExit; } // Get the user store. IfFailGo(GetUserStore(0, NULL, __uuidof(IStore), &pStore)); // Get the AppId authority IfFailGo(GetAppIdAuthority(&pAppIdAuth)); // Get the IDefintionIdentity of the application full name passed in as an argument. IfFailGo(pAppIdAuth->TextToDefinition(0, pwzAppFullName, &pDefinitionIdentity)); // Get the ICMS object representing the application manifest. IfFailGo(pDefinitionIdentity->EnumAppPath(&pEnumDefinitionIdentity)); IfFailGo(pEnumDefinitionIdentity->Reset()); ULONG numItems = 0; IfFailGo(pEnumDefinitionIdentity->Next(1, &pDeploymentDefinitionIdentity, &numItems)); if (numItems < 1) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); goto ErrExit; } IfFailGo(pEnumDefinitionIdentity->Next(1, &pApplicationDefinitionIdentity, &numItems)); if (numItems < 1) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); goto ErrExit; } if (ppszKeyForm){ // Create subscription identity from deployment identity. IfFailGo(pDeploymentDefinitionIdentity->Clone(0,NULL,&pSubscriptionIdentity)); IfFailGo(pSubscriptionIdentity->SetAttribute(NULL,W("version"),NULL)); // Create the subscription app id. IfFailGo(pAppIdAuth->CreateDefinition(&pSubscriptionAppId)); IDefinitionIdentity *defIdentityArray[1]; defIdentityArray[0] = pSubscriptionIdentity; IfFailGo(pSubscriptionAppId->SetAppPath(1,defIdentityArray)); IfFailGo(pAppIdAuth->GenerateDefinitionKey(0,pSubscriptionAppId,ppszKeyForm)); } hr = pStore->GetAssemblyInformation(0, pApplicationDefinitionIdentity, __uuidof(ICMS), &TempFetched); if (SUCCEEDED(hr)) { if (ppwzApplicationFolderPath) { // Get the application folder path. LPVOID cookie = NULL; IfFailGo(pStore->LockApplicationPath(0, pDefinitionIdentity, &cookie, ppwzApplicationFolderPath)); IfFailGo(pStore->ReleaseApplicationPath(cookie)); } } IfFailGo(TempFetched->QueryInterface(__uuidof(ICMS), (void**) ppApplicationManifest)); TempFetched.Release(); ErrExit: pStore.Release(); pAppIdAuth.Release(); pDefinitionIdentity.Release(); pEnumDefinitionIdentity.Release(); pDeploymentDefinitionIdentity.Release(); pApplicationDefinitionIdentity.Release(); pSubscriptionIdentity.Release(); pSubscriptionAppId.Release(); return hr; } BOOL DoesMarkOfTheWebExist (LPCWSTR pwzAppFullName) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pwzAppFullName)); } CONTRACTL_END; HANDLE alternateStreamHandle = INVALID_HANDLE_VALUE; StackSString alternateStreamPath(pwzAppFullName); alternateStreamPath.Append(W(":Zone.Identifier")); // Try to open alternate file stream alternateStreamHandle = WszCreateFile( alternateStreamPath.GetUnicode(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); if (INVALID_HANDLE_VALUE != alternateStreamHandle) { CloseHandle(alternateStreamHandle); // We only check if MOTW (alternate stream) is present, // no matter what the zone is. return TRUE; } return FALSE; } HRESULT GetApplicationEntryPointInfo (LPCWSTR pwzAppFullName, DWORD dwManifestPaths, LPCWSTR *ppwzManifestPaths, __out_z __deref_out_opt LPWSTR *ppwzApplicationFolderPath, LPCWSTR *ppwzCodeBase, LPCWSTR *ppwzParameters, __out_z __deref_out_opt LPWSTR *ppwzProcessorArch, __out_z __deref_out_opt LPWSTR *ppwzAppIdKeyForm) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pwzAppFullName)); PRECONDITION(CheckPointer(ppwzManifestPaths, NULL_OK)); PRECONDITION(CheckPointer(ppwzCodeBase, NULL_OK)); PRECONDITION(CheckPointer(ppwzParameters, NULL_OK)); PRECONDITION(CheckPointer(ppwzProcessorArch, NULL_OK)); } CONTRACTL_END; ReleaseHolder pApplicationManifest(NULL); ReleaseHolder pEntrySection(NULL); ReleaseHolder pEntryEnum(NULL); ReleaseHolder pEntry(NULL); ReleaseHolder pReferenceId(NULL); ReleaseHolder pNamedRefSection(NULL); ReleaseHolder pRefSection(NULL); ReleaseHolder pRefEntry(NULL); ReleaseHolder pDependentAssemblyEntry(NULL); CoTaskMemHolder pwszDependencyName = NULL; ReleaseHolder TempFetched(NULL); ReleaseHolder TempFetchedSection(NULL); HRESULT hr = S_OK; // Get the ICMS object representing the application manifest. IfFailGo(GetApplicationManifest(pwzAppFullName, dwManifestPaths, ppwzManifestPaths, ppwzApplicationFolderPath, ppwzAppIdKeyForm,&pApplicationManifest)); // Get the app entry point section. IfFailGo(pApplicationManifest->get_EntryPointSection(&pEntrySection)); if (pEntrySection == NULL) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); goto ErrExit; } // Get the entry point enum. IfFailGo(pEntrySection->get__NewEnum(&TempFetched)); IfFailGo(TempFetched->QueryInterface(__uuidof(IEnumUnknown), &pEntryEnum)); TempFetched.Release(); // Get the first entry point. ULONG numItems = 0; IfFailGo(pEntryEnum->Next(1, &TempFetched, &numItems)); if (numItems < 1) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); goto ErrExit; } IfFailGo(TempFetched->QueryInterface(__uuidof(IEntryPointEntry), &pEntry)); TempFetched.Release(); // We support both name and identity based entry points. IfFailGo(pEntry->get_Identity(&pReferenceId)); if (pReferenceId == NULL) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); goto ErrExit; } // Get the assembly reference section. IfFailGo(pApplicationManifest->get_AssemblyReferenceSection(&TempFetchedSection)); IfFailGo(TempFetchedSection->QueryInterface(__uuidof(ISectionWithReferenceIdentityKey), &pRefSection)); TempFetchedSection.Release(); #ifdef CLICKONCE_LONGHORN_RELATED // // If a reference assembly matching entry point does not exist, use the codebase // of command line file. // if (FAILED(pRefSection->Lookup(pReferenceId, &TempFetched))) { if (ppwzCodeBase) { IfFailGo(pEntry->get_CommandLine_File(ppwzCodeBase)); } } else #endif { // Lookup the assembly reference entry. IfFailGo(pRefSection->Lookup(pReferenceId, &TempFetched)); IfFailGo(TempFetched->QueryInterface(__uuidof(IAssemblyReferenceEntry), &pRefEntry)); TempFetched.Release(); // Get the assembly codebase. Codebase may either come from or . // In a valid reference there should always be a section. IfFailGo(pRefEntry->get_DependentAssembly(&pDependentAssemblyEntry)); if (ppwzCodeBase) { IfFailGo(pDependentAssemblyEntry->get_Codebase(ppwzCodeBase)); } } // Get the parameters if (ppwzParameters) IfFailGo(pEntry->get_CommandLine_Parameters(ppwzParameters)); // Get the processor architecture requested in the app manifest if (ppwzProcessorArch) IfFailGo(pReferenceId->GetAttribute(NULL, W("processorArchitecture"), ppwzProcessorArch)); ErrExit: pApplicationManifest.Release(); pEntrySection.Release(); pEntryEnum.Release(); pEntry.Release(); pReferenceId.Release(); pNamedRefSection.Release(); pRefSection.Release(); pRefEntry.Release(); pDependentAssemblyEntry.Release(); pwszDependencyName.Release(); return hr; } // // Export used in the ClickOnce installer for launching manifest-based applications. // typedef struct _tagNameMap { LPWSTR pwszProcessorArch; DWORD dwRuntimeInfoFlag; } NAME_MAP; DWORD g_DfSvcSpinLock = 0; void EnterDfSvcSpinLock () { WRAPPER_NO_CONTRACT; while (1) { if (InterlockedExchange ((LPLONG)&g_DfSvcSpinLock, 1) == 1) ClrSleepEx (5, FALSE); else return; } } void LeaveDfSvcSpinLock () { InterlockedExchange ((LPLONG)&g_DfSvcSpinLock, 0); } // // ThreadProc used by SHCreateProcess call - to activate ClickOnce app with ShellExecuteEx // ShellExecuteEx can only be used from STA threads - we are creating our own STA thread // DWORD CorLaunchApplication_ThreadProc(void*) { return 0; } // // This callback is executed as the sync-callback on SHCreateThread. // SHCreateThread does not return till this callback returns. // DWORD CorLaunchApplication_Callback(void* pv) { SHELLEXECUTEINFO *pSei = static_cast(pv); IUnknown* pDummyUnknown; CreateStreamOnHGlobal(NULL, TRUE, (LPSTREAM*) &pDummyUnknown); if (RunningOnWin8()) { // When SEE_MASK_FLAG_HINST_IS_SITE is specified SHELLEXECUTEINFO.hInstApp is used as an // _In_ parameter and specifies a IUnknown* to be used as a site pointer. The site pointer // is used to provide services to shell execute, the handler binding process and the verb handlers // once they are invoked. // // SEE_MASK_HINST_IS_SITE is available on Win8+ // Defining it locally in Win8-conditioned code // const ULONG SEE_MASK_HINST_IS_SITE = 0x08000000; pSei->fMask = SEE_MASK_HINST_IS_SITE; pSei->hInstApp = reinterpret_cast(pDummyUnknown); } WszShellExecuteEx(pSei); // We ignore all errors from ShellExecute. // // This may change with Win8:783168 if (pDummyUnknown) { pDummyUnknown->Release(); } return 0; } //----------------------------------------------------------------------------- // WszSHCreateThread // // @func calls SHCreateThread with the provided parameters // // @rdesc Result //----------------------------------------------------------------------------------- HRESULT WszSHCreateThread( LPTHREAD_START_ROUTINE pfnThreadProc, void *pData, SHCT_FLAGS dwFlags, LPTHREAD_START_ROUTINE pfnCallback ) { CONTRACTL { NOTHROW; MODE_PREEMPTIVE; INJECT_FAULT(return E_OUTOFMEMORY;); } CONTRACTL_END; HRESULT hr = S_OK; HMODULE _hmodShlwapi = 0; typedef BOOL (*PFNSHCREATETHREAD) ( __in LPTHREAD_START_ROUTINE pfnThreadProc, __in_opt void *pData, __in SHCT_FLAGS dwFlags, __in_opt LPTHREAD_START_ROUTINE pfnCallback ); static PFNSHCREATETHREAD pfnW = NULL; if (NULL == pfnW) { _hmodShlwapi = CLRLoadLibrary(W("shlwapi.dll")); if (_hmodShlwapi) { pfnW = (PFNSHCREATETHREAD)GetProcAddress(_hmodShlwapi, "SHCreateThread"); } } if (pfnW) { BOOL bRet = pfnW(pfnThreadProc, pData, dwFlags, pfnCallback); if (!bRet) { hr = HRESULT_FROM_WIN32(GetLastError()); } } else { hr = HRESULT_FROM_WIN32(GetLastError()); } // NOTE: We leak the module handles and let the OS gather them at process shutdown. return hr; } STDAPI CorLaunchApplication (HOST_TYPE dwClickOnceHost, LPCWSTR pwzAppFullName, DWORD dwManifestPaths, LPCWSTR *ppwzManifestPaths, DWORD dwActivationData, LPCWSTR *ppwzActivationData, LPPROCESS_INFORMATION lpProcessInformation) { // HostType is encoded in the lowest 2 bits. unsigned hostType = dwClickOnceHost & MASK_HOSTTYPE; // NoPinnableBit is the highest bit. unsigned notPinnableBit = dwClickOnceHost & MASK_NOTPINNABLE; // DontShowInstallDialog bit unsigned dontShowInstallDialog = dwClickOnceHost & MASK_DONT_SHOW_INSTALL_DIALOG; bool bUseShellExecute = false; CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_ANY; ENTRY_POINT; PRECONDITION(CheckPointer(pwzAppFullName, NULL_OK)); PRECONDITION(CheckPointer(ppwzManifestPaths, NULL_OK)); PRECONDITION(CheckPointer(ppwzActivationData, NULL_OK)); PRECONDITION(hostType == HOST_TYPE_DEFAULT || hostType == HOST_TYPE_APPLAUNCH || hostType == HOST_TYPE_CORFLAG); PRECONDITION(CheckPointer(lpProcessInformation)); } CONTRACTL_END; if (pwzAppFullName == NULL) return E_POINTER; HRESULT hr = S_OK; BEGIN_ENTRYPOINT_NOTHROW; LPVOID lpEnvironment = NULL; EX_TRY { StackSString commandLine(StackSString::Ascii, "\""); // put quotes around path to the command line. StackSString appEntryPath(W("")); // the path to the entry point(the exe to run) of the application, initialized to empty string NewArrayHolder wszDirectory(NULL); NewArrayHolder wszVersion(NULL); CoTaskMemHolder pwszApplicationFolderPath(NULL); CoTaskMemHolder pwszAppIdKeyForm(NULL); CoTaskMemHolder pwszCodebase(NULL); CoTaskMemHolder pwszParameters(NULL); CoTaskMemHolder pwszProcessorArch(NULL); hr = GetApplicationEntryPointInfo(pwzAppFullName, dwManifestPaths, ppwzManifestPaths, (LPWSTR*) (void*) &pwszApplicationFolderPath, (LPCWSTR*) (void*) &pwszCodebase, (LPCWSTR*) (void*) &pwszParameters, (LPWSTR*) (void*) &pwszProcessorArch,(LPWSTR*) (void*) &pwszAppIdKeyForm); if (SUCCEEDED(hr)) { // construct the application Entry Path if (pwszApplicationFolderPath != NULL) { appEntryPath.Append(pwszApplicationFolderPath); SString::CIterator i = appEntryPath.End()-1; if (i[0] != '\\') appEntryPath.Append(W("\\")); } appEntryPath.Append(pwszCodebase); if (hostType == HOST_TYPE_CORFLAG) { // construct the command line commandLine.Append(appEntryPath); commandLine.Append(W("\"")); if (RunningOnWin8() && DoesMarkOfTheWebExist(appEntryPath.GetUnicode())) { // We will use ShellExecute for any zone set in MOTW stream. // ShellExecute would call Application Reputation API if the zone is the one // that requires AppRep validation. At the moment, they would do this for Internet Zone only, // but there are talks about changing the behavior to include some of the other zones. // By not checking the zone here we leave to AppRep/ShellExecute to decide, // which is exactly what we want. bUseShellExecute = true; } else { if (pwszParameters != NULL) { commandLine.Append(W(" ")); commandLine.Append(pwszParameters); } } // now construct the environment variables EnterDfSvcSpinLock(); WszSetEnvironmentVariable(g_pwzClickOnceEnv_FullName, pwzAppFullName); if (dwManifestPaths > 0 && ppwzManifestPaths) { for (DWORD i=0; i 0 && ppwzActivationData) { for (DWORD i=0; i 0) { dwRuntimeInfoFlags |= RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG; } else { // show even if SEM_CRITICAL is set dwRuntimeInfoFlags |= METAHOST_POLICY_IGNORE_ERROR_MODE; } if (pwszProcessorArch) { for (DWORD index = 0; index < sizeof(g_NameMapArray) / sizeof(NAME_MAP); index++) { if (SString::_wcsicmp(g_NameMapArray[index].pwszProcessorArch, pwszProcessorArch) == 0) { dwRuntimeInfoFlags |= g_NameMapArray[index].dwRuntimeInfoFlag; break; } } } wszDirectory = new WCHAR[MAX_LONGPATH + 1]; wszVersion = new WCHAR[MAX_PATH_FNAME + 1]; wszVersion[0] = 0; // we don't prefer any version DWORD cchBuffer = MAX_LONGPATH; // Use GetRequestedRuntimeInfo because MetaHost APIs do not yet support architecture arguments. // Calls to GetRequestedRuntimeInfo() will goes to a local copy inside clr.dll, // have to call mscoree::GetRequestedRuntimeInfo. typedef HRESULT (*PFNGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion, LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags, LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength, LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength); PFNGetRequestedRuntimeInfo pfnGetRequestedRuntimeInfo = NULL; HMODULE hMscoree = GetModuleHandleW( W("mscoree.dll") ); // mscoree.dll should have already been loaded if( hMscoree != NULL ) pfnGetRequestedRuntimeInfo = (PFNGetRequestedRuntimeInfo)GetProcAddress( hMscoree, "GetRequestedRuntimeInfo" ); if( pfnGetRequestedRuntimeInfo == NULL ) pfnGetRequestedRuntimeInfo = GetRequestedRuntimeInfoInternal; // in case mscoree has not been loaded, use the built in function hr = pfnGetRequestedRuntimeInfo(appEntryPath.GetUnicode(), // Use the image path to guide all version binding NULL, // Do not prime with any preferred version NULL, // No explicit config file - pick up on one next to image if there. 0, // startupFlags dwRuntimeInfoFlags, // Will bind to post-v2 runtimes if EXE PE runtime version is post-v2 // or EXE has config file binding to post-v2 runtime. wszDirectory, MAX_LONGPATH, NULL, // Retrieve bound directory wszVersion, MAX_PATH_FNAME, NULL); // Retrieve bound version if (SUCCEEDED(hr)) { commandLine.Append(wszDirectory); commandLine.Append(wszVersion); commandLine.Append(W("\\applaunch.exe")); commandLine.Append(W("\" /activate \"")); commandLine.Append(pwzAppFullName); commandLine.Append(W("\" ")); if (dwManifestPaths > 0 && ppwzManifestPaths) { commandLine.Append(W("/manifests ")); for (DWORD i=0; i 0 && ppwzActivationData) { commandLine.Append(W("/parameters ")); for (DWORD i=0; i wszCommandLine(new WCHAR[len + 1]); memcpy(wszCommandLine, wszCommandLineNonWritable, len * sizeof(WCHAR)); wszCommandLine[len] = W('\0'); STARTUPINFO sui; memset(&sui, 0, sizeof(STARTUPINFO)); sui.cb = sizeof(STARTUPINFO); sui.lpTitle = pwszAppIdKeyForm; sui.dwFlags = STARTF_TITLEISAPPID; if (notPinnableBit>0) sui.dwFlags |= STARTF_PREVENTPINNING; // ClickOnce uses ShellExecute to utilize Win8+ Application Reputation service. // Application Reputation validates applications coming from the Internet. // ClickOnce will use ShellExecute only if there is a Mark-of-the-Web file-stream for the executable. // In all other cases we continue to use CreateProcess. CreateProcess does not use AppRep service. if (bUseShellExecute) { SHELLEXECUTEINFO sei; memset(&sei, 0, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.hwnd = NULL; sei.lpVerb = NULL; sei.lpFile = wszCommandLine; sei.lpParameters = pwszParameters; sei.lpDirectory = pwszApplicationFolderPath; sei.nShow = SW_SHOWDEFAULT; sei.hInstApp = NULL; // Application Reputation is a COM Shell Extension that requires a calling thread to be an STA // CorLaunchApplication_Callback calls ShellExecuteEx. hr = WszSHCreateThread((LPTHREAD_START_ROUTINE) CorLaunchApplication_ThreadProc, &sei, CTF_COINIT_STA, (LPTHREAD_START_ROUTINE) CorLaunchApplication_Callback); } else { // Launch the child process BOOL result = WszCreateProcess(NULL, wszCommandLine, NULL, NULL, FALSE, (lpEnvironment) ? NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT | CREATE_DEFAULT_ERROR_MODE : NORMAL_PRIORITY_CLASS | CREATE_DEFAULT_ERROR_MODE, lpEnvironment, pwszApplicationFolderPath, &sui, lpProcessInformation); if (!result) hr = HRESULT_FROM_GetLastError(); } } } EX_CATCH_HRESULT(hr); // cleanup if (hostType == HOST_TYPE_CORFLAG) { // free the environment block #undef FreeEnvironmentStringsA #undef FreeEnvironmentStringsW if (NULL != lpEnvironment) { FreeEnvironmentStringsW((LPWSTR) lpEnvironment); } #define FreeEnvironmentStringsW(lpEnvironment) Use_WszFreeEnvironmentStrings(lpEnvironment) #define FreeEnvironmentStringsA(lpEnvironment) Use_WszFreeEnvironmentStrings(lpEnvironment) // reset the environment variables WszSetEnvironmentVariable(g_pwzClickOnceEnv_FullName, NULL); EX_TRY { if (dwManifestPaths > 0 && ppwzManifestPaths) { for (DWORD i=0; i 0 && ppwzActivationData) { for (DWORD i=0; i