wowthunk.c 21.4 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
static SEGPTR call16_ret_addr;  /* segptr to __wine_call_to_16_ret routine */
56

57 58 59 60 61
static WORD  dpmi_checker_selector;
static DWORD dpmi_checker_offset_call;
static DWORD dpmi_checker_offset_cleanup;
static DWORD dpmi_checker_offset_return;

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

71
    if (!codesel) return FALSE;
72 73 74 75
    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 );
76 77 78 79

      /* Patch the return addresses for CallTo16 routines */

    CallTo16_DataSelector = wine_get_ds();
80
    call16_ret_addr = MAKESEGPTR( codesel, (BYTE *)__wine_call_to_16_ret - __wine_call16_start );
81
    CALL32_CBClient_RetAddr =
82
        MAKESEGPTR( codesel, (BYTE *)CALL32_CBClient_Ret - __wine_call16_start );
83
    CALL32_CBClientEx_RetAddr =
84
        MAKESEGPTR( codesel, (BYTE *)CALL32_CBClientEx_Ret - __wine_call16_start );
85

86 87
    /* Prepare selector and offsets for DPMI event checking. */
    dpmi_checker_selector = codesel;
88 89 90
    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;
91

92
    if (TRACE_ON(relay) || TRACE_ON(snoop)) RELAY16_InitDebugLists();
93

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    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;

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

    /* 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;
136 137 138 139
    return TRUE;
}


140 141 142 143 144 145 146 147 148 149 150 151 152
/*************************************************************
 *            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 );

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

157 158 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
    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();
}


213 214 215 216 217
/*************************************************************
 *            call16_handler
 *
 * Handler for exceptions occurring in 16-bit code.
 */
218 219
static DWORD call16_handler( EXCEPTION_RECORD *record, EXCEPTION_REGISTRATION_RECORD *frame,
                             CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **pdispatcher )
220 221 222 223
{
    if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND))
    {
        /* unwinding: restore the stack pointer in the TEB, and leave the Win16 mutex */
224
        STACK32FRAME *frame32 = CONTAINING_RECORD(frame, STACK32FRAME, frame);
225
        NtCurrentTeb()->WOW32Reserved = (void *)frame32->frame16;
226 227
        _LeaveWin16Lock();
    }
228 229
    else if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
             record->ExceptionCode == EXCEPTION_PRIV_INSTRUCTION)
230
    {
231
        if (wine_ldt_is_system(context->SegCs))
232
        {
233 234
            if (fix_selector( context )) return ExceptionContinueExecution;
        }
235
        else
236
        {
237
            SEGPTR gpHandler;
238
            DWORD ret = __wine_emulate_instruction( record, context );
239

240 241 242 243 244 245
            /*
             * 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.
             */
246
            if(get_vm86_teb_info()->dpmi_vif)
247 248
                insert_event_check( context );

249
            if (ret != ExceptionContinueSearch) return ret;
250 251 252

            /* check for Win16 __GP handler */
            if ((gpHandler = HasGPHandler16( MAKESEGPTR( context->SegCs, context->Eip ) )))
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
            {
                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;
            }
268 269
        }
    }
270 271 272 273
    else if (record->ExceptionCode == EXCEPTION_VM86_STI)
    {
        insert_event_check( context );
    }
274 275 276
    return ExceptionContinueSearch;
}

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291

/*************************************************************
 *            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)
    {
292
        return __wine_emulate_instruction( record, context );
293 294 295 296 297 298
    }

    return ExceptionContinueSearch;
}


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

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

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

    if ( fProtectedMode )
320
        return MapSL( vp );
321 322 323 324 325
    else
        return DOSMEM_MapRealToLinear( vp );
}

/**********************************************************************
326
 *           K32WOWGetVDMPointerFix     (KERNEL32.68)
327
 */
328
LPVOID WINAPI K32WOWGetVDMPointerFix( DWORD vp, DWORD dwBytes, BOOL fProtectedMode )
329
{
330
    /*
331 332 333 334 335
     * Hmmm. According to the docu, we should call:
     *
     *          GlobalFix16( SELECTOROF(vp) );
     *
     * But this is unnecessary under Wine, as we never move global
336
     * memory segments in linear memory anyway.
337
     *
338
     * (I'm not so sure what we are *supposed* to do if
339 340 341
     *  fProtectedMode is TRUE, anyway ...)
     */

342
    return K32WOWGetVDMPointer( vp, dwBytes, fProtectedMode );
343 344 345
}

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

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

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

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

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

390
    return K32WOWGlobalLock16( hMem );
391 392 393
}

/**********************************************************************
394
 *           K32WOWGlobalLockSize16     (KERNEL32.65)
395
 */
396
DWORD WINAPI K32WOWGlobalLockSize16( WORD hMem, PDWORD pcb )
397
{
398
    if ( pcb )
399 400
        *pcb = GlobalSize16( (HGLOBAL16)hMem );

401
    return K32WOWGlobalLock16( hMem );
402 403 404
}

/**********************************************************************
405
 *           K32WOWGlobalUnlockFree16   (KERNEL32.64)
406
 */
407
WORD WINAPI K32WOWGlobalUnlockFree16( DWORD vpMem )
408
{
409
    if ( !K32WOWGlobalUnlock16( HIWORD(vpMem) ) )
410 411
        return FALSE;

412
    return K32WOWGlobalFree16( HIWORD(vpMem) );
413 414 415 416
}


/**********************************************************************
417
 *           K32WOWYield16              (KERNEL32.66)
418
 */
419
VOID WINAPI K32WOWYield16( void )
420 421
{
    /*
422
     * This does the right thing for both Win16 and Win32 tasks.
423 424 425 426 427 428
     * More or less, at least :-/
     */
    Yield16();
}

/**********************************************************************
429
 *           K32WOWDirectedYield16       (KERNEL32.67)
430
 */
431
VOID WINAPI K32WOWDirectedYield16( WORD htask16 )
432 433 434
{
    /*
     * Argh.  Our scheduler doesn't like DirectedYield by Win32
435
     * tasks at all.  So we do hope that this routine is indeed
436 437 438 439 440 441 442
     * only ever called by Win16 tasks that have thunked up ...
     */
    DirectedYield16( (HTASK16)htask16 );
}


/***********************************************************************
443
 *           K32WOWHandle32              (KERNEL32.57)
444
 */
445
HANDLE WINAPI K32WOWHandle32( WORD handle, WOW_HANDLE_TYPE type )
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
{
    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:
461 462 463 464
        return (HANDLE)(ULONG_PTR)handle;

    case WOW_TYPE_HMETAFILE:
        FIXME( "conversion of metafile handles not supported yet\n" );
465
        return (HANDLE)(ULONG_PTR)handle;
466

467
    case WOW_TYPE_HTASK:
468
        return ((TDB *)GlobalLock16(handle))->teb->ClientId.UniqueThread;
469

470 471 472 473
    case WOW_TYPE_FULLHWND:
        FIXME( "conversion of full window handles not supported yet\n" );
        return (HANDLE)(ULONG_PTR)handle;

474 475
    default:
        ERR( "handle 0x%04x of unknown type %d\n", handle, type );
476
        return (HANDLE)(ULONG_PTR)handle;
477 478 479 480
    }
}

/***********************************************************************
481
 *           K32WOWHandle16              (KERNEL32.58)
482
 */
483
WORD WINAPI K32WOWHandle16( HANDLE handle, WOW_HANDLE_TYPE type )
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
{
    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:
500 501
    	if ( HIWORD(handle ) )
        	ERR( "handle %p of type %d has non-zero HIWORD\n", handle, type );
502 503
        return LOWORD(handle);

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

508
    case WOW_TYPE_HTASK:
509
        return TASK_GetTaskFromThread( (DWORD)handle );
510

511
    default:
512
        ERR( "handle %p of unknown type %d\n", handle, type );
513 514 515 516 517
        return LOWORD(handle);
    }
}

/**********************************************************************
518
 *           K32WOWCallback16Ex         (KERNEL32.55)
519
 */
520 521
BOOL WINAPI K32WOWCallback16Ex( DWORD vpfn16, DWORD dwFlags,
                                DWORD cbArgs, LPVOID pArgs, LPDWORD pdwRetCode )
522 523 524 525
{
    /*
     * Arguments must be prepared in the correct order by the caller
     * (both for PASCAL and CDECL calling convention), so we simply
526
     * copy them to the 16-bit stack ...
527
     */
528
    char *stack = (char *)CURRENT_STACK16 - cbArgs;
529 530

    memcpy( stack, pArgs, cbArgs );
531

532 533 534 535 536 537 538
    if (dwFlags & (WCB16_REGS|WCB16_REGS_LONG))
    {
        CONTEXT *context = (CONTEXT *)pdwRetCode;

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

541
            DPRINTF("%04x:CallTo16(func=%04x:%04x,ds=%04x",
542 543
                    GetCurrentThreadId(),
                    context->SegCs, LOWORD(context->Eip), context->SegDs );
544
            while (count) DPRINTF( ",%04x", wstack[--count] );
545
            DPRINTF(") ss:sp=%04x:%04x",
546
                    SELECTOROF(NtCurrentTeb()->WOW32Reserved), OFFSETOF(NtCurrentTeb()->WOW32Reserved) );
547 548 549 550 551 552 553
            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 );
        }

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

590 591 592 593 594
            /*
             * 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.
             */
595
            if (get_vm86_teb_info()->dpmi_vif)
596 597 598 599 600 601 602
            {
                context->SegSs = wine_get_ds();
                context->Esp   = (DWORD)stack;
                insert_event_check( context );
                cbArgs += (DWORD)stack - context->Esp;
            }

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

        if (TRACE_ON(relay))
        {
610
            DPRINTF("%04x:RetFrom16() ss:sp=%04x:%04x ",
611 612
                    GetCurrentThreadId(), SELECTOROF(NtCurrentTeb()->WOW32Reserved),
                    OFFSETOF(NtCurrentTeb()->WOW32Reserved));
613 614 615 616 617 618 619 620 621 622 623 624 625
            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);
626
            WORD * wstack = (WORD *)stack;
627

628
            DPRINTF("%04x:CallTo16(func=%04x:%04x,ds=%04x",
629
                    GetCurrentThreadId(), HIWORD(vpfn16), LOWORD(vpfn16),
630
                    SELECTOROF(NtCurrentTeb()->WOW32Reserved) );
631
            while (count) DPRINTF( ",%04x", wstack[--count] );
632
            DPRINTF(") ss:sp=%04x:%04x\n",
633
                    SELECTOROF(NtCurrentTeb()->WOW32Reserved), OFFSETOF(NtCurrentTeb()->WOW32Reserved) );
634 635 636
            SYSLEVEL_CheckNotLevel( 2 );
        }

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

642 643 644 645 646 647 648
        /*
         * 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();
649
        ret = wine_call_to_16( (FARPROC16)vpfn16, cbArgs, call16_handler );
650 651 652 653 654
        if (pdwRetCode) *pdwRetCode = ret;
        _LeaveWin16Lock();

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

    return TRUE;  /* success */
}

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

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

    return ret;
}