VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/win/thread-win.cpp@ 105155

Last change on this file since 105155 was 99901, checked in by vboxsync, 19 months ago

IPRT: Cleaned up RTThreadGetExecutionTimeMilli and associated testcase.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 19.7 KB
Line 
1/* $Id: thread-win.cpp 99901 2023-05-22 14:15:10Z vboxsync $ */
2/** @file
3 * IPRT - Threads, Windows.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#define LOG_GROUP RTLOGGROUP_THREAD
42#include <iprt/nt/nt-and-windows.h>
43
44#ifndef IPRT_NO_CRT
45# include <errno.h>
46# include <process.h>
47#endif
48
49#include <iprt/thread.h>
50#include "internal/iprt.h"
51
52#include <iprt/asm-amd64-x86.h>
53#include <iprt/assert.h>
54#include <iprt/cpuset.h>
55#include <iprt/err.h>
56#include <iprt/ldr.h>
57#include <iprt/log.h>
58#include <iprt/mem.h>
59#include <iprt/param.h>
60#include "internal/thread.h"
61#include "internal-r3-win.h"
62
63
64/*********************************************************************************************************************************
65* Structures and Typedefs *
66*********************************************************************************************************************************/
67/** SetThreadDescription */
68typedef HRESULT (WINAPI *PFNSETTHREADDESCRIPTION)(HANDLE hThread, WCHAR *pwszName); /* Since W10 1607 */
69
70/** CoInitializeEx */
71typedef HRESULT (WINAPI *PFNCOINITIALIZEEX)(LPVOID, DWORD);
72/** CoUninitialize */
73typedef void (WINAPI *PFNCOUNINITIALIZE)(void);
74/** OleUninitialize */
75typedef void (WINAPI *PFNOLEUNINITIALIZE)(void);
76
77
78
79/*********************************************************************************************************************************
80* Global Variables *
81*********************************************************************************************************************************/
82/** The TLS index allocated for storing the RTTHREADINT pointer. */
83static DWORD g_dwSelfTLS = TLS_OUT_OF_INDEXES;
84/** Pointer to SetThreadDescription (KERNEL32.DLL) if available. */
85static PFNSETTHREADDESCRIPTION g_pfnSetThreadDescription = NULL;
86
87/** Pointer to CoInitializeEx (OLE32.DLL / combase.dll) if available. */
88static PFNCOINITIALIZEEX volatile g_pfnCoInitializeEx = NULL;
89/** Pointer to CoUninitialize (OLE32.DLL / combase.dll) if available. */
90static PFNCOUNINITIALIZE volatile g_pfnCoUninitialize = NULL;
91/** Pointer to OleUninitialize (OLE32.DLL / combase.dll) if available. */
92static PFNOLEUNINITIALIZE volatile g_pfnOleUninitialize = NULL;
93
94
95/*********************************************************************************************************************************
96* Internal Functions *
97*********************************************************************************************************************************/
98static void rtThreadWinTellDebuggerThreadName(uint32_t idThread, const char *pszName);
99DECLINLINE(void) rtThreadWinSetThreadName(PRTTHREADINT pThread, DWORD idThread);
100
101
102DECLHIDDEN(int) rtThreadNativeInit(void)
103{
104 g_dwSelfTLS = TlsAlloc();
105 if (g_dwSelfTLS == TLS_OUT_OF_INDEXES)
106 return VERR_NO_TLS_FOR_SELF;
107
108 g_pfnSetThreadDescription = (PFNSETTHREADDESCRIPTION)GetProcAddress(g_hModKernel32, "SetThreadDescription");
109 return VINF_SUCCESS;
110}
111
112
113DECLHIDDEN(void) rtThreadNativeReInitObtrusive(void)
114{
115 /* nothing to do here. */
116}
117
118
119DECLHIDDEN(void) rtThreadNativeDetach(void)
120{
121 /*
122 * Deal with alien threads.
123 */
124 PRTTHREADINT pThread = (PRTTHREADINT)TlsGetValue(g_dwSelfTLS);
125 if ( pThread
126 && (pThread->fIntFlags & RTTHREADINT_FLAGS_ALIEN))
127 {
128 rtThreadTerminate(pThread, 0);
129 TlsSetValue(g_dwSelfTLS, NULL);
130 }
131}
132
133
134DECLHIDDEN(void) rtThreadNativeDestroy(PRTTHREADINT pThread)
135{
136 if (pThread == (PRTTHREADINT)TlsGetValue(g_dwSelfTLS))
137 TlsSetValue(g_dwSelfTLS, NULL);
138
139 if ((HANDLE)pThread->hThread != INVALID_HANDLE_VALUE)
140 {
141 CloseHandle((HANDLE)pThread->hThread);
142 pThread->hThread = (uintptr_t)INVALID_HANDLE_VALUE;
143 }
144}
145
146
147DECLHIDDEN(int) rtThreadNativeAdopt(PRTTHREADINT pThread)
148{
149 if (!TlsSetValue(g_dwSelfTLS, pThread))
150 return VERR_FAILED_TO_SET_SELF_TLS;
151 rtThreadWinSetThreadName(pThread, GetCurrentThreadId());
152 return VINF_SUCCESS;
153}
154
155
156DECLHIDDEN(void) rtThreadNativeInformDebugger(PRTTHREADINT pThread)
157{
158 rtThreadWinTellDebuggerThreadName((uint32_t)(uintptr_t)pThread->Core.Key, pThread->szName);
159}
160
161
162/**
163 * Communicates the thread name to the debugger, if we're begin debugged that
164 * is.
165 *
166 * See http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx for debugger
167 * interface details.
168 *
169 * @param idThread The thread ID. UINT32_MAX for current thread.
170 * @param pszName The name.
171 */
172static void rtThreadWinTellDebuggerThreadName(uint32_t idThread, const char *pszName)
173{
174 struct
175 {
176 uint32_t uType;
177 const char *pszName;
178 uint32_t idThread;
179 uint32_t fFlags;
180 } Pkg = { 0x1000, pszName, idThread, 0 };
181 __try
182 {
183 RaiseException(0x406d1388, 0, sizeof(Pkg)/sizeof(ULONG_PTR), (ULONG_PTR *)&Pkg);
184 }
185 __except(EXCEPTION_CONTINUE_EXECUTION)
186 {
187
188 }
189}
190
191
192/**
193 * Sets the thread name as best as we can.
194 */
195DECLINLINE(void) rtThreadWinSetThreadName(PRTTHREADINT pThread, DWORD idThread)
196{
197 if (g_pfnIsDebuggerPresent && g_pfnIsDebuggerPresent())
198 rtThreadWinTellDebuggerThreadName(idThread, &pThread->szName[0]);
199
200 /* The SetThreadDescription API introduced in windows 10 1607 / server 2016
201 allows setting the thread name while the debugger isn't attached. Works
202 with WinDbgX, VisualStudio 2017 v15.6+, and presumeably some recent windbg
203 version. */
204 if (g_pfnSetThreadDescription)
205 {
206 /* The name should be ASCII, so we just need to expand 'char' to 'WCHAR'. */
207 WCHAR wszName[RTTHREAD_NAME_LEN];
208 for (size_t i = 0; i < RTTHREAD_NAME_LEN; i++)
209 wszName[i] = pThread->szName[i];
210
211 HRESULT hrc = g_pfnSetThreadDescription(GetCurrentThread(), wszName);
212 Assert(SUCCEEDED(hrc)); RT_NOREF(hrc);
213 }
214}
215
216
217/**
218 * Bitch about dangling COM and OLE references, dispose of them
219 * afterwards so we don't end up deadlocked somewhere below
220 * OLE32!DllMain.
221 */
222static void rtThreadNativeUninitComAndOle(void)
223{
224#if 1 /* experimental code */
225 /*
226 * Read the counters.
227 */
228 struct MySOleTlsData
229 {
230 void *apvReserved0[2]; /**< x86=0x00 W7/64=0x00 */
231 DWORD adwReserved0[3]; /**< x86=0x08 W7/64=0x10 */
232 void *apvReserved1[1]; /**< x86=0x14 W7/64=0x20 */
233 DWORD cComInits; /**< x86=0x18 W7/64=0x28 */
234 DWORD cOleInits; /**< x86=0x1c W7/64=0x2c */
235 DWORD dwReserved1; /**< x86=0x20 W7/64=0x30 */
236 void *apvReserved2[4]; /**< x86=0x24 W7/64=0x38 */
237 DWORD adwReserved2[1]; /**< x86=0x34 W7/64=0x58 */
238 void *pvCurrentCtx; /**< x86=0x38 W7/64=0x60 */
239 IUnknown *pCallState; /**< x86=0x3c W7/64=0x68 */
240 } *pOleTlsData = NULL; /* outside the try/except for debugging */
241 DWORD cComInits = 0;
242 DWORD cOleInits = 0;
243 __try
244 {
245 void *pvTeb = NtCurrentTeb();
246# ifdef RT_ARCH_AMD64
247 pOleTlsData = *(struct MySOleTlsData **)((uintptr_t)pvTeb + 0x1758); /*TEB.ReservedForOle*/
248# elif RT_ARCH_X86
249 pOleTlsData = *(struct MySOleTlsData **)((uintptr_t)pvTeb + 0x0f80); /*TEB.ReservedForOle*/
250# else
251# error "Port me!"
252# endif
253 if (pOleTlsData)
254 {
255 cComInits = pOleTlsData->cComInits;
256 cOleInits = pOleTlsData->cOleInits;
257 }
258 }
259 __except(EXCEPTION_EXECUTE_HANDLER)
260 {
261 AssertFailedReturnVoid();
262 }
263
264 /*
265 * Assert sanity. If any of these breaks, the structure layout above is
266 * probably not correct any longer.
267 */
268 AssertMsgReturnVoid(cComInits < 1000, ("%u (%#x)\n", cComInits, cComInits));
269 AssertMsgReturnVoid(cOleInits < 1000, ("%u (%#x)\n", cOleInits, cOleInits));
270 AssertMsgReturnVoid(cComInits >= cOleInits, ("cComInits=%#x cOleInits=%#x\n", cComInits, cOleInits));
271
272 /*
273 * Do the uninitializing.
274 */
275 if (cComInits)
276 {
277 AssertMsgFailed(("cComInits=%u (%#x) cOleInits=%u (%#x) - dangling COM/OLE inits!\n",
278 cComInits, cComInits, cOleInits, cOleInits));
279
280 PFNOLEUNINITIALIZE pfnOleUninitialize = g_pfnOleUninitialize;
281 PFNCOUNINITIALIZE pfnCoUninitialize = g_pfnCoUninitialize;
282 if (pfnCoUninitialize && pfnOleUninitialize)
283 { /* likely */ }
284 else
285 {
286 HMODULE hOle32 = GetModuleHandle("ole32.dll");
287 AssertReturnVoid(hOle32 != NULL);
288
289 pfnOleUninitialize = (PFNOLEUNINITIALIZE)GetProcAddress(hOle32, "OleUninitialize");
290 AssertReturnVoid(pfnOleUninitialize);
291
292 pfnCoUninitialize = (PFNCOUNINITIALIZE)GetProcAddress(hOle32, "CoUninitialize");
293 AssertReturnVoid(pfnCoUninitialize);
294 }
295
296 while (cOleInits-- > 0)
297 {
298 pfnOleUninitialize();
299 cComInits--;
300 }
301
302 while (cComInits-- > 0)
303 pfnCoUninitialize();
304 }
305#endif
306}
307
308
309/**
310 * Implements the RTTHREADFLAGS_COM_MTA and RTTHREADFLAGS_COM_STA flags.
311 *
312 * @returns true if COM uninitialization should be done, false if not.
313 * @param fFlags The thread flags.
314 */
315static bool rtThreadNativeWinCoInitialize(unsigned fFlags)
316{
317 /*
318 * Resolve the ole32 init and uninit functions dynamically.
319 */
320 PFNCOINITIALIZEEX pfnCoInitializeEx = g_pfnCoInitializeEx;
321 PFNCOUNINITIALIZE pfnCoUninitialize = g_pfnCoUninitialize;
322 if (pfnCoInitializeEx && pfnCoUninitialize)
323 { /* likely */ }
324 else
325 {
326 RTLDRMOD hModOle32 = NIL_RTLDRMOD;
327 int rc = RTLdrLoadSystem("ole32.dll", true /*fNoUnload*/, &hModOle32);
328 AssertRCReturn(rc, false);
329
330 PFNOLEUNINITIALIZE pfnOleUninitialize;
331 pfnOleUninitialize = (PFNOLEUNINITIALIZE)RTLdrGetFunction(hModOle32, "OleUninitialize");
332 pfnCoUninitialize = (PFNCOUNINITIALIZE )RTLdrGetFunction(hModOle32, "CoUninitialize");
333 pfnCoInitializeEx = (PFNCOINITIALIZEEX )RTLdrGetFunction(hModOle32, "CoInitializeEx");
334
335 RTLdrClose(hModOle32);
336 AssertReturn(pfnCoInitializeEx && pfnCoUninitialize, false);
337
338 if (pfnOleUninitialize && !g_pfnOleUninitialize)
339 g_pfnOleUninitialize = pfnOleUninitialize;
340 g_pfnCoInitializeEx = pfnCoInitializeEx;
341 g_pfnCoUninitialize = pfnCoUninitialize;
342 }
343
344 /*
345 * Do the initializating.
346 */
347 DWORD fComInit;
348 if (fFlags & RTTHREADFLAGS_COM_MTA)
349 fComInit = COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY | COINIT_DISABLE_OLE1DDE;
350 else
351 fComInit = COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY;
352 HRESULT hrc = pfnCoInitializeEx(NULL, fComInit);
353 AssertMsg(SUCCEEDED(hrc), ("%Rhrc fComInit=%#x\n", hrc, fComInit));
354 return SUCCEEDED(hrc);
355}
356
357
358/**
359 * Wrapper which unpacks the param stuff and calls thread function.
360 */
361#ifndef IPRT_NO_CRT
362static unsigned __stdcall rtThreadNativeMain(void *pvArgs) RT_NOTHROW_DEF
363#else
364static DWORD __stdcall rtThreadNativeMain(void *pvArgs) RT_NOTHROW_DEF
365#endif
366{
367 DWORD dwThreadId = GetCurrentThreadId();
368 PRTTHREADINT pThread = (PRTTHREADINT)pvArgs;
369
370 if (!TlsSetValue(g_dwSelfTLS, pThread))
371 AssertReleaseMsgFailed(("failed to set self TLS. lasterr=%d thread '%s'\n", GetLastError(), pThread->szName));
372 rtThreadWinSetThreadName(pThread, dwThreadId);
373
374 bool fUninitCom = (pThread->fFlags & (RTTHREADFLAGS_COM_MTA | RTTHREADFLAGS_COM_STA)) != 0;
375 if (fUninitCom)
376 fUninitCom = rtThreadNativeWinCoInitialize(pThread->fFlags);
377
378 int rc = rtThreadMain(pThread, dwThreadId, &pThread->szName[0]);
379
380 TlsSetValue(g_dwSelfTLS, NULL); /* rtThreadMain already released the structure. */
381
382 if (fUninitCom && g_pfnCoUninitialize)
383 g_pfnCoUninitialize();
384
385 rtThreadNativeUninitComAndOle();
386#ifndef IPRT_NO_CRT
387 _endthreadex(rc);
388 return rc; /* not reached */
389#else
390 for (;;)
391 ExitThread(rc);
392#endif
393}
394
395
396DECLHIDDEN(int) rtThreadNativeCreate(PRTTHREADINT pThread, PRTNATIVETHREAD pNativeThread)
397{
398 AssertReturn(pThread->cbStack < ~(unsigned)0, VERR_INVALID_PARAMETER);
399
400 /*
401 * If a stack size is given, make sure it's not a multiple of 64KB so that we
402 * get one or more pages for overflow protection. (ASSUMES 64KB alloc align.)
403 */
404 unsigned cbStack = (unsigned)pThread->cbStack;
405 if (cbStack > 0 && RT_ALIGN_T(cbStack, _64K, unsigned) == cbStack)
406 cbStack += PAGE_SIZE;
407
408 /*
409 * Create the thread.
410 */
411 pThread->hThread = (uintptr_t)INVALID_HANDLE_VALUE;
412#ifndef IPRT_NO_CRT
413 unsigned uThreadId = 0;
414 uintptr_t hThread = _beginthreadex(NULL /*pSecAttrs*/, cbStack, rtThreadNativeMain, pThread, 0 /*fFlags*/, &uThreadId);
415 if (hThread != 0 && hThread != ~0U)
416 {
417 pThread->hThread = hThread;
418 *pNativeThread = uThreadId;
419 return VINF_SUCCESS;
420 }
421 return RTErrConvertFromErrno(errno);
422#else
423 DWORD idThread = 0;
424 HANDLE hThread = CreateThread(NULL /*pSecAttrs*/, cbStack, rtThreadNativeMain, pThread, 0 /*fFlags*/, &idThread);
425 if (hThread != NULL)
426 {
427 pThread->hThread = (uintptr_t)hThread;
428 *pNativeThread = idThread;
429 return VINF_SUCCESS;
430 }
431 return RTErrConvertFromWin32(GetLastError());
432#endif
433}
434
435
436DECLHIDDEN(bool) rtThreadNativeIsAliveKludge(PRTTHREADINT pThread)
437{
438 PPEB_COMMON pPeb = NtCurrentPeb();
439 if (!pPeb || !pPeb->Ldr || !pPeb->Ldr->ShutdownInProgress)
440 return true;
441 DWORD rcWait = WaitForSingleObject((HANDLE)pThread->hThread, 0);
442 return rcWait != WAIT_OBJECT_0;
443}
444
445
446RTDECL(RTTHREAD) RTThreadSelf(void)
447{
448 PRTTHREADINT pThread = (PRTTHREADINT)TlsGetValue(g_dwSelfTLS);
449 /** @todo import alien threads ? */
450 return pThread;
451}
452
453
454#if 0 /* noone is using this ... */
455/**
456 * Returns the processor number the current thread was running on during this call
457 *
458 * @returns processor nr
459 */
460static int rtThreadGetCurrentProcessorNumber(void)
461{
462 static bool fInitialized = false;
463 static DWORD (WINAPI *pfnGetCurrentProcessorNumber)(void) = NULL;
464 if (!fInitialized)
465 {
466 HMODULE hmodKernel32 = GetModuleHandle("kernel32.dll");
467 if (hmodKernel32)
468 pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)(void))GetProcAddress(hmodKernel32, "GetCurrentProcessorNumber");
469 fInitialized = true;
470 }
471 if (pfnGetCurrentProcessorNumber)
472 return pfnGetCurrentProcessorNumber();
473 return -1;
474}
475#endif
476
477
478RTR3DECL(int) RTThreadSetAffinity(PCRTCPUSET pCpuSet)
479{
480 /* The affinity functionality was added in NT 3.50, so we resolve the APIs
481 dynamically to be able to run on NT 3.1. */
482 if (g_pfnSetThreadAffinityMask)
483 {
484 DWORD_PTR fNewMask = pCpuSet ? RTCpuSetToU64(pCpuSet) : ~(DWORD_PTR)0;
485 DWORD_PTR dwRet = g_pfnSetThreadAffinityMask(GetCurrentThread(), fNewMask);
486 if (dwRet)
487 return VINF_SUCCESS;
488
489 int iLastError = GetLastError();
490 AssertMsgFailed(("SetThreadAffinityMask failed, LastError=%d\n", iLastError));
491 return RTErrConvertFromWin32(iLastError);
492 }
493 return VERR_NOT_SUPPORTED;
494}
495
496
497RTR3DECL(int) RTThreadGetAffinity(PRTCPUSET pCpuSet)
498{
499 /* The affinity functionality was added in NT 3.50, so we resolve the APIs
500 dynamically to be able to run on NT 3.1. */
501 if ( g_pfnSetThreadAffinityMask
502 && g_pfnGetProcessAffinityMask)
503 {
504 /*
505 * Haven't found no query api, but the set api returns the old mask, so let's use that.
506 */
507 DWORD_PTR dwIgnored;
508 DWORD_PTR dwProcAff = 0;
509 if (g_pfnGetProcessAffinityMask(GetCurrentProcess(), &dwProcAff, &dwIgnored))
510 {
511 HANDLE hThread = GetCurrentThread();
512 DWORD_PTR dwRet = g_pfnSetThreadAffinityMask(hThread, dwProcAff);
513 if (dwRet)
514 {
515 DWORD_PTR dwSet = g_pfnSetThreadAffinityMask(hThread, dwRet);
516 Assert(dwSet == dwProcAff); NOREF(dwRet);
517
518 RTCpuSetFromU64(pCpuSet, (uint64_t)dwSet);
519 return VINF_SUCCESS;
520 }
521 }
522
523 int iLastError = GetLastError();
524 AssertMsgFailed(("SetThreadAffinityMask or GetProcessAffinityMask failed, LastError=%d\n", iLastError));
525 return RTErrConvertFromWin32(iLastError);
526 }
527 return VERR_NOT_SUPPORTED;
528}
529
530
531RTR3DECL(int) RTThreadGetExecutionTimeMilli(uint64_t *pcMsKernelTime, uint64_t *pcMsUserTime)
532{
533 uint64_t u64CreationTime, u64ExitTime, u64KernelTime, u64UserTime;
534
535 if (GetThreadTimes(GetCurrentThread(), (LPFILETIME)&u64CreationTime, (LPFILETIME)&u64ExitTime, (LPFILETIME)&u64KernelTime, (LPFILETIME)&u64UserTime))
536 {
537 *pcMsKernelTime = u64KernelTime / 10000; /* GetThreadTimes returns time in 100 ns units */
538 *pcMsUserTime = u64UserTime / 10000; /* GetThreadTimes returns time in 100 ns units */
539 return VINF_SUCCESS;
540 }
541
542 DWORD const dwErr = GetLastError();
543 AssertMsgFailed(("GetThreadTimes failed, LastError=%d\n", dwErr));
544 return RTErrConvertFromWin32(dwErr);
545}
546
547
548/**
549 * Gets the native thread handle for a IPRT thread.
550 *
551 * @returns The thread handle. INVALID_HANDLE_VALUE on failure.
552 * @param hThread The IPRT thread handle.
553 *
554 * @note Windows only.
555 * @note Only valid after parent returns from the thread creation call.
556 */
557RTDECL(uintptr_t) RTThreadGetNativeHandle(RTTHREAD hThread)
558{
559 PRTTHREADINT pThread = rtThreadGet(hThread);
560 if (pThread)
561 {
562 uintptr_t hHandle = pThread->hThread;
563 rtThreadRelease(pThread);
564 return hHandle;
565 }
566 return (uintptr_t)INVALID_HANDLE_VALUE;
567}
568RT_EXPORT_SYMBOL(RTThreadGetNativeHandle);
569
570
571RTDECL(int) RTThreadPoke(RTTHREAD hThread)
572{
573 AssertReturn(hThread != RTThreadSelf(), VERR_INVALID_PARAMETER);
574 if (g_pfnNtAlertThread)
575 {
576 PRTTHREADINT pThread = rtThreadGet(hThread);
577 AssertReturn(pThread, VERR_INVALID_HANDLE);
578
579 NTSTATUS rcNt = g_pfnNtAlertThread((HANDLE)pThread->hThread);
580
581 rtThreadRelease(pThread);
582 if (NT_SUCCESS(rcNt))
583 return VINF_SUCCESS;
584 return RTErrConvertFromNtStatus(rcNt);
585 }
586 return VERR_NOT_IMPLEMENTED;
587}
588
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette