56e4ede53fc19692d2a9966af30a2f63de11b799
[reactos.git] / reactos / lib / kernel32 / thread / thread.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * FILE: lib/kernel32/thread/thread.c
5 * PURPOSE: Thread functions
6 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
7 * Ariadne ( ariadne@xs4all.nl)
8 *
9 */
10
11 /* INCLUDES ******************************************************************/
12
13 #include <k32.h>
14
15 #define NDEBUG
16 #include "../include/debug.h"
17
18 /* FIXME: NDK */
19 #define HIGH_PRIORITY 31
20
21 /* FUNCTIONS *****************************************************************/
22 _SEH_FILTER(BaseThreadExceptionFilter)
23 {
24 EXCEPTION_POINTERS * ExceptionInfo = _SEH_GetExceptionPointers();
25 LONG ExceptionDisposition = EXCEPTION_EXECUTE_HANDLER;
26
27 if (GlobalTopLevelExceptionFilter != NULL)
28 {
29 _SEH_TRY
30 {
31 ExceptionDisposition = GlobalTopLevelExceptionFilter(ExceptionInfo);
32 }
33 _SEH_HANDLE
34 {
35 ExceptionDisposition = UnhandledExceptionFilter(ExceptionInfo);
36 }
37 _SEH_END;
38 }
39
40 return ExceptionDisposition;
41 }
42
43 __declspec(noreturn)
44 VOID
45 STDCALL
46 BaseThreadStartup(LPTHREAD_START_ROUTINE lpStartAddress,
47 LPVOID lpParameter)
48 {
49 volatile UINT uExitCode = 0;
50
51 /* Attempt to call the Thread Start Address */
52 _SEH_TRY
53 {
54 /* Get the exit code from the Thread Start */
55 uExitCode = (lpStartAddress)((PVOID)lpParameter);
56 }
57 _SEH_EXCEPT(BaseThreadExceptionFilter)
58 {
59 /* Get the Exit code from the SEH Handler */
60 uExitCode = _SEH_GetExceptionCode();
61 } _SEH_END;
62
63 /* Exit the Thread */
64 ExitThread(uExitCode);
65 }
66
67 /*
68 * @implemented
69 */
70 HANDLE
71 STDCALL
72 CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
73 DWORD dwStackSize,
74 LPTHREAD_START_ROUTINE lpStartAddress,
75 LPVOID lpParameter,
76 DWORD dwCreationFlags,
77 LPDWORD lpThreadId)
78 {
79 /* Act as if we're going to create a remote thread in ourselves */
80 return CreateRemoteThread(NtCurrentProcess(),
81 lpThreadAttributes,
82 dwStackSize,
83 lpStartAddress,
84 lpParameter,
85 dwCreationFlags,
86 lpThreadId);
87 }
88
89 /*
90 * @implemented
91 */
92 HANDLE
93 STDCALL
94 CreateRemoteThread(HANDLE hProcess,
95 LPSECURITY_ATTRIBUTES lpThreadAttributes,
96 DWORD dwStackSize,
97 LPTHREAD_START_ROUTINE lpStartAddress,
98 LPVOID lpParameter,
99 DWORD dwCreationFlags,
100 LPDWORD lpThreadId)
101 {
102 NTSTATUS Status;
103 INITIAL_TEB InitialTeb;
104 CONTEXT Context;
105 CLIENT_ID ClientId;
106 OBJECT_ATTRIBUTES LocalObjectAttributes;
107 POBJECT_ATTRIBUTES ObjectAttributes;
108 HANDLE hThread;
109 ULONG Dummy;
110
111 DPRINT("CreateRemoteThread: hProcess: %ld dwStackSize: %ld lpStartAddress"
112 ": %p lpParameter: %lx, dwCreationFlags: %lx\n", hProcess,
113 dwStackSize, lpStartAddress, lpParameter, dwCreationFlags);
114
115 /* Clear the Context */
116 RtlZeroMemory(&Context, sizeof(CONTEXT));
117
118 /* Write PID */
119 ClientId.UniqueProcess = hProcess;
120
121 /* Create the Stack */
122 Status = BasepCreateStack(hProcess,
123 dwStackSize,
124 dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION ?
125 dwStackSize : 0,
126 &InitialTeb);
127 if(!NT_SUCCESS(Status))
128 {
129 SetLastErrorByStatus(Status);
130 return NULL;
131 }
132
133 /* Create Initial Context */
134 BasepInitializeContext(&Context,
135 lpParameter,
136 lpStartAddress,
137 InitialTeb.StackBase,
138 1);
139
140 /* initialize the attributes for the thread object */
141 ObjectAttributes = BasepConvertObjectAttributes(&LocalObjectAttributes,
142 lpThreadAttributes,
143 NULL);
144
145 /* Create the Kernel Thread Object */
146 Status = NtCreateThread(&hThread,
147 THREAD_ALL_ACCESS,
148 ObjectAttributes,
149 hProcess,
150 &ClientId,
151 &Context,
152 &InitialTeb,
153 TRUE);
154 if(!NT_SUCCESS(Status))
155 {
156 BasepFreeStack(hProcess, &InitialTeb);
157 SetLastErrorByStatus(Status);
158 return NULL;
159 }
160
161 #ifdef SXS_SUPPORT_ENABLED
162 /* Are we in the same process? */
163 if (Process = NtCurrentProcess())
164 {
165 PTEB Teb;
166 PVOID ActivationContextStack;
167 PTHREAD_BASIC_INFORMATION ThreadBasicInfo;
168 PACTIVATION_CONTEXT_BASIC_INFORMATION ActivationCtxInfo;
169 ULONG_PTR Cookie;
170
171 /* Get the TEB */
172 Status = NtQueryInformationThread(hThread,
173 ThreadBasicIformation,
174 &ThreadBasicInfo,
175 sizeof(ThreadBasicInfo),
176 NULL);
177
178 /* Allocate the Activation Context Stack */
179 Status = RtlAllocateActivationContextStack(&ActivationContextStack);
180 Teb = ThreadBasicInfo.TebBaseAddress;
181
182 /* Save it */
183 Teb->ActivationContextStackPointer = ActivationContextStack;
184
185 /* Query the Context */
186 Status = RtlQueryInformationActivationContext(1,
187 0,
188 NULL,
189 ActivationContextBasicInformation,
190 &ActivationCtxInfo,
191 sizeof(ActivationCtxInfo),
192 NULL);
193
194 /* Does it need to be activated? */
195 if (!ActivationCtxInfo.hActCtx)
196 {
197 /* Activate it */
198 Status = RtlActivateActivationContextEx(1,
199 Teb,
200 ActivationCtxInfo.hActCtx,
201 &Cookie);
202 }
203 }
204 #endif
205
206 /* FIXME: Notify CSR */
207
208 /* Success */
209 if(lpThreadId) *lpThreadId = (DWORD)ClientId.UniqueThread;
210
211 /* Resume it if asked */
212 if (!(dwCreationFlags & CREATE_SUSPENDED))
213 {
214 NtResumeThread(hThread, &Dummy);
215 }
216
217 /* Return handle to thread */
218 return hThread;
219 }
220
221 /*
222 * @implemented
223 */
224 VOID
225 STDCALL
226 ExitThread(DWORD uExitCode)
227 {
228 NTSTATUS Status;
229 BOOLEAN LastThread;
230
231 /*
232 * Terminate process if this is the last thread
233 * of the current process
234 */
235 Status = NtQueryInformationThread(NtCurrentThread(),
236 ThreadAmILastThread,
237 &LastThread,
238 sizeof(BOOLEAN),
239 NULL);
240 if (NT_SUCCESS(Status) && LastThread)
241 {
242 /* Exit the Process */
243 ExitProcess(uExitCode);
244 }
245
246 /* Notify DLLs and TLS Callbacks of termination */
247 LdrShutdownThread();
248
249 /* Tell the Kernel to free the Stack */
250 NtCurrentTeb()->FreeStackOnTermination = TRUE;
251 NtTerminateThread(NULL, uExitCode);
252
253 /* We will never reach this place. This silences the compiler */
254 ExitThread(uExitCode);
255 }
256
257 /*
258 * @implemented
259 */
260 HANDLE
261 STDCALL
262 OpenThread(
263 DWORD dwDesiredAccess,
264 BOOL bInheritHandle,
265 DWORD dwThreadId
266 )
267 {
268 NTSTATUS errCode;
269 HANDLE ThreadHandle;
270 OBJECT_ATTRIBUTES ObjectAttributes;
271 CLIENT_ID ClientId ;
272
273 ClientId.UniqueProcess = 0;
274 ClientId.UniqueThread = (HANDLE)dwThreadId;
275
276 InitializeObjectAttributes (&ObjectAttributes,
277 NULL,
278 (bInheritHandle ? OBJ_INHERIT : 0),
279 NULL,
280 NULL);
281
282 errCode = NtOpenThread(&ThreadHandle,
283 dwDesiredAccess,
284 &ObjectAttributes,
285 &ClientId);
286 if (!NT_SUCCESS(errCode))
287 {
288 SetLastErrorByStatus (errCode);
289 return NULL;
290 }
291 return ThreadHandle;
292 }
293
294
295 /*
296 * @implemented
297 */
298 PTEB
299 GetTeb(VOID)
300 {
301 return(NtCurrentTeb());
302 }
303
304
305 /*
306 * @implemented
307 */
308 BOOL STDCALL
309 SwitchToThread(VOID)
310 {
311 NTSTATUS Status;
312 Status = NtYieldExecution();
313 return Status != STATUS_NO_YIELD_PERFORMED;
314 }
315
316
317 /*
318 * @implemented
319 */
320 DWORD STDCALL
321 GetCurrentThreadId(VOID)
322 {
323 return((DWORD)(NtCurrentTeb()->Cid).UniqueThread);
324 }
325
326 /*
327 * @implemented
328 */
329 BOOL STDCALL
330 GetThreadTimes(HANDLE hThread,
331 LPFILETIME lpCreationTime,
332 LPFILETIME lpExitTime,
333 LPFILETIME lpKernelTime,
334 LPFILETIME lpUserTime)
335 {
336 KERNEL_USER_TIMES KernelUserTimes;
337 NTSTATUS Status;
338
339 Status = NtQueryInformationThread(hThread,
340 ThreadTimes,
341 &KernelUserTimes,
342 sizeof(KERNEL_USER_TIMES),
343 NULL);
344 if (!NT_SUCCESS(Status))
345 {
346 SetLastErrorByStatus(Status);
347 return(FALSE);
348 }
349
350 lpCreationTime->dwLowDateTime = KernelUserTimes.CreateTime.u.LowPart;
351 lpCreationTime->dwHighDateTime = KernelUserTimes.CreateTime.u.HighPart;
352
353 lpExitTime->dwLowDateTime = KernelUserTimes.ExitTime.u.LowPart;
354 lpExitTime->dwHighDateTime = KernelUserTimes.ExitTime.u.HighPart;
355
356 lpKernelTime->dwLowDateTime = KernelUserTimes.KernelTime.u.LowPart;
357 lpKernelTime->dwHighDateTime = KernelUserTimes.KernelTime.u.HighPart;
358
359 lpUserTime->dwLowDateTime = KernelUserTimes.UserTime.u.LowPart;
360 lpUserTime->dwHighDateTime = KernelUserTimes.UserTime.u.HighPart;
361
362 return(TRUE);
363 }
364
365
366 /*
367 * @implemented
368 */
369 BOOL STDCALL
370 GetThreadContext(HANDLE hThread,
371 LPCONTEXT lpContext)
372 {
373 NTSTATUS Status;
374
375 Status = NtGetContextThread(hThread,
376 lpContext);
377 if (!NT_SUCCESS(Status))
378 {
379 SetLastErrorByStatus(Status);
380 return(FALSE);
381 }
382
383 return(TRUE);
384 }
385
386
387 /*
388 * @implemented
389 */
390 BOOL STDCALL
391 SetThreadContext(HANDLE hThread,
392 CONST CONTEXT *lpContext)
393 {
394 NTSTATUS Status;
395
396 Status = NtSetContextThread(hThread,
397 (void *)lpContext);
398 if (!NT_SUCCESS(Status))
399 {
400 SetLastErrorByStatus(Status);
401 return(FALSE);
402 }
403
404 return(TRUE);
405 }
406
407
408 /*
409 * @implemented
410 */
411 BOOL STDCALL
412 GetExitCodeThread(HANDLE hThread,
413 LPDWORD lpExitCode)
414 {
415 THREAD_BASIC_INFORMATION ThreadBasic;
416 NTSTATUS Status;
417
418 Status = NtQueryInformationThread(hThread,
419 ThreadBasicInformation,
420 &ThreadBasic,
421 sizeof(THREAD_BASIC_INFORMATION),
422 NULL);
423 if (!NT_SUCCESS(Status))
424 {
425 SetLastErrorByStatus(Status);
426 return(FALSE);
427 }
428
429 memcpy(lpExitCode, &ThreadBasic.ExitStatus, sizeof(DWORD));
430
431 return(TRUE);
432 }
433
434
435 /*
436 * @implemented
437 */
438 DWORD STDCALL
439 ResumeThread(HANDLE hThread)
440 {
441 ULONG PreviousResumeCount;
442 NTSTATUS Status;
443
444 Status = NtResumeThread(hThread,
445 &PreviousResumeCount);
446 if (!NT_SUCCESS(Status))
447 {
448 SetLastErrorByStatus(Status);
449 return(-1);
450 }
451
452 return(PreviousResumeCount);
453 }
454
455
456 /*
457 * @implemented
458 */
459 BOOL STDCALL
460 TerminateThread(HANDLE hThread,
461 DWORD dwExitCode)
462 {
463 NTSTATUS Status;
464
465 if (0 == hThread)
466 {
467 SetLastError(ERROR_INVALID_HANDLE);
468 return(FALSE);
469 }
470
471 Status = NtTerminateThread(hThread,
472 dwExitCode);
473 if (!NT_SUCCESS(Status))
474 {
475 SetLastErrorByStatus(Status);
476 return(FALSE);
477 }
478
479 return(TRUE);
480 }
481
482
483 /*
484 * @implemented
485 */
486 DWORD STDCALL
487 SuspendThread(HANDLE hThread)
488 {
489 ULONG PreviousSuspendCount;
490 NTSTATUS Status;
491
492 Status = NtSuspendThread(hThread,
493 &PreviousSuspendCount);
494 if (!NT_SUCCESS(Status))
495 {
496 SetLastErrorByStatus(Status);
497 return(-1);
498 }
499
500 return(PreviousSuspendCount);
501 }
502
503
504 /*
505 * @implemented
506 */
507 DWORD STDCALL
508 SetThreadAffinityMask(HANDLE hThread,
509 DWORD dwThreadAffinityMask)
510 {
511 THREAD_BASIC_INFORMATION ThreadBasic;
512 KAFFINITY AffinityMask;
513 NTSTATUS Status;
514
515 AffinityMask = (KAFFINITY)dwThreadAffinityMask;
516
517 Status = NtQueryInformationThread(hThread,
518 ThreadBasicInformation,
519 &ThreadBasic,
520 sizeof(THREAD_BASIC_INFORMATION),
521 NULL);
522 if (!NT_SUCCESS(Status))
523 {
524 SetLastErrorByStatus(Status);
525 return(0);
526 }
527
528 Status = NtSetInformationThread(hThread,
529 ThreadAffinityMask,
530 &AffinityMask,
531 sizeof(KAFFINITY));
532 if (!NT_SUCCESS(Status))
533 SetLastErrorByStatus(Status);
534
535 return(ThreadBasic.AffinityMask);
536 }
537
538
539 /*
540 * @implemented
541 */
542 BOOL
543 STDCALL
544 SetThreadPriority(HANDLE hThread,
545 int nPriority)
546 {
547 ULONG Prio = nPriority;
548 NTSTATUS Status;
549
550 /* Check if values forcing saturation should be used */
551 if (Prio == THREAD_PRIORITY_TIME_CRITICAL)
552 {
553 Prio = (HIGH_PRIORITY + 1) / 2;
554 }
555 else if (Prio == THREAD_PRIORITY_IDLE)
556 {
557 Prio = -((HIGH_PRIORITY + 1) / 2);
558 }
559
560 /* Set the Base Priority */
561 Status = NtSetInformationThread(hThread,
562 ThreadBasePriority,
563 &Prio,
564 sizeof(ULONG));
565 if (!NT_SUCCESS(Status))
566 {
567 /* Failure */
568 SetLastErrorByStatus(Status);
569 return FALSE;
570 }
571
572 /* Return */
573 return TRUE;
574 }
575
576 /*
577 * @implemented
578 */
579 int
580 STDCALL
581 GetThreadPriority(HANDLE hThread)
582 {
583 THREAD_BASIC_INFORMATION ThreadBasic;
584 NTSTATUS Status;
585
586 /* Query the Base Priority Increment */
587 Status = NtQueryInformationThread(hThread,
588 ThreadBasicInformation,
589 &ThreadBasic,
590 sizeof(THREAD_BASIC_INFORMATION),
591 NULL);
592 if (!NT_SUCCESS(Status))
593 {
594 /* Failure */
595 SetLastErrorByStatus(Status);
596 return THREAD_PRIORITY_ERROR_RETURN;
597 }
598
599 /* Do some conversions for out of boundary values */
600 if (ThreadBasic.BasePriority > THREAD_BASE_PRIORITY_MAX)
601 {
602 ThreadBasic.BasePriority = THREAD_PRIORITY_TIME_CRITICAL;
603 }
604 else if (ThreadBasic.BasePriority < THREAD_BASE_PRIORITY_MIN)
605 {
606 ThreadBasic.BasePriority = THREAD_PRIORITY_IDLE;
607 }
608
609 /* Return the final result */
610 return ThreadBasic.BasePriority;
611 }
612
613 /*
614 * @implemented
615 */
616 BOOL STDCALL
617 GetThreadPriorityBoost(IN HANDLE hThread,
618 OUT PBOOL pDisablePriorityBoost)
619 {
620 ULONG PriorityBoost;
621 NTSTATUS Status;
622
623 Status = NtQueryInformationThread(hThread,
624 ThreadPriorityBoost,
625 &PriorityBoost,
626 sizeof(ULONG),
627 NULL);
628 if (!NT_SUCCESS(Status))
629 {
630 SetLastErrorByStatus(Status);
631 return(FALSE);
632 }
633
634 *pDisablePriorityBoost = !((BOOL)PriorityBoost);
635
636 return(TRUE);
637 }
638
639
640 /*
641 * @implemented
642 */
643 BOOL STDCALL
644 SetThreadPriorityBoost(IN HANDLE hThread,
645 IN BOOL bDisablePriorityBoost)
646 {
647 ULONG PriorityBoost;
648 NTSTATUS Status;
649
650 PriorityBoost = (ULONG)!bDisablePriorityBoost;
651
652 Status = NtSetInformationThread(hThread,
653 ThreadPriorityBoost,
654 &PriorityBoost,
655 sizeof(ULONG));
656 if (!NT_SUCCESS(Status))
657 {
658 SetLastErrorByStatus(Status);
659 return(FALSE);
660 }
661
662 return(TRUE);
663 }
664
665
666 /*
667 * @implemented
668 */
669 BOOL STDCALL
670 GetThreadSelectorEntry(IN HANDLE hThread,
671 IN DWORD dwSelector,
672 OUT LPLDT_ENTRY lpSelectorEntry)
673 {
674 DESCRIPTOR_TABLE_ENTRY DescriptionTableEntry;
675 NTSTATUS Status;
676
677 DescriptionTableEntry.Selector = dwSelector;
678 Status = NtQueryInformationThread(hThread,
679 ThreadDescriptorTableEntry,
680 &DescriptionTableEntry,
681 sizeof(DESCRIPTOR_TABLE_ENTRY),
682 NULL);
683 if(!NT_SUCCESS(Status))
684 {
685 SetLastErrorByStatus(Status);
686 return FALSE;
687 }
688
689 *lpSelectorEntry = DescriptionTableEntry.Descriptor;
690 return TRUE;
691 }
692
693
694 /*
695 * @implemented
696 */
697 DWORD STDCALL
698 SetThreadIdealProcessor(HANDLE hThread,
699 DWORD dwIdealProcessor)
700 {
701 NTSTATUS Status;
702
703 Status = NtSetInformationThread(hThread,
704 ThreadIdealProcessor,
705 &dwIdealProcessor,
706 sizeof(ULONG));
707 if (!NT_SUCCESS(Status))
708 {
709 SetLastErrorByStatus(Status);
710 return -1;
711 }
712
713 return dwIdealProcessor;
714 }
715
716
717 /*
718 * @implemented
719 */
720 DWORD STDCALL
721 GetProcessIdOfThread(HANDLE Thread)
722 {
723 THREAD_BASIC_INFORMATION ThreadBasic;
724 NTSTATUS Status;
725
726 Status = NtQueryInformationThread(Thread,
727 ThreadBasicInformation,
728 &ThreadBasic,
729 sizeof(THREAD_BASIC_INFORMATION),
730 NULL);
731 if(!NT_SUCCESS(Status))
732 {
733 SetLastErrorByStatus(Status);
734 return 0;
735 }
736
737 return (DWORD)ThreadBasic.ClientId.UniqueProcess;
738 }
739
740
741 /*
742 * @implemented
743 */
744 DWORD STDCALL
745 GetThreadId(HANDLE Thread)
746 {
747 THREAD_BASIC_INFORMATION ThreadBasic;
748 NTSTATUS Status;
749
750 Status = NtQueryInformationThread(Thread,
751 ThreadBasicInformation,
752 &ThreadBasic,
753 sizeof(THREAD_BASIC_INFORMATION),
754 NULL);
755 if(!NT_SUCCESS(Status))
756 {
757 SetLastErrorByStatus(Status);
758 return 0;
759 }
760
761 return (DWORD)ThreadBasic.ClientId.UniqueThread;
762 }
763
764 /*
765 * @unimplemented
766 */
767 LANGID STDCALL
768 SetThreadUILanguage(WORD wReserved)
769 {
770 DPRINT1("SetThreadUILanguage(0x%4x) unimplemented!\n", wReserved);
771 return 0;
772 }
773
774 static void CALLBACK
775 IntCallUserApc(PVOID Function, PVOID dwData, PVOID Argument3)
776 {
777 PAPCFUNC pfnAPC = (PAPCFUNC)Function;
778 pfnAPC((ULONG_PTR)dwData);
779 }
780
781 /*
782 * @implemented
783 */
784 DWORD STDCALL
785 QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData)
786 {
787 NTSTATUS Status;
788
789 Status = NtQueueApcThread(hThread, IntCallUserApc, pfnAPC,
790 (PVOID)dwData, NULL);
791 if (Status)
792 SetLastErrorByStatus(Status);
793
794 return NT_SUCCESS(Status);
795 }
796
797 /*
798 * @implemented
799 */
800 BOOL STDCALL
801 GetThreadIOPendingFlag(HANDLE hThread,
802 PBOOL lpIOIsPending)
803 {
804 ULONG IoPending;
805 NTSTATUS Status;
806
807 if(lpIOIsPending == NULL)
808 {
809 SetLastError(ERROR_INVALID_PARAMETER);
810 return FALSE;
811 }
812
813 Status = NtQueryInformationThread(hThread,
814 ThreadIsIoPending,
815 (PVOID)&IoPending,
816 sizeof(IoPending),
817 NULL);
818 if(NT_SUCCESS(Status))
819 {
820 *lpIOIsPending = ((IoPending != 0) ? TRUE : FALSE);
821 return TRUE;
822 }
823
824 SetLastErrorByStatus(Status);
825 return FALSE;
826 }
827
828
829 /*
830 * @implemented
831 */
832 VOID STDCALL
833 Sleep(DWORD dwMilliseconds)
834 {
835 SleepEx(dwMilliseconds, FALSE);
836 return;
837 }
838
839
840 /*
841 * @implemented
842 */
843 DWORD STDCALL
844 SleepEx(DWORD dwMilliseconds,
845 BOOL bAlertable)
846 {
847 LARGE_INTEGER Interval;
848 NTSTATUS errCode;
849
850 if (dwMilliseconds != INFINITE)
851 {
852 /*
853 * System time units are 100 nanoseconds (a nanosecond is a billionth of
854 * a second).
855 */
856 Interval.QuadPart = -((ULONGLONG)dwMilliseconds * 10000);
857 }
858 else
859 {
860 /* Approximately 292000 years hence */
861 Interval.QuadPart = -0x7FFFFFFFFFFFFFFFLL;
862 }
863
864 errCode = NtDelayExecution ((bAlertable ? TRUE : FALSE), &Interval);
865 if (!NT_SUCCESS(errCode))
866 {
867 SetLastErrorByStatus (errCode);
868 return -1;
869 }
870 return 0;
871 }
872
873 /* EOF */