/*
 * Unit test suite for windowless rich edit controls
 *
 * Copyright 2008 Maarten Lankhorst
 * Copyright 2008 Austin Lund
 * Copyright 2008 Dylan Smith
 *
 * 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
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS

#include <stdio.h>
#include <stdarg.h>
#include <windef.h>
#include <winbase.h>
#include <objbase.h>
#include <richedit.h>
#include <initguid.h>
#include <textserv.h>
#include <wine/test.h>
#include <oleauto.h>
#include <limits.h>

static HMODULE hmoduleRichEdit;

/* Define C Macros for ITextServices calls. */

/* Use a special table for x86 machines to convert the thiscall
 * calling convention.  This isn't needed on other platforms. */
#ifdef __i386__
static ITextServicesVtbl itextServicesStdcallVtbl;
#define TXTSERV_VTABLE(This) (&itextServicesStdcallVtbl)
#else /* __i386__ */
#define TXTSERV_VTABLE(This) (This)->lpVtbl
#endif /* __i386__ */

#define ITextServices_TxSendMessage(This,a,b,c,d) TXTSERV_VTABLE(This)->TxSendMessage(This,a,b,c,d)
#define ITextServices_TxDraw(This,a,b,c,d,e,f,g,h,i,j,k,l) TXTSERV_VTABLE(This)->TxDraw(This,a,b,c,d,e,f,g,h,i,j,k,l)
#define ITextServices_TxGetHScroll(This,a,b,c,d,e) TXTSERV_VTABLE(This)->TxGetHScroll(This,a,b,c,d,e)
#define ITextServices_TxGetVScroll(This,a,b,c,d,e) TXTSERV_VTABLE(This)->TxGetVScroll(This,a,b,c,d,e)
#define ITextServices_OnTxSetCursor(This,a,b,c,d,e,f,g,h,i) TXTSERV_VTABLE(This)->OnTxSetCursor(This,a,b,c,d,e,f,g,h,i)
#define ITextServices_TxQueryHitPoint(This,a,b,c,d,e,f,g,h,i,j) TXTSERV_VTABLE(This)->TxQueryHitPoint(This,a,b,c,d,e,f,g,h,i,j)
#define ITextServices_OnTxInplaceActivate(This,a) TXTSERV_VTABLE(This)->OnTxInplaceActivate(This,a)
#define ITextServices_OnTxInplaceDeactivate(This) TXTSERV_VTABLE(This)->OnTxInplaceDeactivate(This)
#define ITextServices_OnTxUIActivate(This) TXTSERV_VTABLE(This)->OnTxUIActivate(This)
#define ITextServices_OnTxUIDeactivate(This) TXTSERV_VTABLE(This)->OnTxUIDeactivate(This)
#define ITextServices_TxGetText(This,a) TXTSERV_VTABLE(This)->TxGetText(This,a)
#define ITextServices_TxSetText(This,a) TXTSERV_VTABLE(This)->TxSetText(This,a)
#define ITextServices_TxGetCurrentTargetX(This,a) TXTSERV_VTABLE(This)->TxGetCurrentTargetX(This,a)
#define ITextServices_TxGetBaseLinePos(This,a) TXTSERV_VTABLE(This)->TxGetBaseLinePos(This,a)
#define ITextServices_TxGetNaturalSize(This,a,b,c,d,e,f,g,h) TXTSERV_VTABLE(This)->TxGetNaturalSize(This,a,b,c,d,e,f,g,h)
#define ITextServices_TxGetDropTarget(This,a) TXTSERV_VTABLE(This)->TxGetDropTarget(This,a)
#define ITextServices_OnTxPropertyBitsChange(This,a,b) TXTSERV_VTABLE(This)->OnTxPropertyBitsChange(This,a,b)
#define ITextServices_TxGetCachedSize(This,a,b) TXTSERV_VTABLE(This)->TxGetCachedSize(This,a,b)

/* Set the WINETEST_DEBUG environment variable to be greater than 1 for verbose
 * function call traces of ITextHost. */
#define TRACECALL if(winetest_debug > 1) trace

/************************************************************************/
/* ITextHost implementation for conformance testing. */

typedef struct ITextHostTestImpl
{
    ITextHostVtbl *lpVtbl;
    LONG refCount;
} ITextHostTestImpl;

static HRESULT WINAPI ITextHostImpl_QueryInterface(ITextHost *iface,
                                                   REFIID riid,
                                                   LPVOID *ppvObject)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;

    if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_ITextHost)) {
        *ppvObject = This;
        ITextHost_AddRef((ITextHost *)*ppvObject);
        return S_OK;
    }

    return E_NOINTERFACE;
}

static ULONG WINAPI ITextHostImpl_AddRef(ITextHost *iface)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    ULONG refCount = InterlockedIncrement(&This->refCount);
    return refCount;
}

static ULONG WINAPI ITextHostImpl_Release(ITextHost *iface)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    ULONG refCount = InterlockedDecrement(&This->refCount);

    if (!refCount)
    {
        CoTaskMemFree(This);
        return 0;
    } else {
        return refCount;
    }
}

static HDC WINAPI ITextHostImpl_TxGetDC(ITextHost *iface)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetDC(%p)\n", This);
    return NULL;
}

static INT WINAPI ITextHostImpl_TxReleaseDC(ITextHost *iface,
                                            HDC hdc)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxReleaseDC(%p)\n", This);
    return 0;
}

static BOOL WINAPI ITextHostImpl_TxShowScrollBar(ITextHost *iface,
                                                 INT fnBar,
                                                 BOOL fShow)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxShowScrollBar(%p, fnBar=%d, fShow=%d)\n",
                This, fnBar, fShow);
    return FALSE;
}

static BOOL WINAPI ITextHostImpl_TxEnableScrollBar(ITextHost *iface,
                                                   INT fuSBFlags,
                                                   INT fuArrowflags)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxEnableScrollBar(%p, fuSBFlags=%d, fuArrowflags=%d)\n",
               This, fuSBFlags, fuArrowflags);
    return FALSE;
}

static BOOL WINAPI ITextHostImpl_TxSetScrollRange(ITextHost *iface,
                                                  INT fnBar,
                                                  LONG nMinPos,
                                                  INT nMaxPos,
                                                  BOOL fRedraw)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxSetScrollRange(%p, fnBar=%d, nMinPos=%d, nMaxPos=%d, fRedraw=%d)\n",
               This, fnBar, nMinPos, nMaxPos, fRedraw);
    return FALSE;
}

static BOOL WINAPI ITextHostImpl_TxSetScrollPos(ITextHost *iface,
                                                INT fnBar,
                                                INT nPos,
                                                BOOL fRedraw)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxSetScrollPos(%p, fnBar=%d, nPos=%d, fRedraw=%d)\n",
               This, fnBar, nPos, fRedraw);
    return FALSE;
}

static void WINAPI ITextHostImpl_TxInvalidateRect(ITextHost *iface,
                                                  LPCRECT prc,
                                                  BOOL fMode)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxInvalidateRect(%p, prc=%p, fMode=%d)\n",
               This, prc, fMode);
}

static void WINAPI ITextHostImpl_TxViewChange(ITextHost *iface, BOOL fUpdate)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxViewChange(%p, fUpdate=%d)\n",
               This, fUpdate);
}

static BOOL WINAPI ITextHostImpl_TxCreateCaret(ITextHost *iface,
                                               HBITMAP hbmp,
                                               INT xWidth, INT yHeight)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxCreateCaret(%p, nbmp=%p, xWidth=%d, yHeight=%d)\n",
               This, hbmp, xWidth, yHeight);
    return FALSE;
}

static BOOL WINAPI ITextHostImpl_TxShowCaret(ITextHost *iface, BOOL fShow)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxShowCaret(%p, fShow=%d)\n",
               This, fShow);
    return FALSE;
}

static BOOL WINAPI ITextHostImpl_TxSetCaretPos(ITextHost *iface,
                                               INT x, INT y)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxSetCaretPos(%p, x=%d, y=%d)\n", This, x, y);
    return FALSE;
}

static BOOL WINAPI ITextHostImpl_TxSetTimer(ITextHost *iface,
                                            UINT idTimer, UINT uTimeout)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxSetTimer(%p, idTimer=%u, uTimeout=%u)\n",
              This, idTimer, uTimeout);
    return FALSE;
}

static void WINAPI ITextHostImpl_TxKillTimer(ITextHost *iface, UINT idTimer)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxKillTimer(%p, idTimer=%u)\n", This, idTimer);
}

static void WINAPI ITextHostImpl_TxScrollWindowEx(ITextHost *iface,
                                                  INT dx, INT dy,
                                                  LPCRECT lprcScroll,
                                                  LPCRECT lprcClip,
                                                  HRGN hRgnUpdate,
                                                  LPRECT lprcUpdate,
                                                  UINT fuScroll)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxScrollWindowEx(%p, %d, %d, %p, %p, %p, %p, %d)\n",
              This, dx, dy, lprcScroll, lprcClip, hRgnUpdate, lprcUpdate, fuScroll);
}

static void WINAPI ITextHostImpl_TxSetCapture(ITextHost *iface, BOOL fCapture)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxSetCapture(%p, fCapture=%d)\n", This, fCapture);
}

static void WINAPI ITextHostImpl_TxSetFocus(ITextHost *iface)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxSetFocus(%p)\n", This);
}

static void WINAPI ITextHostImpl_TxSetCursor(ITextHost *iface,
                                             HCURSOR hcur,
                                             BOOL fText)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxSetCursor(%p, hcur=%p, fText=%d)\n",
              This, hcur, fText);
}

static BOOL WINAPI ITextHostImpl_TxScreenToClient(ITextHost *iface,
                                                  LPPOINT lppt)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxScreenToClient(%p, lppt=%p)\n", This, lppt);
    return FALSE;
}

static BOOL WINAPI ITextHostImpl_TxClientToScreen(ITextHost *iface,
                                                  LPPOINT lppt)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxClientToScreen(%p, lppt=%p)\n", This, lppt);
    return FALSE;
}

static HRESULT WINAPI ITextHostImpl_TxActivate(ITextHost *iface,
                                               LONG *plOldState)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxActivate(%p, plOldState=%p)\n", This, plOldState);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxDeactivate(ITextHost *iface,
                                                 LONG lNewState)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxDeactivate(%p, lNewState=%d)\n", This, lNewState);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetClientRect(ITextHost *iface,
                                                    LPRECT prc)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetClientRect(%p, prc=%p)\n", This, prc);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetViewInset(ITextHost *iface,
                                                   LPRECT prc)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetViewInset(%p, prc=%p)\n", This, prc);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetCharFormat(ITextHost *iface,
                                                    const CHARFORMATW **ppCF)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetCharFormat(%p, ppCF=%p)\n", This, ppCF);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetParaFormat(ITextHost *iface,
                                                    const PARAFORMAT **ppPF)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetParaFormat(%p, ppPF=%p)\n", This, ppPF);
    return E_NOTIMPL;
}

static COLORREF WINAPI ITextHostImpl_TxGetSysColor(ITextHost *iface,
                                                   int nIndex)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetSysColor(%p, nIndex=%d)\n", This, nIndex);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetBackStyle(ITextHost *iface,
                                                   TXTBACKSTYLE *pStyle)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetBackStyle(%p, pStyle=%p)\n", This, pStyle);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetMaxLength(ITextHost *iface,
                                                   DWORD *pLength)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetMaxLength(%p, pLength=%p)\n", This, pLength);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetScrollBars(ITextHost *iface,
                                                    DWORD *pdwScrollBar)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetScrollBars(%p, pdwScrollBar=%p)\n",
               This, pdwScrollBar);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetPasswordChar(ITextHost *iface,
                                                      WCHAR *pch)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetPasswordChar(%p, pch=%p)\n", This, pch);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetAcceleratorPos(ITextHost *iface,
                                                        LONG *pch)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetAcceleratorPos(%p, pch=%p)\n", This, pch);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_TxGetExtent(ITextHost *iface,
                                                LPSIZEL lpExtent)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetExtent(%p, lpExtent=%p)\n", This, lpExtent);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_OnTxCharFormatChange(ITextHost *iface,
                                                         const CHARFORMATW *pcf)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to OnTxCharFormatChange(%p, pcf=%p)\n", This, pcf);
    return E_NOTIMPL;
}

static HRESULT WINAPI ITextHostImpl_OnTxParaFormatChange(ITextHost *iface,
                                                         const PARAFORMAT *ppf)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to OnTxParaFormatChange(%p, ppf=%p)\n", This, ppf);
    return E_NOTIMPL;
}

/* This must return S_OK for the native ITextServices object to
   initialize. */
static HRESULT WINAPI ITextHostImpl_TxGetPropertyBits(ITextHost *iface,
                                                      DWORD dwMask,
                                                      DWORD *pdwBits)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetPropertyBits(%p, dwMask=0x%08x, pdwBits=%p)\n",
              This, dwMask, pdwBits);
    *pdwBits = 0;
    return S_OK;
}

static HRESULT WINAPI ITextHostImpl_TxNotify(ITextHost *iface, DWORD iNotify,
                                             void *pv)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxNotify(%p, iNotify=%d, pv=%p)\n", This, iNotify, pv);
    return E_NOTIMPL;
}

static HIMC WINAPI ITextHostImpl_TxImmGetContext(ITextHost *iface)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxImmGetContext(%p)\n", This);
    return 0;
}

static void WINAPI ITextHostImpl_TxImmReleaseContext(ITextHost *iface, HIMC himc)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxImmReleaseContext(%p, himc=%p)\n", This, himc);
}

/* This function must set the variable pointed to by *lSelBarWidth.
   Otherwise an uninitalized value will be used to calculate
   positions and sizes even if E_NOTIMPL is returned. */
static HRESULT WINAPI ITextHostImpl_TxGetSelectionBarWidth(ITextHost *iface,
                                                           LONG *lSelBarWidth)
{
    ITextHostTestImpl *This = (ITextHostTestImpl *)iface;
    TRACECALL("Call to TxGetSelectionBarWidth(%p, lSelBarWidth=%p)\n",
                This, lSelBarWidth);
    *lSelBarWidth = 0;
    return E_NOTIMPL;
}

static ITextHostVtbl itextHostVtbl = {
    ITextHostImpl_QueryInterface,
    ITextHostImpl_AddRef,
    ITextHostImpl_Release,
    ITextHostImpl_TxGetDC,
    ITextHostImpl_TxReleaseDC,
    ITextHostImpl_TxShowScrollBar,
    ITextHostImpl_TxEnableScrollBar,
    ITextHostImpl_TxSetScrollRange,
    ITextHostImpl_TxSetScrollPos,
    ITextHostImpl_TxInvalidateRect,
    ITextHostImpl_TxViewChange,
    ITextHostImpl_TxCreateCaret,
    ITextHostImpl_TxShowCaret,
    ITextHostImpl_TxSetCaretPos,
    ITextHostImpl_TxSetTimer,
    ITextHostImpl_TxKillTimer,
    ITextHostImpl_TxScrollWindowEx,
    ITextHostImpl_TxSetCapture,
    ITextHostImpl_TxSetFocus,
    ITextHostImpl_TxSetCursor,
    ITextHostImpl_TxScreenToClient,
    ITextHostImpl_TxClientToScreen,
    ITextHostImpl_TxActivate,
    ITextHostImpl_TxDeactivate,
    ITextHostImpl_TxGetClientRect,
    ITextHostImpl_TxGetViewInset,
    ITextHostImpl_TxGetCharFormat,
    ITextHostImpl_TxGetParaFormat,
    ITextHostImpl_TxGetSysColor,
    ITextHostImpl_TxGetBackStyle,
    ITextHostImpl_TxGetMaxLength,
    ITextHostImpl_TxGetScrollBars,
    ITextHostImpl_TxGetPasswordChar,
    ITextHostImpl_TxGetAcceleratorPos,
    ITextHostImpl_TxGetExtent,
    ITextHostImpl_OnTxCharFormatChange,
    ITextHostImpl_OnTxParaFormatChange,
    ITextHostImpl_TxGetPropertyBits,
    ITextHostImpl_TxNotify,
    ITextHostImpl_TxImmGetContext,
    ITextHostImpl_TxImmReleaseContext,
    ITextHostImpl_TxGetSelectionBarWidth
};

static ITextServices *txtserv = NULL;
static ITextHostTestImpl *dummyTextHost;
static void *wrapperCodeMem = NULL;

#include "pshpack1.h"

/* Code structure for x86 byte code */
typedef struct
{
    BYTE pop_eax;  /* popl  %eax  */
    BYTE push_ecx; /* pushl %ecx  */
    BYTE push_eax; /* pushl %eax  */
    BYTE jmp_func; /* jmp   $func */
    DWORD func;
} THISCALL_TO_STDCALL_THUNK;

typedef struct
{
    BYTE pop_eax;               /* popl  %eax */
    BYTE pop_ecx;               /* popl  %ecx */
    BYTE push_eax;              /* pushl %eax */
    BYTE mov_vtable_eax[2];     /* movl (%ecx), %eax */
    BYTE jmp_eax[2];            /* jmp *$vtablefunc_offset(%eax) */
    int  vtablefunc_offset;
} STDCALL_TO_THISCALL_THUNK;

#include "poppack.h"

static void setup_thiscall_wrappers(void)
{
#ifdef __i386__
    void** pVtable;
    void** pVtableEnd;
    THISCALL_TO_STDCALL_THUNK *thunk;
    STDCALL_TO_THISCALL_THUNK *thunk2;

    wrapperCodeMem = VirtualAlloc(NULL,
                                  (sizeof(ITextHostVtbl)/sizeof(void*) - 3)
                                    * sizeof(THISCALL_TO_STDCALL_THUNK)
                                  +(sizeof(ITextServicesVtbl)/sizeof(void*) - 3)
                                    * sizeof(STDCALL_TO_THISCALL_THUNK),
                                  MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    thunk = wrapperCodeMem;

    /* Wrap all ITextHostImpl methods with code to perform a thiscall to
     * stdcall conversion. The thiscall calling convention places the This
     * pointer in ecx on the x86 platform, and the stdcall calling convention
     * pushes the This pointer on the stack as the first argument.
     *
     * The byte code does the conversion then jumps to the real function.
     *
     * Each wrapper needs to be modified so that the function to jump to is
     * modified in the byte code. */

    /* Skip QueryInterface, AddRef, and Release native actually
     * defined them with the stdcall calling convention. */
    pVtable = (void**)&itextHostVtbl + 3;
    pVtableEnd = (void**)(&itextHostVtbl + 1);
    while (pVtable != pVtableEnd) {
        /* write byte code to executable memory */
        thunk->pop_eax = 0x58;  /* popl  %eax  */
        thunk->push_ecx = 0x51; /* pushl %ecx  */
        thunk->push_eax = 0x50; /* pushl %eax  */
        thunk->jmp_func = 0xe9; /* jmp   $func */
        /* The address needs to be relative to the end of the jump instructions. */
        thunk->func = (char*)*pVtable - (char*)(&thunk->func + 1);
        *pVtable = thunk;
        pVtable++;
        thunk++;
    }

    /* Setup an ITextServices standard call vtable that will call the
     * native thiscall vtable when the methods are called. */

    /* QueryInterface, AddRef, and Release should be called directly on the
     * real vtable since they use the stdcall calling convention. */
    thunk2 = (STDCALL_TO_THISCALL_THUNK *)thunk;
    pVtable = (void**)&itextServicesStdcallVtbl + 3;
    pVtableEnd = (void**)(&itextServicesStdcallVtbl + 1);
    while (pVtable != pVtableEnd) {
        /* write byte code to executable memory */
        thunk2->pop_eax = 0x58;               /* popl  %eax */
        thunk2->pop_ecx = 0x59;               /* popl  %ecx */
        thunk2->push_eax = 0x50;              /* pushl %eax */
        thunk2->mov_vtable_eax[0] = 0x8b;     /* movl (%ecx), %eax */
        thunk2->mov_vtable_eax[1] = 0x01;
        thunk2->jmp_eax[0] = 0xff;            /* jmp *$vtablefunc_offset(%eax) */
        thunk2->jmp_eax[1] = 0xa0;
        thunk2->vtablefunc_offset = (char*)pVtable - (char*)&itextServicesStdcallVtbl;
        *pVtable = thunk2;
        pVtable++;
        thunk2++;
    }
#endif /* __i386__ */
}

/*************************************************************************/
/* Conformance test functions. */

/* Initialize the test texthost structure */
static BOOL init_texthost(void)
{
    IUnknown *init;
    HRESULT result;
    PCreateTextServices pCreateTextServices;

    dummyTextHost = CoTaskMemAlloc(sizeof(*dummyTextHost));
    if (dummyTextHost == NULL) {
        skip("Insufficient memory to create ITextHost interface\n");
        return FALSE;
    }
    dummyTextHost->lpVtbl = &itextHostVtbl;
    dummyTextHost->refCount = 1;

    /* MSDN states that an IUnknown object is returned by
       CreateTextServices which is then queried to obtain a
       ITextServices object. */
    pCreateTextServices = (void*)GetProcAddress(hmoduleRichEdit, "CreateTextServices");
    result = (*pCreateTextServices)(NULL,(ITextHost*)dummyTextHost, &init);
    ok(result == S_OK, "Did not return OK when created. Returned %x\n", result);
    if (result != S_OK) {
        CoTaskMemFree(dummyTextHost);
        skip("CreateTextServices failed.\n");
        return FALSE;
    }

    result = IUnknown_QueryInterface(init, &IID_ITextServices,
                                     (void **)&txtserv);
    ok((result == S_OK) && (txtserv != NULL), "Querying interface failed\n");
    IUnknown_Release(init);
    if (!((result == S_OK) && (txtserv != NULL))) {
        CoTaskMemFree(dummyTextHost);
        skip("Could not retrieve ITextServices interface\n");
        return FALSE;
    }

    return TRUE;
}

static void test_TxGetText(void)
{
    HRESULT hres;
    BSTR rettext;

    if (!init_texthost())
        return;

    hres = ITextServices_TxGetText(txtserv, &rettext);
    ok(hres == S_OK, "ITextServices_TxGetText failed\n");

    IUnknown_Release(txtserv);
    CoTaskMemFree(dummyTextHost);
}

static void test_TxSetText(void)
{
    HRESULT hres;
    BSTR rettext;
    WCHAR settext[] = {'T','e','s','t',0};

    if (!init_texthost())
        return;

    hres = ITextServices_TxSetText(txtserv, settext);
    ok(hres == S_OK, "ITextServices_TxSetText failed\n");

    hres = ITextServices_TxGetText(txtserv, &rettext);
    ok(hres == S_OK, "ITextServices_TxGetText failed\n");

    ok(SysStringLen(rettext) == 4,
                 "String returned of wrong length\n");
    ok(memcmp(rettext,settext,SysStringByteLen(rettext)) == 0,
                 "String returned differs\n");

    IUnknown_Release(txtserv);
    CoTaskMemFree(dummyTextHost);
}

static void test_TxGetNaturalSize(void) {
    HRESULT result;
    BOOL ret;

    /* This value is used when calling TxGetNaturalSize.  MSDN says
       that this is not supported however a null pointer cannot be
       used as it will cause a segmentation violation.  The values in
       the structure being pointed to are required to be INT_MAX
       otherwise calculations can give wrong values. */
    const SIZEL psizelExtent = {INT_MAX,INT_MAX};

    static const WCHAR oneA[] = {'A',0};

    /* Results of measurements */
    LONG xdim, ydim;

    /* The device context to do the tests in */
    HDC hdcDraw;

    /* Variables with the text metric information */
    INT charwidth_caps_text[26];
    TEXTMETRIC tmInfo_text;

    if (!init_texthost())
        return;

    hdcDraw = GetDC(NULL);
    SaveDC(hdcDraw);

    /* Populate the metric strucs */
    SetMapMode(hdcDraw,MM_TEXT);
    GetTextMetrics(hdcDraw, &tmInfo_text);
    SetLastError(0xdeadbeef);
    ret = GetCharWidth32(hdcDraw,'A','Z',charwidth_caps_text);
    if (!ret && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
        win_skip("GetCharWidth32 is not available\n");
        RestoreDC(hdcDraw,1);
        ReleaseDC(NULL,hdcDraw);
        return;
    }

    /* Make measurements in MM_TEXT */
    SetMapMode(hdcDraw,MM_TEXT);
    xdim = 0; ydim = 0;

    result = ITextServices_TxSetText(txtserv, oneA);
    ok(result == S_OK, "ITextServices_TxSetText failed\n");

    result = ITextServices_TxGetNaturalSize(txtserv, DVASPECT_CONTENT,
                                            hdcDraw, NULL, NULL,
                                            TXTNS_FITTOCONTENT, &psizelExtent,
                                            &xdim, &ydim);
    todo_wine ok(result == S_OK, "TxGetNaturalSize failed\n");
    todo_wine ok(ydim == tmInfo_text.tmHeight,
                 "Height calculated incorrectly (expected %d, got %d)\n",
                 tmInfo_text.tmHeight, ydim);
    /* The native DLL adds one pixel extra when calculating widths. */
    todo_wine ok(xdim >= charwidth_caps_text[0] && xdim <= charwidth_caps_text[0] + 1,
                 "Width calculated incorrectly (expected %d {+1}, got %d)\n",
                 charwidth_caps_text[0], xdim);

    RestoreDC(hdcDraw,1);
    ReleaseDC(NULL,hdcDraw);

    IUnknown_Release(txtserv);
    CoTaskMemFree(dummyTextHost);
}

START_TEST( txtsrv )
{
    setup_thiscall_wrappers();

    /* Must explicitly LoadLibrary(). The test has no references to functions in
     * RICHED20.DLL, so the linker doesn't actually link to it. */
    hmoduleRichEdit = LoadLibrary("RICHED20.DLL");
    ok(hmoduleRichEdit != NULL, "error: %d\n", (int) GetLastError());

    if (init_texthost())
    {
        IUnknown_Release(txtserv);
        CoTaskMemFree(dummyTextHost);

        test_TxGetText();
        test_TxSetText();
        test_TxGetNaturalSize();
    }
    if (wrapperCodeMem) VirtualFree(wrapperCodeMem, 0, MEM_RELEASE);
}