wowthunk.c 21.5 KB
Newer Older
1 2 3
/*
 * Win32 WOW Generic Thunk API
 *
4
 * Copyright 1999 Ulrich Weigand
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 22 23
#include "config.h"
#include "wine/port.h"

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 41 42
WINE_DECLARE_DEBUG_CHANNEL(relay);
WINE_DECLARE_DEBUG_CHANNEL(snoop);

43 44
/* symbols exported from relay16.s */
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 48 49 50 51
extern void CALL32_CBClient_Ret(void);
extern void CALL32_CBClientEx_Ret(void);
extern void DPMI_PendingEventCheck(void);
extern void DPMI_PendingEventCheck_Cleanup(void);
extern void DPMI_PendingEventCheck_Return(void);
52 53
extern BYTE __wine_call16_start[];
extern BYTE __wine_call16_end[];
54

55 56
extern void RELAY16_InitDebugLists(void);

57
static SEGPTR call16_ret_addr;  /* segptr to __wine_call_to_16_ret routine */
58

59 60 61 62 63
static WORD  dpmi_checker_selector;
static DWORD dpmi_checker_offset_call;
static DWORD dpmi_checker_offset_cleanup;
static DWORD dpmi_checker_offset_return;

64 65 66 67 68 69
/***********************************************************************
 *           WOWTHUNK_Init
 */
BOOL WOWTHUNK_Init(void)
{
    /* allocate the code selector for CallTo16 routines */
70 71 72
    LDT_ENTRY entry;
    WORD codesel = wine_ldt_alloc_entries(1);

73
    if (!codesel) return FALSE;
74 75 76 77
    wine_ldt_set_base( &entry, __wine_call16_start );
    wine_ldt_set_limit( &entry, (BYTE *)(&CallTo16_TebSelector + 1) - __wine_call16_start - 1 );
    wine_ldt_set_flags( &entry, WINE_LDT_FLAGS_CODE | WINE_LDT_FLAGS_32BIT );
    wine_ldt_set_entry( codesel, &entry );
78 79 80 81

      /* Patch the return addresses for CallTo16 routines */

    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 89
    /* Prepare selector and offsets for DPMI event checking. */
    dpmi_checker_selector = codesel;
90 91 92
    dpmi_checker_offset_call = (BYTE *)DPMI_PendingEventCheck - __wine_call16_start;
    dpmi_checker_offset_cleanup = (BYTE *)DPMI_PendingEventCheck_Cleanup - __wine_call16_start;
    dpmi_checker_offset_return = (BYTE *)DPMI_PendingEventCheck_Return - __wine_call16_start;
93

94
    if (TRACE_ON(relay) || TRACE_ON(snoop)) RELAY16_InitDebugLists();
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
    return TRUE;
}


/*************************************************************
 *            fix_selector
 *
 * Fix a selector load that caused an exception if it's in the
 * 16-bit relay code.
 */
static BOOL fix_selector( CONTEXT *context )
{
    WORD *stack;
    BYTE *instr = (BYTE *)context->Eip;

111
    if (instr < __wine_call16_start || instr >= __wine_call16_end) return FALSE;
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137

    /* skip prefixes */
    while (*instr == 0x66 || *instr == 0x67) instr++;

    switch(instr[0])
    {
    case 0x07: /* pop es */
    case 0x17: /* pop ss */
    case 0x1f: /* pop ds */
        break;
    case 0x0f: /* extended instruction */
        switch(instr[1])
        {
        case 0xa1: /* pop fs */
        case 0xa9: /* pop gs */
            break;
        default:
            return FALSE;
        }
        break;
    default:
        return FALSE;
    }
    stack = wine_ldt_get_ptr( context->SegSs, context->Esp );
    TRACE( "fixing up selector %x for pop instruction\n", *stack );
    *stack = 0;
138 139 140 141
    return TRUE;
}


142 143 144 145 146 147 148 149 150 151 152 153 154
/*************************************************************
 *            insert_event_check
 *
 * Make resuming the context check for pending DPMI events
 * before the original context is restored. This is required
 * because DPMI events are asynchronous, they are blocked while 
 * Wine 32-bit code is being executed and we want to prevent 
 * a race when returning back to 16-bit or 32-bit DPMI context.
 */
static void insert_event_check( CONTEXT *context )
{
    char *stack = wine_ldt_get_ptr( context->SegSs, context->Esp );

155 156 157 158
    /* don't do event check while in system code */
    if (wine_ldt_is_system(context->SegCs))
        return;

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
    if(context->SegCs == dpmi_checker_selector &&
       context->Eip   >= dpmi_checker_offset_call && 
       context->Eip   <= dpmi_checker_offset_cleanup)
    {
        /*
         * Nested call. Stack will be preserved. 
         */
    }
    else if(context->SegCs == dpmi_checker_selector &&
            context->Eip   == dpmi_checker_offset_return)
    {
        /*
         * Nested call. We have just finished popping the fs
         * register, lets put it back into stack.
         */

        stack -= sizeof(WORD);
        *(WORD*)stack = context->SegFs;

        context->Esp -= 2;
    }
    else
    {
        /*
         * Call is not nested.
         * Push modified registers into stack.
         * These will be popped by the assembler stub.
         */

        stack -= sizeof(DWORD);
        *(DWORD*)stack = context->EFlags;
   
        stack -= sizeof(DWORD);
        *(DWORD*)stack = context->SegCs;

        stack -= sizeof(DWORD);
        *(DWORD*)stack = context->Eip;

        stack -= sizeof(WORD);
        *(WORD*)stack = context->SegFs;

        context->Esp  -= 14;
    }

    /*
     * Modify the context so that we jump into assembler stub.
     * TEB access is made easier by providing the stub
     * with the correct fs register value.
     */

    context->SegCs = dpmi_checker_selector;
    context->Eip   = dpmi_checker_offset_call;
    context->SegFs = wine_get_fs();
}


215 216 217 218 219
/*************************************************************
 *            call16_handler
 *
 * Handler for exceptions occurring in 16-bit code.
 */
220 221
static DWORD call16_handler( EXCEPTION_RECORD *record, EXCEPTION_REGISTRATION_RECORD *frame,
                             CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **pdispatcher )
222 223 224 225
{
    if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND))
    {
        /* 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 229
        _LeaveWin16Lock();
    }
230 231
    else if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
             record->ExceptionCode == EXCEPTION_PRIV_INSTRUCTION)
232
    {
233
        if (wine_ldt_is_system(context->SegCs))
234
        {
235 236
            if (fix_selector( context )) return ExceptionContinueExecution;
        }
237
        else
238
        {
239
            SEGPTR gpHandler;
240
            DWORD ret = __wine_emulate_instruction( record, context );
241

242 243 244 245 246 247
            /*
             * Insert check for pending DPMI events. Note that this 
             * check must be inserted after instructions have been 
             * emulated because the instruction emulation requires
             * original CS:IP and the emulation may change TEB.dpmi_vif.
             */
248
            if(get_vm86_teb_info()->dpmi_vif)
249 250
                insert_event_check( context );

251
            if (ret != ExceptionContinueSearch) return ret;
252 253 254

            /* check for Win16 __GP handler */
            if ((gpHandler = HasGPHandler16( MAKESEGPTR( context->SegCs, context->Eip ) )))
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
            {
                WORD *stack = wine_ldt_get_ptr( context->SegSs, context->Esp );
                *--stack = context->SegCs;
                *--stack = context->Eip;

                if (!IS_SELECTOR_32BIT(context->SegSs))
                    context->Esp = MAKELONG( LOWORD(context->Esp - 2*sizeof(WORD)),
                                             HIWORD(context->Esp) );
                else
                    context->Esp -= 2*sizeof(WORD);

                context->SegCs = SELECTOROF( gpHandler );
                context->Eip   = OFFSETOF( gpHandler );
                return ExceptionContinueExecution;
            }
270 271
        }
    }
272 273 274 275
    else if (record->ExceptionCode == EXCEPTION_VM86_STI)
    {
        insert_event_check( context );
    }
276 277 278
    return ExceptionContinueSearch;
}

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

/*************************************************************
 *            vm86_handler
 *
 * Handler for exceptions occurring in vm86 code.
 */
static DWORD vm86_handler( EXCEPTION_RECORD *record, EXCEPTION_REGISTRATION_RECORD *frame,
                           CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **pdispatcher )
{
    if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND))
        return ExceptionContinueSearch;

    if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
        record->ExceptionCode == EXCEPTION_PRIV_INSTRUCTION)
    {
294
        return __wine_emulate_instruction( record, context );
295 296 297 298 299 300
    }

    return ExceptionContinueSearch;
}


301 302 303 304 305
/*
 *  32-bit WOW routines (in WOW32, but actually forwarded to KERNEL32)
 */

/**********************************************************************
306
 *           K32WOWGetDescriptor        (KERNEL32.70)
307
 */
308
BOOL WINAPI K32WOWGetDescriptor( SEGPTR segptr, LPLDT_ENTRY ldtent )
309
{
310
    return GetThreadSelectorEntry( GetCurrentThread(),
311 312 313 314
                                   segptr >> 16, ldtent );
}

/**********************************************************************
315
 *           K32WOWGetVDMPointer        (KERNEL32.56)
316
 */
317
LPVOID WINAPI K32WOWGetVDMPointer( DWORD vp, DWORD dwBytes, BOOL fProtectedMode )
318 319 320 321
{
    /* FIXME: add size check too */

    if ( fProtectedMode )
322
        return MapSL( vp );
323 324 325 326 327
    else
        return DOSMEM_MapRealToLinear( vp );
}

/**********************************************************************
328
 *           K32WOWGetVDMPointerFix     (KERNEL32.68)
329
 */
330
LPVOID WINAPI K32WOWGetVDMPointerFix( DWORD vp, DWORD dwBytes, BOOL fProtectedMode )
331
{
332
    /*
333 334 335 336 337
     * Hmmm. According to the docu, we should call:
     *
     *          GlobalFix16( SELECTOROF(vp) );
     *
     * 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 342 343
     *  fProtectedMode is TRUE, anyway ...)
     */

344
    return K32WOWGetVDMPointer( vp, dwBytes, fProtectedMode );
345 346 347
}

/**********************************************************************
Patrik Stridvall's avatar
Patrik Stridvall committed
348
 *           K32WOWGetVDMPointerUnfix   (KERNEL32.69)
349
 */
350
VOID WINAPI K32WOWGetVDMPointerUnfix( DWORD vp )
351 352 353 354 355 356 357 358 359 360
{
    /*
     * See above why we don't call:
     *
     * GlobalUnfix16( SELECTOROF(vp) );
     *
     */
}

/**********************************************************************
361
 *           K32WOWGlobalAlloc16        (KERNEL32.59)
362
 */
363
WORD WINAPI K32WOWGlobalAlloc16( WORD wFlags, DWORD cb )
364 365 366 367 368
{
    return (WORD)GlobalAlloc16( wFlags, cb );
}

/**********************************************************************
369
 *           K32WOWGlobalFree16         (KERNEL32.62)
370
 */
371
WORD WINAPI K32WOWGlobalFree16( WORD hMem )
372 373 374 375 376
{
    return (WORD)GlobalFree16( (HGLOBAL16)hMem );
}

/**********************************************************************
377
 *           K32WOWGlobalUnlock16       (KERNEL32.61)
378
 */
379
BOOL WINAPI K32WOWGlobalUnlock16( WORD hMem )
380 381 382 383 384
{
    return (BOOL)GlobalUnlock16( (HGLOBAL16)hMem );
}

/**********************************************************************
385
 *           K32WOWGlobalAllocLock16    (KERNEL32.63)
386
 */
387
DWORD WINAPI K32WOWGlobalAllocLock16( WORD wFlags, DWORD cb, WORD *phMem )
388
{
389
    WORD hMem = K32WOWGlobalAlloc16( wFlags, cb );
390 391
    if (phMem) *phMem = hMem;

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 402
        *pcb = GlobalSize16( (HGLOBAL16)hMem );

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 413
        return FALSE;

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 426 427 428 429 430
     * More or less, at least :-/
     */
    Yield16();
}

/**********************************************************************
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 439 440 441 442 443 444
     * only ever called by Win16 tasks that have thunked up ...
     */
    DirectedYield16( (HTASK16)htask16 );
}


/***********************************************************************
445
 *           K32WOWHandle32              (KERNEL32.57)
446
 */
447
HANDLE WINAPI K32WOWHandle32( WORD handle, WOW_HANDLE_TYPE type )
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
{
    switch ( type )
    {
    case WOW_TYPE_HWND:
    case WOW_TYPE_HMENU:
    case WOW_TYPE_HDWP:
    case WOW_TYPE_HDROP:
    case WOW_TYPE_HDC:
    case WOW_TYPE_HFONT:
    case WOW_TYPE_HRGN:
    case WOW_TYPE_HBITMAP:
    case WOW_TYPE_HBRUSH:
    case WOW_TYPE_HPALETTE:
    case WOW_TYPE_HPEN:
    case WOW_TYPE_HACCEL:
463 464 465 466
        return (HANDLE)(ULONG_PTR)handle;

    case WOW_TYPE_HMETAFILE:
        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 473 474 475
    case WOW_TYPE_FULLHWND:
        FIXME( "conversion of full window handles not supported yet\n" );
        return (HANDLE)(ULONG_PTR)handle;

476 477
    default:
        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 488 489 490 491 492 493 494 495 496 497 498 499 500 501
{
    switch ( type )
    {
    case WOW_TYPE_HWND:
    case WOW_TYPE_HMENU:
    case WOW_TYPE_HDWP:
    case WOW_TYPE_HDROP:
    case WOW_TYPE_HDC:
    case WOW_TYPE_HFONT:
    case WOW_TYPE_HRGN:
    case WOW_TYPE_HBITMAP:
    case WOW_TYPE_HBRUSH:
    case WOW_TYPE_HPALETTE:
    case WOW_TYPE_HPEN:
    case WOW_TYPE_HACCEL:
    case WOW_TYPE_FULLHWND:
502 503
    	if ( HIWORD(handle ) )
        	ERR( "handle %p of type %d has non-zero HIWORD\n", handle, type );
504 505
        return LOWORD(handle);

506 507 508 509
    case WOW_TYPE_HMETAFILE:
        FIXME( "conversion of metafile handles not supported yet\n" );
        return LOWORD(handle);

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 516 517 518 519
        return LOWORD(handle);
    }
}

/**********************************************************************
520
 *           K32WOWCallback16Ex         (KERNEL32.55)
521
 */
522 523
BOOL WINAPI K32WOWCallback16Ex( DWORD vpfn16, DWORD dwFlags,
                                DWORD cbArgs, LPVOID pArgs, LPDWORD pdwRetCode )
524 525 526 527
{
    /*
     * Arguments must be prepared in the correct order by the caller
     * (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 535 536 537 538 539 540
    if (dwFlags & (WCB16_REGS|WCB16_REGS_LONG))
    {
        CONTEXT *context = (CONTEXT *)pdwRetCode;

        if (TRACE_ON(relay))
        {
            DWORD count = cbArgs / sizeof(WORD);
541
            WORD * wstack = (WORD *)stack;
542

543
            DPRINTF("%04x:CallTo16(func=%04x:%04x,ds=%04x",
544 545
                    GetCurrentThreadId(),
                    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 550 551 552 553 554 555
            DPRINTF(" ax=%04x bx=%04x cx=%04x dx=%04x si=%04x di=%04x bp=%04x es=%04x fs=%04x\n",
                    (WORD)context->Eax, (WORD)context->Ebx, (WORD)context->Ecx,
                    (WORD)context->Edx, (WORD)context->Esi, (WORD)context->Edi,
                    (WORD)context->Ebp, (WORD)context->SegEs, (WORD)context->SegFs );
            SYSLEVEL_CheckNotLevel( 2 );
        }

556
        if (context->EFlags & 0x00020000)  /* v86 mode */
557
        {
558 559
            EXCEPTION_REGISTRATION_RECORD frame;
            frame.Handler = vm86_handler;
560
            errno = 0;
561 562 563
            __wine_push_frame( &frame );
            __wine_enter_vm86( context );
            __wine_pop_frame( &frame );
564 565
            if (errno != 0)  /* enter_vm86 will fall with ENOSYS on x64 kernels */
            {
566
                WARN("__wine_enter_vm86 failed (errno=%d)\n", errno);
567 568 569 570 571 572
                if (errno == ENOSYS)
                    SetLastError(ERROR_NOT_SUPPORTED);
                else
                    SetLastError(ERROR_GEN_FAILURE);
                return FALSE;
            }
573
        }
574
        else
575
        {
576 577 578
            /* push return address */
            if (dwFlags & WCB16_REGS_LONG)
            {
579 580 581 582
                stack -= sizeof(DWORD);
                *((DWORD *)stack) = HIWORD(call16_ret_addr);
                stack -= sizeof(DWORD);
                *((DWORD *)stack) = LOWORD(call16_ret_addr);
583 584 585 586
                cbArgs += 2 * sizeof(DWORD);
            }
            else
            {
587 588
                stack -= sizeof(SEGPTR);
                *((SEGPTR *)stack) = call16_ret_addr;
589 590
                cbArgs += sizeof(SEGPTR);
            }
591

592 593 594 595 596
            /*
             * Start call by checking for pending events.
             * Note that wine_call_to_16_regs overwrites context stack
             * pointer so we may modify it here without a problem.
             */
597
            if (get_vm86_teb_info()->dpmi_vif)
598 599 600 601 602 603 604
            {
                context->SegSs = wine_get_ds();
                context->Esp   = (DWORD)stack;
                insert_event_check( context );
                cbArgs += (DWORD)stack - context->Esp;
            }

605 606 607 608
            _EnterWin16Lock();
            wine_call_to_16_regs( context, cbArgs, call16_handler );
            _LeaveWin16Lock();
        }
609 610 611

        if (TRACE_ON(relay))
        {
612
            DPRINTF("%04x:RetFrom16() ss:sp=%04x:%04x ",
613 614
                    GetCurrentThreadId(), SELECTOROF(NtCurrentTeb()->WOW32Reserved),
                    OFFSETOF(NtCurrentTeb()->WOW32Reserved));
615 616 617 618 619 620 621 622 623 624 625 626 627
            DPRINTF(" ax=%04x bx=%04x cx=%04x dx=%04x bp=%04x sp=%04x\n",
                    (WORD)context->Eax, (WORD)context->Ebx, (WORD)context->Ecx,
                    (WORD)context->Edx, (WORD)context->Ebp, (WORD)context->Esp );
            SYSLEVEL_CheckNotLevel( 2 );
        }
    }
    else
    {
        DWORD ret;

        if (TRACE_ON(relay))
        {
            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 637 638
            SYSLEVEL_CheckNotLevel( 2 );
        }

639
        /* push return address */
640 641
        stack -= sizeof(SEGPTR);
        *((SEGPTR *)stack) = call16_ret_addr;
642 643
        cbArgs += sizeof(SEGPTR);

644 645 646 647 648 649 650
        /*
         * Actually, we should take care whether the called routine cleans up
         * its stack or not.  Fortunately, our wine_call_to_16 core doesn't rely on
         * the callee to do so; after the routine has returned, the 16-bit
         * stack pointer is always reset to the position it had before.
         */
        _EnterWin16Lock();
651
        ret = wine_call_to_16( (FARPROC16)vpfn16, cbArgs, call16_handler );
652 653 654 655 656
        if (pdwRetCode) *pdwRetCode = ret;
        _LeaveWin16Lock();

        if (TRACE_ON(relay))
        {
657
            DPRINTF("%04x:RetFrom16() ss:sp=%04x:%04x retval=%08x\n",
658 659
                    GetCurrentThreadId(), SELECTOROF(NtCurrentTeb()->WOW32Reserved),
                    OFFSETOF(NtCurrentTeb()->WOW32Reserved), ret);
660 661 662
            SYSLEVEL_CheckNotLevel( 2 );
        }
    }
663 664 665 666

    return TRUE;  /* success */
}

667
/**********************************************************************
668
 *           K32WOWCallback16            (KERNEL32.54)
669 670 671 672 673
 */
DWORD WINAPI K32WOWCallback16( DWORD vpfn16, DWORD dwParam )
{
    DWORD ret;

674
    if ( !K32WOWCallback16Ex( vpfn16, WCB16_PASCAL,
675 676 677 678 679
                           sizeof(DWORD), &dwParam, &ret ) )
        ret = 0L;

    return ret;
}