Sync with trunk (r49303)
[reactos.git] / subsystems / win32 / win32k / ntuser / callback.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * PURPOSE: Window classes
5 * FILE: subsys/win32k/ntuser/wndproc.c
6 * PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net)
7 * Thomas Weidenmueller (w3seek@users.sourceforge.net)
8 * REVISION HISTORY:
9 * 06-06-2001 CSH Created
10 * NOTES: Please use the Callback Memory Management functions for
11 * callbacks to make sure, the memory is freed on thread
12 * termination!
13 */
14
15 /* INCLUDES ******************************************************************/
16
17 #include <win32k.h>
18
19 #define NDEBUG
20 #include <debug.h>
21
22 /* CALLBACK MEMORY MANAGEMENT ************************************************/
23
24 typedef struct _INT_CALLBACK_HEADER
25 {
26 /* list entry in the THREADINFO structure */
27 LIST_ENTRY ListEntry;
28 }
29 INT_CALLBACK_HEADER, *PINT_CALLBACK_HEADER;
30
31 PVOID FASTCALL
32 IntCbAllocateMemory(ULONG Size)
33 {
34 PINT_CALLBACK_HEADER Mem;
35 PTHREADINFO W32Thread;
36
37 if(!(Mem = ExAllocatePoolWithTag(PagedPool, Size + sizeof(INT_CALLBACK_HEADER),
38 TAG_CALLBACK)))
39 {
40 return NULL;
41 }
42
43 W32Thread = PsGetCurrentThreadWin32Thread();
44 ASSERT(W32Thread);
45
46 /* insert the callback memory into the thread's callback list */
47
48 InsertTailList(&W32Thread->W32CallbackListHead, &Mem->ListEntry);
49
50 return (Mem + 1);
51 }
52
53 VOID FASTCALL
54 IntCbFreeMemory(PVOID Data)
55 {
56 PINT_CALLBACK_HEADER Mem;
57 PTHREADINFO W32Thread;
58
59 ASSERT(Data);
60
61 Mem = ((PINT_CALLBACK_HEADER)Data - 1);
62
63 W32Thread = PsGetCurrentThreadWin32Thread();
64 ASSERT(W32Thread);
65
66 /* remove the memory block from the thread's callback list */
67 RemoveEntryList(&Mem->ListEntry);
68
69 /* free memory */
70 ExFreePoolWithTag(Mem, TAG_CALLBACK);
71 }
72
73 VOID FASTCALL
74 IntCleanupThreadCallbacks(PTHREADINFO W32Thread)
75 {
76 PLIST_ENTRY CurrentEntry;
77 PINT_CALLBACK_HEADER Mem;
78
79 while (!IsListEmpty(&W32Thread->W32CallbackListHead))
80 {
81 CurrentEntry = RemoveHeadList(&W32Thread->W32CallbackListHead);
82 Mem = CONTAINING_RECORD(CurrentEntry, INT_CALLBACK_HEADER,
83 ListEntry);
84
85 /* free memory */
86 ExFreePool(Mem);
87 }
88 }
89
90
91 //
92 // Pass the Current Window handle and pointer to the Client Callback.
93 // This will help user space programs speed up read access with the window object.
94 //
95 static VOID
96 IntSetTebWndCallback (HWND * hWnd, PWND * pWnd)
97 {
98 HWND hWndS = *hWnd;
99 PWND Window = UserGetWindowObject(*hWnd);
100 PCLIENTINFO ClientInfo = GetWin32ClientInfo();
101
102 *hWnd = ClientInfo->CallbackWnd.hWnd;
103 *pWnd = ClientInfo->CallbackWnd.pWnd;
104
105 ClientInfo->CallbackWnd.hWnd = hWndS;
106 ClientInfo->CallbackWnd.pWnd = DesktopHeapAddressToUser(Window);
107 }
108
109 static VOID
110 IntRestoreTebWndCallback (HWND hWnd, PWND pWnd)
111 {
112 PCLIENTINFO ClientInfo = GetWin32ClientInfo();
113
114 ClientInfo->CallbackWnd.hWnd = hWnd;
115 ClientInfo->CallbackWnd.pWnd = pWnd;
116 }
117
118 /* FUNCTIONS *****************************************************************/
119
120 VOID APIENTRY
121 co_IntCallSentMessageCallback(SENDASYNCPROC CompletionCallback,
122 HWND hWnd,
123 UINT Msg,
124 ULONG_PTR CompletionCallbackContext,
125 LRESULT Result)
126 {
127 SENDASYNCPROC_CALLBACK_ARGUMENTS Arguments;
128 PVOID ResultPointer;
129 PWND pWnd;
130 ULONG ResultLength;
131 NTSTATUS Status;
132
133 Arguments.Callback = CompletionCallback;
134 Arguments.Wnd = hWnd;
135 Arguments.Msg = Msg;
136 Arguments.Context = CompletionCallbackContext;
137 Arguments.Result = Result;
138
139 IntSetTebWndCallback (&hWnd, &pWnd);
140
141 UserLeaveCo();
142
143 Status = KeUserModeCallback(USER32_CALLBACK_SENDASYNCPROC,
144 &Arguments,
145 sizeof(SENDASYNCPROC_CALLBACK_ARGUMENTS),
146 &ResultPointer,
147 &ResultLength);
148
149 UserEnterCo();
150
151 IntRestoreTebWndCallback (hWnd, pWnd);
152
153 if (!NT_SUCCESS(Status))
154 {
155 return;
156 }
157 return;
158 }
159
160 LRESULT APIENTRY
161 co_IntCallWindowProc(WNDPROC Proc,
162 BOOLEAN IsAnsiProc,
163 HWND Wnd,
164 UINT Message,
165 WPARAM wParam,
166 LPARAM lParam,
167 INT lParamBufferSize)
168 {
169 WINDOWPROC_CALLBACK_ARGUMENTS StackArguments;
170 PWINDOWPROC_CALLBACK_ARGUMENTS Arguments;
171 NTSTATUS Status;
172 PVOID ResultPointer;
173 PWND pWnd;
174 ULONG ResultLength;
175 ULONG ArgumentLength;
176 LRESULT Result;
177
178 if (0 < lParamBufferSize)
179 {
180 ArgumentLength = sizeof(WINDOWPROC_CALLBACK_ARGUMENTS) + lParamBufferSize;
181 Arguments = IntCbAllocateMemory(ArgumentLength);
182 if (NULL == Arguments)
183 {
184 DPRINT1("Unable to allocate buffer for window proc callback\n");
185 return -1;
186 }
187 RtlMoveMemory((PVOID) ((char *) Arguments + sizeof(WINDOWPROC_CALLBACK_ARGUMENTS)),
188 (PVOID) lParam, lParamBufferSize);
189 }
190 else
191 {
192 Arguments = &StackArguments;
193 ArgumentLength = sizeof(WINDOWPROC_CALLBACK_ARGUMENTS);
194 }
195 Arguments->Proc = Proc;
196 Arguments->IsAnsiProc = IsAnsiProc;
197 Arguments->Wnd = Wnd;
198 Arguments->Msg = Message;
199 Arguments->wParam = wParam;
200 Arguments->lParam = lParam;
201 Arguments->lParamBufferSize = lParamBufferSize;
202 ResultPointer = NULL;
203 ResultLength = ArgumentLength;
204
205 IntSetTebWndCallback (&Wnd, &pWnd);
206
207 UserLeaveCo();
208
209 Status = KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
210 Arguments,
211 ArgumentLength,
212 &ResultPointer,
213 &ResultLength);
214
215 _SEH2_TRY
216 {
217 /* Simulate old behaviour: copy into our local buffer */
218 RtlMoveMemory(Arguments, ResultPointer, ArgumentLength);
219 }
220 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
221 {
222 Status = _SEH2_GetExceptionCode();
223 }
224 _SEH2_END;
225
226 UserEnterCo();
227
228 IntRestoreTebWndCallback (Wnd, pWnd);
229
230 if (!NT_SUCCESS(Status))
231 {
232 if (0 < lParamBufferSize)
233 {
234 IntCbFreeMemory(Arguments);
235 }
236 return -1;
237 }
238 Result = Arguments->Result;
239
240 if (0 < lParamBufferSize)
241 {
242 RtlMoveMemory((PVOID) lParam,
243 (PVOID) ((char *) Arguments + sizeof(WINDOWPROC_CALLBACK_ARGUMENTS)),
244 lParamBufferSize);
245 IntCbFreeMemory(Arguments);
246 }
247
248 return Result;
249 }
250
251 HMENU APIENTRY
252 co_IntLoadSysMenuTemplate()
253 {
254 LRESULT Result = 0;
255 NTSTATUS Status;
256 PVOID ResultPointer;
257 ULONG ResultLength;
258
259 ResultPointer = NULL;
260 ResultLength = sizeof(LRESULT);
261
262 UserLeaveCo();
263
264 Status = KeUserModeCallback(USER32_CALLBACK_LOADSYSMENUTEMPLATE,
265 NULL,
266 0,
267 &ResultPointer,
268 &ResultLength);
269 if (NT_SUCCESS(Status))
270 {
271 /* Simulate old behaviour: copy into our local buffer */
272 _SEH2_TRY
273 {
274 ProbeForRead(ResultPointer, sizeof(LRESULT), 1);
275 Result = *(LRESULT*)ResultPointer;
276 }
277 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
278 {
279 Result = 0;
280 }
281 _SEH2_END
282 }
283
284 UserEnterCo();
285
286 return (HMENU)Result;
287 }
288
289 BOOL APIENTRY
290 co_IntLoadDefaultCursors(VOID)
291 {
292 NTSTATUS Status;
293 PVOID ResultPointer;
294 ULONG ResultLength;
295 BOOL DefaultCursor = TRUE;
296
297 ResultPointer = NULL;
298 ResultLength = sizeof(LRESULT);
299
300 UserLeaveCo();
301
302 Status = KeUserModeCallback(USER32_CALLBACK_LOADDEFAULTCURSORS,
303 &DefaultCursor,
304 sizeof(BOOL),
305 &ResultPointer,
306 &ResultLength);
307
308 UserEnterCo();
309
310 if (!NT_SUCCESS(Status))
311 {
312 return FALSE;
313 }
314 return TRUE;
315 }
316
317 LRESULT APIENTRY
318 co_IntCallHookProc(INT HookId,
319 INT Code,
320 WPARAM wParam,
321 LPARAM lParam,
322 HOOKPROC Proc,
323 BOOLEAN Ansi,
324 PUNICODE_STRING ModuleName)
325 {
326 ULONG ArgumentLength;
327 PVOID Argument = NULL;
328 LRESULT Result = 0;
329 NTSTATUS Status;
330 PVOID ResultPointer;
331 ULONG ResultLength;
332 PHOOKPROC_CALLBACK_ARGUMENTS Common;
333 CBT_CREATEWNDW *CbtCreateWnd = NULL;
334 PCHAR Extra;
335 PHOOKPROC_CBT_CREATEWND_EXTRA_ARGUMENTS CbtCreatewndExtra = NULL;
336 PTHREADINFO pti;
337 PWND pWnd;
338 BOOL Hit = FALSE;
339
340 ASSERT(Proc);
341
342 pti = PsGetCurrentThreadWin32Thread();
343 if (pti->TIF_flags & TIF_INCLEANUP)
344 {
345 DPRINT1("Thread is in cleanup and trying to call hook %d\n", Code);
346 return 0;
347 }
348
349 ArgumentLength = sizeof(HOOKPROC_CALLBACK_ARGUMENTS) - sizeof(WCHAR)
350 + ModuleName->Length;
351 switch(HookId)
352 {
353 case WH_CBT:
354 switch(Code)
355 {
356 case HCBT_CREATEWND:
357 pWnd = UserGetWindowObject((HWND) wParam);
358 if (!pWnd)
359 {
360 DPRINT1("WH_CBT HCBT_CREATEWND wParam bad hWnd!\n");
361 goto Fault_Exit;
362 }
363 // Due to KsStudio.exe, just pass the callers original pointers
364 // except class which point to kernel space if not an atom.
365 // Found by, Olaf Siejka
366 CbtCreateWnd = (CBT_CREATEWNDW *) lParam;
367 ArgumentLength += sizeof(HOOKPROC_CBT_CREATEWND_EXTRA_ARGUMENTS);
368 break;
369
370 case HCBT_MOVESIZE:
371 ArgumentLength += sizeof(RECTL);
372 break;
373 case HCBT_ACTIVATE:
374 ArgumentLength += sizeof(CBTACTIVATESTRUCT);
375 break;
376 case HCBT_CLICKSKIPPED:
377 ArgumentLength += sizeof(MOUSEHOOKSTRUCT);
378 break;
379 /* ATM pass on */
380 case HCBT_KEYSKIPPED:
381 case HCBT_MINMAX:
382 case HCBT_SETFOCUS:
383 case HCBT_SYSCOMMAND:
384 /* These types pass through. */
385 case HCBT_DESTROYWND:
386 case HCBT_QS:
387 break;
388 default:
389 DPRINT1("Trying to call unsupported CBT hook %d\n", Code);
390 goto Fault_Exit;
391 }
392 break;
393 case WH_KEYBOARD_LL:
394 ArgumentLength += sizeof(KBDLLHOOKSTRUCT);
395 break;
396 case WH_MOUSE_LL:
397 ArgumentLength += sizeof(MSLLHOOKSTRUCT);
398 break;
399 case WH_MOUSE:
400 ArgumentLength += sizeof(MOUSEHOOKSTRUCT);
401 break;
402 case WH_CALLWNDPROC:
403 ArgumentLength += sizeof(CWPSTRUCT);
404 break;
405 case WH_CALLWNDPROCRET:
406 ArgumentLength += sizeof(CWPRETSTRUCT);
407 break;
408 case WH_MSGFILTER:
409 case WH_SYSMSGFILTER:
410 case WH_GETMESSAGE:
411 ArgumentLength += sizeof(MSG);
412 break;
413 case WH_FOREGROUNDIDLE:
414 case WH_KEYBOARD:
415 case WH_SHELL:
416 break;
417 default:
418 DPRINT1("Trying to call unsupported window hook %d\n", HookId);
419 goto Fault_Exit;
420 }
421
422 Argument = IntCbAllocateMemory(ArgumentLength);
423 if (NULL == Argument)
424 {
425 DPRINT1("HookProc callback failed: out of memory\n");
426 goto Fault_Exit;
427 }
428 Common = (PHOOKPROC_CALLBACK_ARGUMENTS) Argument;
429 Common->HookId = HookId;
430 Common->Code = Code;
431 Common->wParam = wParam;
432 Common->lParam = lParam;
433 Common->Proc = Proc;
434 Common->Ansi = Ansi;
435 Common->ModuleNameLength = ModuleName->Length;
436 if (ModuleName->Buffer)
437 RtlCopyMemory(Common->ModuleName, ModuleName->Buffer, ModuleName->Length);
438 Extra = (PCHAR) Common->ModuleName + Common->ModuleNameLength;
439
440 switch(HookId)
441 {
442 case WH_CBT:
443 switch(Code)
444 {
445 case HCBT_CREATEWND:
446 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
447 CbtCreatewndExtra = (PHOOKPROC_CBT_CREATEWND_EXTRA_ARGUMENTS) Extra;
448 RtlCopyMemory( &CbtCreatewndExtra->Cs, CbtCreateWnd->lpcs, sizeof(CREATESTRUCTW) );
449 CbtCreatewndExtra->WndInsertAfter = CbtCreateWnd->hwndInsertAfter;
450 CbtCreatewndExtra->Cs.lpszClass = CbtCreateWnd->lpcs->lpszClass; // if Atom
451 CbtCreatewndExtra->Cs.lpszName = CbtCreateWnd->lpcs->lpszName;
452 Extra = (PCHAR) (CbtCreatewndExtra + 1);
453 break;
454 case HCBT_CLICKSKIPPED:
455 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(MOUSEHOOKSTRUCT));
456 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
457 break;
458 case HCBT_MOVESIZE:
459 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(RECTL));
460 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
461 break;
462 case HCBT_ACTIVATE:
463 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(CBTACTIVATESTRUCT));
464 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
465 break;
466 }
467 break;
468 case WH_KEYBOARD_LL:
469 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(KBDLLHOOKSTRUCT));
470 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
471 break;
472 case WH_MOUSE_LL:
473 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(MSLLHOOKSTRUCT));
474 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
475 break;
476 case WH_MOUSE:
477 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(MOUSEHOOKSTRUCT));
478 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
479 break;
480 case WH_CALLWNDPROC:
481 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(CWPSTRUCT));
482 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
483 break;
484 case WH_CALLWNDPROCRET:
485 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(CWPRETSTRUCT));
486 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
487 break;
488 case WH_MSGFILTER:
489 case WH_SYSMSGFILTER:
490 case WH_GETMESSAGE:
491 RtlCopyMemory(Extra, (PVOID) lParam, sizeof(MSG));
492 Common->lParam = (LPARAM) (Extra - (PCHAR) Common);
493 break;
494 case WH_FOREGROUNDIDLE:
495 case WH_KEYBOARD:
496 case WH_SHELL:
497 break;
498 }
499
500 ResultPointer = NULL;
501 ResultLength = sizeof(LRESULT);
502
503 UserLeaveCo();
504
505 Status = KeUserModeCallback(USER32_CALLBACK_HOOKPROC,
506 Argument,
507 ArgumentLength,
508 &ResultPointer,
509 &ResultLength);
510
511 UserEnterCo();
512
513 _SEH2_TRY
514 {
515 ProbeForRead(ResultPointer, sizeof(LRESULT), 1);
516 /* Simulate old behaviour: copy into our local buffer */
517 Result = *(LRESULT*)ResultPointer;
518 }
519 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
520 {
521 Result = 0;
522 Hit = TRUE;
523 }
524 _SEH2_END;
525
526 if (!NT_SUCCESS(Status))
527 {
528 goto Fault_Exit;
529 }
530 /* Support write backs... SEH is in UserCallNextHookEx. */
531 switch (HookId)
532 {
533 case WH_CBT:
534 if (Code == HCBT_CREATEWND)
535 {
536 if (CbtCreatewndExtra)
537 {/*
538 The parameters could have been changed, include the coordinates
539 and dimensions of the window. We copy it back.
540 */
541 CbtCreateWnd->hwndInsertAfter = CbtCreatewndExtra->WndInsertAfter;
542 CbtCreateWnd->lpcs->x = CbtCreatewndExtra->Cs.x;
543 CbtCreateWnd->lpcs->y = CbtCreatewndExtra->Cs.y;
544 CbtCreateWnd->lpcs->cx = CbtCreatewndExtra->Cs.cx;
545 CbtCreateWnd->lpcs->cy = CbtCreatewndExtra->Cs.cy;
546 }
547 }
548 break;
549 // "The GetMsgProc hook procedure can examine or modify the message."
550 case WH_GETMESSAGE:
551 if (lParam)
552 {
553 RtlCopyMemory((PVOID) lParam, Extra, sizeof(MSG));
554 }
555 break;
556 }
557
558 Fault_Exit:
559 if (Hit)
560 {
561 DPRINT1("Exception CallHookProc HookId %d Code %d\n",HookId,Code);
562 }
563 if (Argument) IntCbFreeMemory(Argument);
564
565 return Result;
566 }
567
568 //
569 // Events are notifications w/o results.
570 //
571 LRESULT
572 APIENTRY
573 co_IntCallEventProc(HWINEVENTHOOK hook,
574 DWORD event,
575 HWND hWnd,
576 LONG idObject,
577 LONG idChild,
578 DWORD dwEventThread,
579 DWORD dwmsEventTime,
580 WINEVENTPROC Proc)
581 {
582 LRESULT Result = 0;
583 NTSTATUS Status;
584 PEVENTPROC_CALLBACK_ARGUMENTS Common;
585 ULONG ArgumentLength, ResultLength;
586 PVOID Argument, ResultPointer;
587
588 ArgumentLength = sizeof(EVENTPROC_CALLBACK_ARGUMENTS);
589
590 Argument = IntCbAllocateMemory(ArgumentLength);
591 if (NULL == Argument)
592 {
593 DPRINT1("EventProc callback failed: out of memory\n");
594 return 0;
595 }
596 Common = (PEVENTPROC_CALLBACK_ARGUMENTS) Argument;
597 Common->hook = hook;
598 Common->event = event;
599 Common->hwnd = hWnd;
600 Common->idObject = idObject;
601 Common->idChild = idChild;
602 Common->dwEventThread = dwEventThread;
603 Common->dwmsEventTime = dwmsEventTime;
604 Common->Proc = Proc;
605
606 ResultPointer = NULL;
607 ResultLength = sizeof(LRESULT);
608
609 UserLeaveCo();
610
611 Status = KeUserModeCallback(USER32_CALLBACK_EVENTPROC,
612 Argument,
613 ArgumentLength,
614 &ResultPointer,
615 &ResultLength);
616
617 UserEnterCo();
618
619 IntCbFreeMemory(Argument);
620
621 if (!NT_SUCCESS(Status))
622 {
623 return 0;
624 }
625
626 return Result;
627 }
628
629 //
630 // Callback Load Menu and results.
631 //
632 HMENU
633 APIENTRY
634 co_IntCallLoadMenu( HINSTANCE hModule,
635 PUNICODE_STRING pMenuName )
636 {
637 LRESULT Result = 0;
638 NTSTATUS Status;
639 PLOADMENU_CALLBACK_ARGUMENTS Common;
640 ULONG ArgumentLength, ResultLength;
641 PVOID Argument, ResultPointer;
642
643 ArgumentLength = sizeof(LOADMENU_CALLBACK_ARGUMENTS);
644
645 ArgumentLength += pMenuName->Length + sizeof(WCHAR);
646
647 Argument = IntCbAllocateMemory(ArgumentLength);
648 if (NULL == Argument)
649 {
650 DPRINT1("LoadMenu callback failed: out of memory\n");
651 return 0;
652 }
653 Common = (PLOADMENU_CALLBACK_ARGUMENTS) Argument;
654
655 // Help Intersource check and MenuName is now 4 bytes + so zero it.
656 RtlZeroMemory(Common, ArgumentLength);
657
658 Common->hModule = hModule;
659 if (pMenuName->Length)
660 RtlCopyMemory(&Common->MenuName, pMenuName->Buffer, pMenuName->Length);
661 else
662 RtlCopyMemory(&Common->MenuName, &pMenuName->Buffer, sizeof(WCHAR));
663
664 ResultPointer = NULL;
665 ResultLength = sizeof(LRESULT);
666
667 UserLeaveCo();
668
669 Status = KeUserModeCallback(USER32_CALLBACK_LOADMENU,
670 Argument,
671 ArgumentLength,
672 &ResultPointer,
673 &ResultLength);
674
675 UserEnterCo();
676
677 Result = *(LRESULT*)ResultPointer;
678
679 IntCbFreeMemory(Argument);
680
681 if (!NT_SUCCESS(Status))
682 {
683 return 0;
684 }
685
686 return (HMENU)Result;
687 }
688
689 NTSTATUS
690 APIENTRY
691 co_IntClientThreadSetup(VOID)
692 {
693 NTSTATUS Status;
694 ULONG ArgumentLength, ResultLength;
695 PVOID Argument, ResultPointer;
696
697 ArgumentLength = ResultLength = 0;
698 Argument = ResultPointer = NULL;
699
700 UserLeaveCo();
701
702 Status = KeUserModeCallback(USER32_CALLBACK_CLIENTTHREADSTARTUP,
703 Argument,
704 ArgumentLength,
705 &ResultPointer,
706 &ResultLength);
707
708 UserEnterCo();
709
710 return Status;
711 }
712
713
714 /* EOF */