The code shown (_beginthreadex function) is part of VC++ CRT (can be found in, e.g.
C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\startup\thread.cpp).
An instance of unique_thread_parameter is a structure that holds the thread procedure pointer, the context argument, and thread and module HANDLEs:
// corecrt_internal.h
typedef struct __acrt_thread_parameter
{
// The thread procedure and context argument
void* _procedure;
void* _context;
// The handle for the newly created thread. This is initialized only from
// _beginthread (not _beginthreadex). When a thread created via _beginthread
// exits, it frees this handle.
HANDLE _thread_handle;
// The handle for the module in which the user's thread procedure is defined.
// This may be null if the handle could not be obtained. This handle enables
// us to bump the reference count of the user's module, to ensure that the
// module will not be unloaded while the thread is executing. When the thread
// exits, it frees this handle.
HMODULE _module_handle;
// This flag is true if RoInitialized was called on the thread to initialize
// it into the MTA.
bool _initialized_apartment;
} __acrt_thread_parameter;
// thread.cpp
using unique_thread_parameter = __crt_unique_heap_ptr<
__acrt_thread_parameter,
thread_parameter_free_policy>;
create_thread_parameter creates such an instance:
static __acrt_thread_parameter* __cdecl create_thread_parameter(
void* const procedure,
void* const context
) throw()
{
unique_thread_parameter parameter(_calloc_crt_t(__acrt_thread_parameter, 1).detach());
if (!parameter)
{
return nullptr;
}
parameter.get()->_procedure = procedure;
parameter.get()->_context = context;
// Attempt to bump the reference count of the module in which the user's
// thread procedure is defined, to ensure that the module will stay loaded
// as long as the thread is executing. We will release this HMDOULE when
// the thread procedure returns or _endthreadex is called.
GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<LPCWSTR>(procedure),
¶meter.get()->_module_handle);
return parameter.detach();
}
thread_start is a template function that invokes the thread procedure:
template <typename ThreadProcedure>
static unsigned long WINAPI thread_start(void* const parameter) throw()
{
if (!parameter)
{
ExitThread(GetLastError());
}
__acrt_thread_parameter* const context = static_cast<__acrt_thread_parameter*>(parameter);
__acrt_getptd()->_beginthread_context = context;
if (__acrt_get_begin_thread_init_policy() == begin_thread_init_policy_ro_initialize)
{
context->_initialized_apartment = __acrt_RoInitialize(RO_INIT_MULTITHREADED) == S_OK;
}
__try
{
ThreadProcedure const procedure = reinterpret_cast<ThreadProcedure>(context->_procedure);
_endthreadex(invoke_thread_procedure(procedure, context->_context));
}
__except (_seh_filter_exe(GetExceptionCode(), GetExceptionInformation()))
{
// Execution should never reach here:
_exit(GetExceptionCode());
}
// This return statement will never be reached. All execution paths result
// in the thread or process exiting.
return 0;
}
It essentially calls invoke_thread_procedure, which just invokes procedure, passing in context:
static __forceinline unsigned int invoke_thread_procedure(
_beginthreadex_proc_type const procedure,
void* const context
) throw()
{
return procedure(context);
}
The code around the calls does some bookkeeping to keep the CRT consistent, for example it
automatically cleans up the thread upon exit (_endthreadex).