vcomp: Better stub for omp_get_max_threads.
[wine] / dlls / krnl386.exe16 / wowthunk.c
1 /*
2  * Win32 WOW Generic Thunk API
3  *
4  * Copyright 1999 Ulrich Weigand
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <assert.h>
25 #include <stdarg.h>
26 #include <errno.h>
27
28 #include "wine/winbase16.h"
29 #include "windef.h"
30 #include "winbase.h"
31 #include "winerror.h"
32 #include "wownt32.h"
33 #include "excpt.h"
34 #include "winternl.h"
35 #include "kernel16_private.h"
36 #include "wine/exception.h"
37 #include "wine/debug.h"
38
39 WINE_DEFAULT_DEBUG_CHANNEL(thunk);
40 WINE_DECLARE_DEBUG_CHANNEL(relay);
41 WINE_DECLARE_DEBUG_CHANNEL(snoop);
42
43 /* symbols exported from relay16.s */
44 extern DWORD WINAPI wine_call_to_16( FARPROC16 target, DWORD cbArgs, PEXCEPTION_HANDLER handler );
45 extern void WINAPI wine_call_to_16_regs( CONTEXT *context, DWORD cbArgs, PEXCEPTION_HANDLER handler );
46 extern void __wine_call_to_16_ret(void);
47 extern void CALL32_CBClient_Ret(void);
48 extern void CALL32_CBClientEx_Ret(void);
49 extern void DPMI_PendingEventCheck(void);
50 extern void DPMI_PendingEventCheck_Cleanup(void);
51 extern void DPMI_PendingEventCheck_Return(void);
52 extern BYTE __wine_call16_start[];
53 extern BYTE __wine_call16_end[];
54
55 extern void RELAY16_InitDebugLists(void);
56
57 static SEGPTR call16_ret_addr;  /* segptr to __wine_call_to_16_ret routine */
58
59 static WORD  dpmi_checker_selector;
60 static DWORD dpmi_checker_offset_call;
61 static DWORD dpmi_checker_offset_cleanup;
62 static DWORD dpmi_checker_offset_return;
63
64 /***********************************************************************
65  *           WOWTHUNK_Init
66  */
67 BOOL WOWTHUNK_Init(void)
68 {
69     /* allocate the code selector for CallTo16 routines */
70     LDT_ENTRY entry;
71     WORD codesel = wine_ldt_alloc_entries(1);
72
73     if (!codesel) return FALSE;
74     wine_ldt_set_base( &entry, __wine_call16_start );
75     wine_ldt_set_limit( &entry, (BYTE *)(&CallTo16_TebSelector + 1) - __wine_call16_start - 1 );
76     wine_ldt_set_flags( &entry, WINE_LDT_FLAGS_CODE | WINE_LDT_FLAGS_32BIT );
77     wine_ldt_set_entry( codesel, &entry );
78
79       /* Patch the return addresses for CallTo16 routines */
80
81     CallTo16_DataSelector = wine_get_ds();
82     call16_ret_addr = MAKESEGPTR( codesel, (BYTE *)__wine_call_to_16_ret - __wine_call16_start );
83     CALL32_CBClient_RetAddr =
84         MAKESEGPTR( codesel, (BYTE *)CALL32_CBClient_Ret - __wine_call16_start );
85     CALL32_CBClientEx_RetAddr =
86         MAKESEGPTR( codesel, (BYTE *)CALL32_CBClientEx_Ret - __wine_call16_start );
87
88     /* Prepare selector and offsets for DPMI event checking. */
89     dpmi_checker_selector = codesel;
90     dpmi_checker_offset_call = (BYTE *)DPMI_PendingEventCheck - __wine_call16_start;
91     dpmi_checker_offset_cleanup = (BYTE *)DPMI_PendingEventCheck_Cleanup - __wine_call16_start;
92     dpmi_checker_offset_return = (BYTE *)DPMI_PendingEventCheck_Return - __wine_call16_start;
93
94     if (TRACE_ON(relay) || TRACE_ON(snoop)) RELAY16_InitDebugLists();
95
96     return TRUE;
97 }
98
99
100 /*************************************************************
101  *            fix_selector
102  *
103  * Fix a selector load that caused an exception if it's in the
104  * 16-bit relay code.
105  */
106 static BOOL fix_selector( CONTEXT *context )
107 {
108     WORD *stack;
109     BYTE *instr = (BYTE *)context->Eip;
110
111     if (instr < __wine_call16_start || instr >= __wine_call16_end) return FALSE;
112
113     /* skip prefixes */
114     while (*instr == 0x66 || *instr == 0x67) instr++;
115
116     switch(instr[0])
117     {
118     case 0x07: /* pop es */
119     case 0x17: /* pop ss */
120     case 0x1f: /* pop ds */
121         break;
122     case 0x0f: /* extended instruction */
123         switch(instr[1])
124         {
125         case 0xa1: /* pop fs */
126         case 0xa9: /* pop gs */
127             break;
128         default:
129             return FALSE;
130         }
131         break;
132     default:
133         return FALSE;
134     }
135     stack = wine_ldt_get_ptr( context->SegSs, context->Esp );
136     TRACE( "fixing up selector %x for pop instruction\n", *stack );
137     *stack = 0;
138     return TRUE;
139 }
140
141
142 /*************************************************************
143  *            insert_event_check
144  *
145  * Make resuming the context check for pending DPMI events
146  * before the original context is restored. This is required
147  * because DPMI events are asynchronous, they are blocked while 
148  * Wine 32-bit code is being executed and we want to prevent 
149  * a race when returning back to 16-bit or 32-bit DPMI context.
150  */
151 static void insert_event_check( CONTEXT *context )
152 {
153     char *stack = wine_ldt_get_ptr( context->SegSs, context->Esp );
154
155     /* don't do event check while in system code */
156     if (wine_ldt_is_system(context->SegCs))
157         return;
158
159     if(context->SegCs == dpmi_checker_selector &&
160        context->Eip   >= dpmi_checker_offset_call && 
161        context->Eip   <= dpmi_checker_offset_cleanup)
162     {
163         /*
164          * Nested call. Stack will be preserved. 
165          */
166     }
167     else if(context->SegCs == dpmi_checker_selector &&
168             context->Eip   == dpmi_checker_offset_return)
169     {
170         /*
171          * Nested call. We have just finished popping the fs
172          * register, lets put it back into stack.
173          */
174
175         stack -= sizeof(WORD);
176         *(WORD*)stack = context->SegFs;
177
178         context->Esp -= 2;
179     }
180     else
181     {
182         /*
183          * Call is not nested.
184          * Push modified registers into stack.
185          * These will be popped by the assembler stub.
186          */
187
188         stack -= sizeof(DWORD);
189         *(DWORD*)stack = context->EFlags;
190    
191         stack -= sizeof(DWORD);
192         *(DWORD*)stack = context->SegCs;
193
194         stack -= sizeof(DWORD);
195         *(DWORD*)stack = context->Eip;
196
197         stack -= sizeof(WORD);
198         *(WORD*)stack = context->SegFs;
199
200         context->Esp  -= 14;
201     }
202
203     /*
204      * Modify the context so that we jump into assembler stub.
205      * TEB access is made easier by providing the stub
206      * with the correct fs register value.
207      */
208
209     context->SegCs = dpmi_checker_selector;
210     context->Eip   = dpmi_checker_offset_call;
211     context->SegFs = wine_get_fs();
212 }
213
214
215 /*************************************************************
216  *            call16_handler
217  *
218  * Handler for exceptions occurring in 16-bit code.
219  */
220 static DWORD call16_handler( EXCEPTION_RECORD *record, EXCEPTION_REGISTRATION_RECORD *frame,
221                              CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **pdispatcher )
222 {
223     if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND))
224     {
225         /* unwinding: restore the stack pointer in the TEB, and leave the Win16 mutex */
226         STACK32FRAME *frame32 = CONTAINING_RECORD(frame, STACK32FRAME, frame);
227         NtCurrentTeb()->WOW32Reserved = (void *)frame32->frame16;
228         _LeaveWin16Lock();
229     }
230     else if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
231              record->ExceptionCode == EXCEPTION_PRIV_INSTRUCTION)
232     {
233         if (wine_ldt_is_system(context->SegCs))
234         {
235             if (fix_selector( context )) return ExceptionContinueExecution;
236         }
237         else
238         {
239             SEGPTR gpHandler;
240             DWORD ret = __wine_emulate_instruction( record, context );
241
242             /*
243              * Insert check for pending DPMI events. Note that this 
244              * check must be inserted after instructions have been 
245              * emulated because the instruction emulation requires
246              * original CS:IP and the emulation may change TEB.dpmi_vif.
247              */
248             if(get_vm86_teb_info()->dpmi_vif)
249                 insert_event_check( context );
250
251             if (ret != ExceptionContinueSearch) return ret;
252
253             /* check for Win16 __GP handler */
254             if ((gpHandler = HasGPHandler16( MAKESEGPTR( context->SegCs, context->Eip ) )))
255             {
256                 WORD *stack = wine_ldt_get_ptr( context->SegSs, context->Esp );
257                 *--stack = context->SegCs;
258                 *--stack = context->Eip;
259
260                 if (!IS_SELECTOR_32BIT(context->SegSs))
261                     context->Esp = MAKELONG( LOWORD(context->Esp - 2*sizeof(WORD)),
262                                              HIWORD(context->Esp) );
263                 else
264                     context->Esp -= 2*sizeof(WORD);
265
266                 context->SegCs = SELECTOROF( gpHandler );
267                 context->Eip   = OFFSETOF( gpHandler );
268                 return ExceptionContinueExecution;
269             }
270         }
271     }
272     else if (record->ExceptionCode == EXCEPTION_VM86_STI)
273     {
274         insert_event_check( context );
275     }
276     return ExceptionContinueSearch;
277 }
278
279
280 /*************************************************************
281  *            vm86_handler
282  *
283  * Handler for exceptions occurring in vm86 code.
284  */
285 static DWORD vm86_handler( EXCEPTION_RECORD *record, EXCEPTION_REGISTRATION_RECORD *frame,
286                            CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **pdispatcher )
287 {
288     if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND))
289         return ExceptionContinueSearch;
290
291     if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
292         record->ExceptionCode == EXCEPTION_PRIV_INSTRUCTION)
293     {
294         return __wine_emulate_instruction( record, context );
295     }
296
297     return ExceptionContinueSearch;
298 }
299
300
301 /*
302  *  32-bit WOW routines (in WOW32, but actually forwarded to KERNEL32)
303  */
304
305 /**********************************************************************
306  *           K32WOWGetDescriptor        (KERNEL32.70)
307  */
308 BOOL WINAPI K32WOWGetDescriptor( SEGPTR segptr, LPLDT_ENTRY ldtent )
309 {
310     return GetThreadSelectorEntry( GetCurrentThread(),
311                                    segptr >> 16, ldtent );
312 }
313
314 /**********************************************************************
315  *           K32WOWGetVDMPointer        (KERNEL32.56)
316  */
317 LPVOID WINAPI K32WOWGetVDMPointer( DWORD vp, DWORD dwBytes, BOOL fProtectedMode )
318 {
319     /* FIXME: add size check too */
320
321     if ( fProtectedMode )
322         return MapSL( vp );
323     else
324         return DOSMEM_MapRealToLinear( vp );
325 }
326
327 /**********************************************************************
328  *           K32WOWGetVDMPointerFix     (KERNEL32.68)
329  */
330 LPVOID WINAPI K32WOWGetVDMPointerFix( DWORD vp, DWORD dwBytes, BOOL fProtectedMode )
331 {
332     /*
333      * Hmmm. According to the docu, we should call:
334      *
335      *          GlobalFix16( SELECTOROF(vp) );
336      *
337      * But this is unnecessary under Wine, as we never move global
338      * memory segments in linear memory anyway.
339      *
340      * (I'm not so sure what we are *supposed* to do if
341      *  fProtectedMode is TRUE, anyway ...)
342      */
343
344     return K32WOWGetVDMPointer( vp, dwBytes, fProtectedMode );
345 }
346
347 /**********************************************************************
348  *           K32WOWGetVDMPointerUnfix   (KERNEL32.69)
349  */
350 VOID WINAPI K32WOWGetVDMPointerUnfix( DWORD vp )
351 {
352     /*
353      * See above why we don't call:
354      *
355      * GlobalUnfix16( SELECTOROF(vp) );
356      *
357      */
358 }
359
360 /**********************************************************************
361  *           K32WOWGlobalAlloc16        (KERNEL32.59)
362  */
363 WORD WINAPI K32WOWGlobalAlloc16( WORD wFlags, DWORD cb )
364 {
365     return (WORD)GlobalAlloc16( wFlags, cb );
366 }
367
368 /**********************************************************************
369  *           K32WOWGlobalFree16         (KERNEL32.62)
370  */
371 WORD WINAPI K32WOWGlobalFree16( WORD hMem )
372 {
373     return (WORD)GlobalFree16( (HGLOBAL16)hMem );
374 }
375
376 /**********************************************************************
377  *           K32WOWGlobalUnlock16       (KERNEL32.61)
378  */
379 BOOL WINAPI K32WOWGlobalUnlock16( WORD hMem )
380 {
381     return (BOOL)GlobalUnlock16( (HGLOBAL16)hMem );
382 }
383
384 /**********************************************************************
385  *           K32WOWGlobalAllocLock16    (KERNEL32.63)
386  */
387 DWORD WINAPI K32WOWGlobalAllocLock16( WORD wFlags, DWORD cb, WORD *phMem )
388 {
389     WORD hMem = K32WOWGlobalAlloc16( wFlags, cb );
390     if (phMem) *phMem = hMem;
391
392     return K32WOWGlobalLock16( hMem );
393 }
394
395 /**********************************************************************
396  *           K32WOWGlobalLockSize16     (KERNEL32.65)
397  */
398 DWORD WINAPI K32WOWGlobalLockSize16( WORD hMem, PDWORD pcb )
399 {
400     if ( pcb )
401         *pcb = GlobalSize16( (HGLOBAL16)hMem );
402
403     return K32WOWGlobalLock16( hMem );
404 }
405
406 /**********************************************************************
407  *           K32WOWGlobalUnlockFree16   (KERNEL32.64)
408  */
409 WORD WINAPI K32WOWGlobalUnlockFree16( DWORD vpMem )
410 {
411     if ( !K32WOWGlobalUnlock16( HIWORD(vpMem) ) )
412         return FALSE;
413
414     return K32WOWGlobalFree16( HIWORD(vpMem) );
415 }
416
417
418 /**********************************************************************
419  *           K32WOWYield16              (KERNEL32.66)
420  */
421 VOID WINAPI K32WOWYield16( void )
422 {
423     /*
424      * This does the right thing for both Win16 and Win32 tasks.
425      * More or less, at least :-/
426      */
427     Yield16();
428 }
429
430 /**********************************************************************
431  *           K32WOWDirectedYield16       (KERNEL32.67)
432  */
433 VOID WINAPI K32WOWDirectedYield16( WORD htask16 )
434 {
435     /*
436      * Argh.  Our scheduler doesn't like DirectedYield by Win32
437      * tasks at all.  So we do hope that this routine is indeed
438      * only ever called by Win16 tasks that have thunked up ...
439      */
440     DirectedYield16( (HTASK16)htask16 );
441 }
442
443
444 /***********************************************************************
445  *           K32WOWHandle32              (KERNEL32.57)
446  */
447 HANDLE WINAPI K32WOWHandle32( WORD handle, WOW_HANDLE_TYPE type )
448 {
449     switch ( type )
450     {
451     case WOW_TYPE_HWND:
452     case WOW_TYPE_HMENU:
453     case WOW_TYPE_HDWP:
454     case WOW_TYPE_HDROP:
455     case WOW_TYPE_HDC:
456     case WOW_TYPE_HFONT:
457     case WOW_TYPE_HRGN:
458     case WOW_TYPE_HBITMAP:
459     case WOW_TYPE_HBRUSH:
460     case WOW_TYPE_HPALETTE:
461     case WOW_TYPE_HPEN:
462     case WOW_TYPE_HACCEL:
463         return (HANDLE)(ULONG_PTR)handle;
464
465     case WOW_TYPE_HMETAFILE:
466         FIXME( "conversion of metafile handles not supported yet\n" );
467         return (HANDLE)(ULONG_PTR)handle;
468
469     case WOW_TYPE_HTASK:
470         return ((TDB *)GlobalLock16(handle))->teb->ClientId.UniqueThread;
471
472     case WOW_TYPE_FULLHWND:
473         FIXME( "conversion of full window handles not supported yet\n" );
474         return (HANDLE)(ULONG_PTR)handle;
475
476     default:
477         ERR( "handle 0x%04x of unknown type %d\n", handle, type );
478         return (HANDLE)(ULONG_PTR)handle;
479     }
480 }
481
482 /***********************************************************************
483  *           K32WOWHandle16              (KERNEL32.58)
484  */
485 WORD WINAPI K32WOWHandle16( HANDLE handle, WOW_HANDLE_TYPE type )
486 {
487     switch ( type )
488     {
489     case WOW_TYPE_HWND:
490     case WOW_TYPE_HMENU:
491     case WOW_TYPE_HDWP:
492     case WOW_TYPE_HDROP:
493     case WOW_TYPE_HDC:
494     case WOW_TYPE_HFONT:
495     case WOW_TYPE_HRGN:
496     case WOW_TYPE_HBITMAP:
497     case WOW_TYPE_HBRUSH:
498     case WOW_TYPE_HPALETTE:
499     case WOW_TYPE_HPEN:
500     case WOW_TYPE_HACCEL:
501     case WOW_TYPE_FULLHWND:
502         if ( HIWORD(handle ) )
503                 ERR( "handle %p of type %d has non-zero HIWORD\n", handle, type );
504         return LOWORD(handle);
505
506     case WOW_TYPE_HMETAFILE:
507         FIXME( "conversion of metafile handles not supported yet\n" );
508         return LOWORD(handle);
509
510     case WOW_TYPE_HTASK:
511         return TASK_GetTaskFromThread( (DWORD)handle );
512
513     default:
514         ERR( "handle %p of unknown type %d\n", handle, type );
515         return LOWORD(handle);
516     }
517 }
518
519 /**********************************************************************
520  *           K32WOWCallback16Ex         (KERNEL32.55)
521  */
522 BOOL WINAPI K32WOWCallback16Ex( DWORD vpfn16, DWORD dwFlags,
523                                 DWORD cbArgs, LPVOID pArgs, LPDWORD pdwRetCode )
524 {
525     /*
526      * Arguments must be prepared in the correct order by the caller
527      * (both for PASCAL and CDECL calling convention), so we simply
528      * copy them to the 16-bit stack ...
529      */
530     char *stack = (char *)CURRENT_STACK16 - cbArgs;
531
532     memcpy( stack, pArgs, cbArgs );
533
534     if (dwFlags & (WCB16_REGS|WCB16_REGS_LONG))
535     {
536         CONTEXT *context = (CONTEXT *)pdwRetCode;
537
538         if (TRACE_ON(relay))
539         {
540             DWORD count = cbArgs / sizeof(WORD);
541             WORD * wstack = (WORD *)stack;
542
543             DPRINTF("%04x:CallTo16(func=%04x:%04x,ds=%04x",
544                     GetCurrentThreadId(),
545                     context->SegCs, LOWORD(context->Eip), context->SegDs );
546             while (count) DPRINTF( ",%04x", wstack[--count] );
547             DPRINTF(") ss:sp=%04x:%04x",
548                     SELECTOROF(NtCurrentTeb()->WOW32Reserved), OFFSETOF(NtCurrentTeb()->WOW32Reserved) );
549             DPRINTF(" ax=%04x bx=%04x cx=%04x dx=%04x si=%04x di=%04x bp=%04x es=%04x fs=%04x\n",
550                     (WORD)context->Eax, (WORD)context->Ebx, (WORD)context->Ecx,
551                     (WORD)context->Edx, (WORD)context->Esi, (WORD)context->Edi,
552                     (WORD)context->Ebp, (WORD)context->SegEs, (WORD)context->SegFs );
553             SYSLEVEL_CheckNotLevel( 2 );
554         }
555
556         if (context->EFlags & 0x00020000)  /* v86 mode */
557         {
558             EXCEPTION_REGISTRATION_RECORD frame;
559             frame.Handler = vm86_handler;
560             errno = 0;
561             __wine_push_frame( &frame );
562             __wine_enter_vm86( context );
563             __wine_pop_frame( &frame );
564             if (errno != 0)  /* enter_vm86 will fall with ENOSYS on x64 kernels */
565             {
566                 WARN("__wine_enter_vm86 failed (errno=%d)\n", errno);
567                 if (errno == ENOSYS)
568                     SetLastError(ERROR_NOT_SUPPORTED);
569                 else
570                     SetLastError(ERROR_GEN_FAILURE);
571                 return FALSE;
572             }
573         }
574         else
575         {
576             /* push return address */
577             if (dwFlags & WCB16_REGS_LONG)
578             {
579                 stack -= sizeof(DWORD);
580                 *((DWORD *)stack) = HIWORD(call16_ret_addr);
581                 stack -= sizeof(DWORD);
582                 *((DWORD *)stack) = LOWORD(call16_ret_addr);
583                 cbArgs += 2 * sizeof(DWORD);
584             }
585             else
586             {
587                 stack -= sizeof(SEGPTR);
588                 *((SEGPTR *)stack) = call16_ret_addr;
589                 cbArgs += sizeof(SEGPTR);
590             }
591
592             /*
593              * Start call by checking for pending events.
594              * Note that wine_call_to_16_regs overwrites context stack
595              * pointer so we may modify it here without a problem.
596              */
597             if (get_vm86_teb_info()->dpmi_vif)
598             {
599                 context->SegSs = wine_get_ds();
600                 context->Esp   = (DWORD)stack;
601                 insert_event_check( context );
602                 cbArgs += (DWORD)stack - context->Esp;
603             }
604
605             _EnterWin16Lock();
606             wine_call_to_16_regs( context, cbArgs, call16_handler );
607             _LeaveWin16Lock();
608         }
609
610         if (TRACE_ON(relay))
611         {
612             DPRINTF("%04x:RetFrom16() ss:sp=%04x:%04x ",
613                     GetCurrentThreadId(), SELECTOROF(NtCurrentTeb()->WOW32Reserved),
614                     OFFSETOF(NtCurrentTeb()->WOW32Reserved));
615             DPRINTF(" ax=%04x bx=%04x cx=%04x dx=%04x bp=%04x sp=%04x\n",
616                     (WORD)context->Eax, (WORD)context->Ebx, (WORD)context->Ecx,
617                     (WORD)context->Edx, (WORD)context->Ebp, (WORD)context->Esp );
618             SYSLEVEL_CheckNotLevel( 2 );
619         }
620     }
621     else
622     {
623         DWORD ret;
624
625         if (TRACE_ON(relay))
626         {
627             DWORD count = cbArgs / sizeof(WORD);
628             WORD * wstack = (WORD *)stack;
629
630             DPRINTF("%04x:CallTo16(func=%04x:%04x,ds=%04x",
631                     GetCurrentThreadId(), HIWORD(vpfn16), LOWORD(vpfn16),
632                     SELECTOROF(NtCurrentTeb()->WOW32Reserved) );
633             while (count) DPRINTF( ",%04x", wstack[--count] );
634             DPRINTF(") ss:sp=%04x:%04x\n",
635                     SELECTOROF(NtCurrentTeb()->WOW32Reserved), OFFSETOF(NtCurrentTeb()->WOW32Reserved) );
636             SYSLEVEL_CheckNotLevel( 2 );
637         }
638
639         /* push return address */
640         stack -= sizeof(SEGPTR);
641         *((SEGPTR *)stack) = call16_ret_addr;
642         cbArgs += sizeof(SEGPTR);
643
644         /*
645          * Actually, we should take care whether the called routine cleans up
646          * its stack or not.  Fortunately, our wine_call_to_16 core doesn't rely on
647          * the callee to do so; after the routine has returned, the 16-bit
648          * stack pointer is always reset to the position it had before.
649          */
650         _EnterWin16Lock();
651         ret = wine_call_to_16( (FARPROC16)vpfn16, cbArgs, call16_handler );
652         if (pdwRetCode) *pdwRetCode = ret;
653         _LeaveWin16Lock();
654
655         if (TRACE_ON(relay))
656         {
657             DPRINTF("%04x:RetFrom16() ss:sp=%04x:%04x retval=%08x\n",
658                     GetCurrentThreadId(), SELECTOROF(NtCurrentTeb()->WOW32Reserved),
659                     OFFSETOF(NtCurrentTeb()->WOW32Reserved), ret);
660             SYSLEVEL_CheckNotLevel( 2 );
661         }
662     }
663
664     return TRUE;  /* success */
665 }
666
667 /**********************************************************************
668  *           K32WOWCallback16            (KERNEL32.54)
669  */
670 DWORD WINAPI K32WOWCallback16( DWORD vpfn16, DWORD dwParam )
671 {
672     DWORD ret;
673
674     if ( !K32WOWCallback16Ex( vpfn16, WCB16_PASCAL,
675                            sizeof(DWORD), &dwParam, &ret ) )
676         ret = 0L;
677
678     return ret;
679 }