490 lines
15 KiB
C++
490 lines
15 KiB
C++
#include "il2cpp-config.h"
|
|||
|
|
|
||
|
|
#if !IL2CPP_THREADS_STD && IL2CPP_THREADS_WIN32 && !RUNTIME_TINY
|
||
|
|
|
||
|
|
#include "ThreadImpl.h"
|
||
|
|
#include "os/ThreadLocalValue.h"
|
||
|
|
#include "os/Time.h"
|
||
|
|
#include "utils/StringUtils.h"
|
||
|
|
#include "os/Debug.h"
|
||
|
|
#include "WindowsHelpers.h"
|
||
|
|
|
||
|
|
namespace il2cpp
|
||
|
|
{
|
||
|
|
namespace os
|
||
|
|
{
|
||
|
|
static Event s_ThreadSleepObject;
|
||
|
|
struct ThreadImplStartData
|
||
|
|
{
|
||
|
|
Thread::StartFunc m_StartFunc;
|
||
|
|
void* m_StartArg;
|
||
|
|
volatile DWORD* m_ThreadId;
|
||
|
|
};
|
||
|
|
|
||
|
|
static DWORD WINAPI ThreadStartWrapper(LPVOID arg)
|
||
|
|
{
|
||
|
|
ThreadImplStartData startData = *(ThreadImplStartData*)arg;
|
||
|
|
free(arg);
|
||
|
|
*startData.m_ThreadId = GetCurrentThreadId();
|
||
|
|
startData.m_StartFunc(startData.m_StartArg);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
ThreadImpl::ThreadImpl()
|
||
|
|
: m_ThreadHandle(0), m_ThreadId(0), m_StackSize(IL2CPP_DEFAULT_STACK_SIZE), m_ApartmentState(kApartmentStateUnknown), m_Priority(kThreadPriorityNormal)
|
||
|
|
, m_ConditionSemaphore(1)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
ThreadImpl::~ThreadImpl()
|
||
|
|
{
|
||
|
|
if (m_ThreadHandle != NULL)
|
||
|
|
CloseHandle(m_ThreadHandle);
|
||
|
|
}
|
||
|
|
|
||
|
|
size_t ThreadImpl::Id()
|
||
|
|
{
|
||
|
|
return m_ThreadId;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::SetNameForDebugger(const char* name)
|
||
|
|
{
|
||
|
|
// http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
|
||
|
|
|
||
|
|
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||
|
|
|
||
|
|
#pragma pack(push,8)
|
||
|
|
typedef struct tagTHREADNAME_INFO
|
||
|
|
{
|
||
|
|
DWORD dwType; // Must be 0x1000.
|
||
|
|
LPCSTR szName; // Pointer to name (in user addr space).
|
||
|
|
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||
|
|
DWORD dwFlags; // Reserved for future use, must be zero.
|
||
|
|
} THREADNAME_INFO;
|
||
|
|
#pragma pack(pop)
|
||
|
|
|
||
|
|
THREADNAME_INFO info;
|
||
|
|
info.dwType = 0x1000;
|
||
|
|
info.szName = name;
|
||
|
|
info.dwThreadID = static_cast<DWORD>(Id());
|
||
|
|
info.dwFlags = 0;
|
||
|
|
|
||
|
|
__try
|
||
|
|
{
|
||
|
|
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||
|
|
}
|
||
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
typedef HRESULT (__stdcall *SETTHREADPROC) (HANDLE, PCWSTR);
|
||
|
|
void ThreadImpl::SetName(const char* name)
|
||
|
|
{
|
||
|
|
#if !IL2CPP_TARGET_WINRT
|
||
|
|
SETTHREADPROC ProcSetThreadDescription;
|
||
|
|
ProcSetThreadDescription = (SETTHREADPROC)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "SetThreadDescription");
|
||
|
|
if (ProcSetThreadDescription != NULL)
|
||
|
|
{
|
||
|
|
const UTF16String varName = utils::StringUtils::Utf8ToUtf16(name);
|
||
|
|
(ProcSetThreadDescription)(m_ThreadHandle, varName.c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
if (Debug::IsDebuggerPresent())
|
||
|
|
SetNameForDebugger(name);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::SetPriority(ThreadPriority priority)
|
||
|
|
{
|
||
|
|
if (m_ThreadHandle == NULL)
|
||
|
|
m_Priority = priority;
|
||
|
|
else
|
||
|
|
{
|
||
|
|
int ret = ::SetThreadPriority(m_ThreadHandle, priority - 2);
|
||
|
|
IL2CPP_ASSERT(ret);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ThreadPriority ThreadImpl::GetPriority()
|
||
|
|
{
|
||
|
|
if (m_ThreadHandle == NULL)
|
||
|
|
return m_Priority;
|
||
|
|
int ret = ::GetThreadPriority(m_ThreadHandle) + 2;
|
||
|
|
IL2CPP_ASSERT(ret != THREAD_PRIORITY_ERROR_RETURN);
|
||
|
|
return (ThreadPriority)ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ErrorCode ThreadImpl::Run(Thread::StartFunc func, void* arg, int64_t affinityMask)
|
||
|
|
{
|
||
|
|
// It might happen that func will start executing and will try to access m_ThreadId before CreateThread gets a chance to assign it.
|
||
|
|
// Therefore m_ThreadId is assigned both by this thread and from the newly created thread (race condition could go the other way too).
|
||
|
|
|
||
|
|
ThreadImplStartData* startData = (ThreadImplStartData*)malloc(sizeof(ThreadImplStartData));
|
||
|
|
startData->m_StartFunc = func;
|
||
|
|
startData->m_StartArg = arg;
|
||
|
|
startData->m_ThreadId = &m_ThreadId;
|
||
|
|
|
||
|
|
// Create thread.
|
||
|
|
DWORD threadId;
|
||
|
|
HANDLE threadHandle = ::CreateThread(NULL, m_StackSize, &ThreadStartWrapper, startData, STACK_SIZE_PARAM_IS_A_RESERVATION, &threadId);
|
||
|
|
|
||
|
|
if (!threadHandle)
|
||
|
|
return kErrorCodeGenFailure;
|
||
|
|
|
||
|
|
#if IL2CPP_TARGET_WINDOWS_GAMES || IL2CPP_TARGET_XBOXONE
|
||
|
|
if (affinityMask != Thread::kThreadAffinityAll)
|
||
|
|
SetThreadAffinityMask(threadHandle, static_cast<DWORD_PTR>(affinityMask));
|
||
|
|
#endif
|
||
|
|
|
||
|
|
m_ThreadHandle = threadHandle;
|
||
|
|
m_ThreadId = threadId;
|
||
|
|
|
||
|
|
SetPriority(m_Priority);
|
||
|
|
|
||
|
|
return kErrorCodeSuccess;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::Sleep(uint32_t ms, bool interruptible)
|
||
|
|
{
|
||
|
|
s_ThreadSleepObject.Wait(ms, interruptible);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::CheckForUserAPCAndHandle()
|
||
|
|
{
|
||
|
|
m_PendingAPCsMutex.Acquire();
|
||
|
|
|
||
|
|
while (!m_PendingAPCs.empty())
|
||
|
|
{
|
||
|
|
APCRequest apcRequest = m_PendingAPCs.front();
|
||
|
|
|
||
|
|
// Remove from list. Do before calling the function to make sure the list
|
||
|
|
// is up to date in case the function throws.
|
||
|
|
m_PendingAPCs.erase(m_PendingAPCs.begin());
|
||
|
|
|
||
|
|
// Release mutex while we call the function so that we don't deadlock
|
||
|
|
// if the function starts waiting on a thread that tries queuing an APC
|
||
|
|
// on us.
|
||
|
|
m_PendingAPCsMutex.Release();
|
||
|
|
|
||
|
|
// Call function.
|
||
|
|
apcRequest.callback(apcRequest.context);
|
||
|
|
|
||
|
|
// Re-acquire mutex.
|
||
|
|
m_PendingAPCsMutex.Acquire();
|
||
|
|
}
|
||
|
|
|
||
|
|
m_PendingAPCsMutex.Release();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::SetWaitObject(WaitObject* waitObject)
|
||
|
|
{
|
||
|
|
// This is an unprotected write as write acccess is restricted to the
|
||
|
|
// current thread.
|
||
|
|
m_CurrentWaitObject = waitObject;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::QueueUserAPC(Thread::APCFunc func, void* context)
|
||
|
|
{
|
||
|
|
IL2CPP_ASSERT(func != NULL);
|
||
|
|
|
||
|
|
// Put on queue.
|
||
|
|
{
|
||
|
|
m_PendingAPCsMutex.Acquire();
|
||
|
|
m_PendingAPCs.push_back(APCRequest(func, context));
|
||
|
|
m_PendingAPCsMutex.Release();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Interrupt an ongoing wait, only interrupt if we have an object waiting
|
||
|
|
if (m_CurrentWaitObject.load())
|
||
|
|
{
|
||
|
|
m_ConditionSemaphore.Release(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int ThreadImpl::GetMaxStackSize()
|
||
|
|
{
|
||
|
|
return INT_MAX;
|
||
|
|
}
|
||
|
|
|
||
|
|
ThreadImpl* ThreadImpl::GetCurrentThread()
|
||
|
|
{
|
||
|
|
return Thread::GetCurrentThread()->m_Thread;
|
||
|
|
}
|
||
|
|
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
// It would be nice to always use CoGetApartmentType but it's only available on Windows 7 and later.
|
||
|
|
// That's why we check for function at runtime and do a fallback on Windows XP.
|
||
|
|
// CoGetApartmentType is always available in Windows Store Apps.
|
||
|
|
|
||
|
|
typedef HRESULT (STDAPICALLTYPE * CoGetApartmentTypeFunc)(APTTYPE* type, APTTYPEQUALIFIER* qualifier);
|
||
|
|
|
||
|
|
ApartmentState GetApartmentWindows7(CoGetApartmentTypeFunc coGetApartmentType, bool* implicit)
|
||
|
|
{
|
||
|
|
*implicit = false;
|
||
|
|
|
||
|
|
APTTYPE type;
|
||
|
|
APTTYPEQUALIFIER qualifier;
|
||
|
|
const HRESULT hr = coGetApartmentType(&type, &qualifier);
|
||
|
|
if (FAILED(hr))
|
||
|
|
{
|
||
|
|
IL2CPP_ASSERT(CO_E_NOTINITIALIZED == hr);
|
||
|
|
return kApartmentStateUnknown;
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (type)
|
||
|
|
{
|
||
|
|
case APTTYPE_STA:
|
||
|
|
case APTTYPE_MAINSTA:
|
||
|
|
return kApartmentStateInSTA;
|
||
|
|
|
||
|
|
case APTTYPE_MTA:
|
||
|
|
*implicit = (APTTYPEQUALIFIER_IMPLICIT_MTA == qualifier);
|
||
|
|
return kApartmentStateInMTA;
|
||
|
|
|
||
|
|
case APTTYPE_NA:
|
||
|
|
switch (qualifier)
|
||
|
|
{
|
||
|
|
case APTTYPEQUALIFIER_NA_ON_STA:
|
||
|
|
case APTTYPEQUALIFIER_NA_ON_MAINSTA:
|
||
|
|
return kApartmentStateInSTA;
|
||
|
|
|
||
|
|
case APTTYPEQUALIFIER_NA_ON_MTA:
|
||
|
|
return kApartmentStateInMTA;
|
||
|
|
|
||
|
|
case APTTYPEQUALIFIER_NA_ON_IMPLICIT_MTA:
|
||
|
|
*implicit = true;
|
||
|
|
return kApartmentStateInMTA;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
IL2CPP_ASSERT(0 && "CoGetApartmentType returned unexpected value.");
|
||
|
|
return kApartmentStateUnknown;
|
||
|
|
}
|
||
|
|
|
||
|
|
#if IL2CPP_TARGET_WINDOWS_DESKTOP
|
||
|
|
|
||
|
|
ApartmentState GetApartmentWindowsXp(bool* implicit)
|
||
|
|
{
|
||
|
|
*implicit = false;
|
||
|
|
|
||
|
|
IUnknown* context = nullptr;
|
||
|
|
HRESULT hr = CoGetContextToken(reinterpret_cast<ULONG_PTR*>(&context));
|
||
|
|
if (SUCCEEDED(hr))
|
||
|
|
{
|
||
|
|
IComThreadingInfo* info;
|
||
|
|
hr = context->QueryInterface(&info);
|
||
|
|
if (SUCCEEDED(hr))
|
||
|
|
{
|
||
|
|
THDTYPE type;
|
||
|
|
hr = info->GetCurrentThreadType(&type);
|
||
|
|
if (SUCCEEDED(hr))
|
||
|
|
{
|
||
|
|
// THDTYPE_PROCESSMESSAGES means that we are in STA thread.
|
||
|
|
// Otherwise it's an MTA thread. We are not sure at this moment if CoInitializeEx has been called explicitly on this thread
|
||
|
|
// or if it has been implicitly made MTA by a CoInitialize call on another thread.
|
||
|
|
if (THDTYPE_PROCESSMESSAGES == type)
|
||
|
|
return kApartmentStateInSTA;
|
||
|
|
|
||
|
|
// Assume implicit. Even if it's explicit, we'll handle the case correctly by checking CoInitializeEx return value.
|
||
|
|
*implicit = true;
|
||
|
|
return kApartmentStateInMTA;
|
||
|
|
}
|
||
|
|
|
||
|
|
info->Release();
|
||
|
|
}
|
||
|
|
|
||
|
|
// No need to release context.
|
||
|
|
}
|
||
|
|
|
||
|
|
return kApartmentStateUnknown;
|
||
|
|
}
|
||
|
|
|
||
|
|
class CoGetApartmentTypeHelper
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
HMODULE _library;
|
||
|
|
CoGetApartmentTypeFunc _func;
|
||
|
|
|
||
|
|
public:
|
||
|
|
inline CoGetApartmentTypeHelper()
|
||
|
|
{
|
||
|
|
_library = LoadLibraryW(L"ole32.dll");
|
||
|
|
Assert(_library);
|
||
|
|
_func = reinterpret_cast<CoGetApartmentTypeFunc>(GetProcAddress(_library, "CoGetApartmentType"));
|
||
|
|
}
|
||
|
|
|
||
|
|
inline ~CoGetApartmentTypeHelper()
|
||
|
|
{
|
||
|
|
FreeLibrary(_library);
|
||
|
|
}
|
||
|
|
|
||
|
|
inline CoGetApartmentTypeFunc GetFunc() const { return _func; }
|
||
|
|
};
|
||
|
|
|
||
|
|
inline ApartmentState GetApartmentImpl(bool* implicit)
|
||
|
|
{
|
||
|
|
static CoGetApartmentTypeHelper coGetApartmentTypeHelper;
|
||
|
|
const CoGetApartmentTypeFunc func = coGetApartmentTypeHelper.GetFunc();
|
||
|
|
return func ? GetApartmentWindows7(func, implicit) : GetApartmentWindowsXp(implicit);
|
||
|
|
}
|
||
|
|
|
||
|
|
#else
|
||
|
|
|
||
|
|
inline ApartmentState GetApartmentImpl(bool* implicit)
|
||
|
|
{
|
||
|
|
return GetApartmentWindows7(CoGetApartmentType, implicit);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
ApartmentState ThreadImpl::GetApartment()
|
||
|
|
{
|
||
|
|
Assert(GetCurrentThreadId() == m_ThreadId);
|
||
|
|
|
||
|
|
ApartmentState state = static_cast<ApartmentState>(m_ApartmentState & ~kApartmentStateCoInitialized);
|
||
|
|
|
||
|
|
if (kApartmentStateUnknown == state)
|
||
|
|
{
|
||
|
|
bool implicit;
|
||
|
|
state = GetApartmentImpl(&implicit);
|
||
|
|
if (!implicit)
|
||
|
|
m_ApartmentState = state;
|
||
|
|
}
|
||
|
|
|
||
|
|
return state;
|
||
|
|
}
|
||
|
|
|
||
|
|
ApartmentState ThreadImpl::GetExplicitApartment()
|
||
|
|
{
|
||
|
|
return static_cast<ApartmentState>(m_ApartmentState & ~kApartmentStateCoInitialized);
|
||
|
|
}
|
||
|
|
|
||
|
|
ApartmentState ThreadImpl::SetApartment(ApartmentState state)
|
||
|
|
{
|
||
|
|
Assert(GetCurrentThreadId() == m_ThreadId);
|
||
|
|
|
||
|
|
// Unknown state uninitializes COM.
|
||
|
|
if (kApartmentStateUnknown == state)
|
||
|
|
{
|
||
|
|
if (m_ApartmentState & kApartmentStateCoInitialized)
|
||
|
|
{
|
||
|
|
CoUninitialize();
|
||
|
|
m_ApartmentState = kApartmentStateUnknown;
|
||
|
|
}
|
||
|
|
|
||
|
|
return GetApartment();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize apartment state. Ignore result of this function because it will return MTA value for both implicit and explicit apartment.
|
||
|
|
// On the other hand m_ApartmentState will only be set to MTA if it was initialized explicitly with CoInitializeEx.
|
||
|
|
GetApartment();
|
||
|
|
|
||
|
|
ApartmentState currentState = static_cast<ApartmentState>(m_ApartmentState & ~kApartmentStateCoInitialized);
|
||
|
|
|
||
|
|
if (kApartmentStateUnknown != currentState)
|
||
|
|
{
|
||
|
|
Assert(state == currentState);
|
||
|
|
return currentState;
|
||
|
|
}
|
||
|
|
|
||
|
|
#if IL2CPP_TARGET_XBOXONE
|
||
|
|
if (state == kApartmentStateInSTA)
|
||
|
|
{
|
||
|
|
// Only assert in debug.. we wouldn't want to bring down the application in Release config
|
||
|
|
IL2CPP_ASSERT(false && "STA apartment state is not supported on Xbox One");
|
||
|
|
state = kApartmentStateInMTA;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
HRESULT hr = CoInitializeEx(nullptr, (kApartmentStateInSTA == state) ? COINIT_APARTMENTTHREADED : COINIT_MULTITHREADED);
|
||
|
|
if (SUCCEEDED(hr))
|
||
|
|
{
|
||
|
|
m_ApartmentState = state;
|
||
|
|
if (S_OK == hr)
|
||
|
|
m_ApartmentState = static_cast<ApartmentState>(m_ApartmentState | kApartmentStateCoInitialized);
|
||
|
|
else
|
||
|
|
CoUninitialize(); // Someone has already called correct CoInitialize. Don't leave incorrect reference count.
|
||
|
|
}
|
||
|
|
else if (RPC_E_CHANGED_MODE == hr)
|
||
|
|
{
|
||
|
|
// CoInitialize has already been called with a different apartment state.
|
||
|
|
m_ApartmentState = (kApartmentStateInSTA == state) ? kApartmentStateInMTA : kApartmentStateInSTA;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Based on where this function is called (Init and Shutdown) we can't really recover from this, so
|
||
|
|
// just abort.
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
|
||
|
|
return GetApartment();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::SetExplicitApartment(ApartmentState state)
|
||
|
|
{
|
||
|
|
Assert(!(m_ApartmentState & kApartmentStateCoInitialized));
|
||
|
|
m_ApartmentState = state;
|
||
|
|
}
|
||
|
|
|
||
|
|
size_t ThreadImpl::CurrentThreadId()
|
||
|
|
{
|
||
|
|
return GetCurrentThreadId();
|
||
|
|
}
|
||
|
|
|
||
|
|
ThreadImpl* ThreadImpl::CreateForCurrentThread()
|
||
|
|
{
|
||
|
|
ThreadImpl* thread = new ThreadImpl();
|
||
|
|
BOOL duplicateResult = DuplicateHandle(::GetCurrentProcess(), ::GetCurrentThread(), ::GetCurrentProcess(), &thread->m_ThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
|
||
|
|
Assert(duplicateResult && "DuplicateHandle failed.");
|
||
|
|
thread->m_ThreadId = ::GetCurrentThreadId();
|
||
|
|
return thread;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ThreadImpl::YieldInternal()
|
||
|
|
{
|
||
|
|
return SwitchToThread();
|
||
|
|
}
|
||
|
|
|
||
|
|
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
|
||
|
|
|
||
|
|
static Thread::ThreadCleanupFunc s_ThreadCleanupFunction;
|
||
|
|
static ThreadLocalValue s_ThreadCleanupArguments;
|
||
|
|
|
||
|
|
void ThreadImpl::SetNativeThreadCleanup(Thread::ThreadCleanupFunc cleanupFunction)
|
||
|
|
{
|
||
|
|
s_ThreadCleanupFunction = cleanupFunction;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::RegisterCurrentThreadForCleanup(void* arg)
|
||
|
|
{
|
||
|
|
s_ThreadCleanupArguments.SetValue(arg);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::UnregisterCurrentThreadForCleanup()
|
||
|
|
{
|
||
|
|
s_ThreadCleanupArguments.SetValue(NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ThreadImpl::OnCurrentThreadExiting()
|
||
|
|
{
|
||
|
|
Thread::ThreadCleanupFunc cleanupFunction = s_ThreadCleanupFunction;
|
||
|
|
if (cleanupFunction == NULL)
|
||
|
|
return;
|
||
|
|
|
||
|
|
void* threadCleanupArgument = NULL;
|
||
|
|
s_ThreadCleanupArguments.GetValue(&threadCleanupArgument);
|
||
|
|
|
||
|
|
if (threadCleanupArgument != NULL)
|
||
|
|
cleanupFunction(threadCleanupArgument);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|