Logo Search packages:      
Sourcecode: wine version File versions

misc.c

/* -*- tab-width: 8; c-basic-offset: 4 -*- */

/*
 * DDEML library
 *
 * Copyright 1997 Alexandre Julliard
 * Copyright 1997 Len White
 * Copyright 1999 Keith Matthews
 * Copyright 2000 Corel
 * Copyright 2001 Eric Pouech
 * Copyright 2003, 2004, 2005 Dmitry Timoshkov
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"
#include "wine/port.h"

#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"
#include "dde.h"
#include "ddeml.h"
#include "win.h"
#include "dde/dde_private.h"
#include "wine/unicode.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddeml);

/* convert between ATOM and HSZ avoiding compiler warnings */
#define ATOM2HSZ(atom)  ((HSZ)      (ULONG_PTR)(atom))
#define HSZ2ATOM(hsz)   ((ATOM)     (ULONG_PTR)(hsz))

static WDML_INSTANCE*   WDML_InstanceList = NULL;
static DWORD            WDML_MaxInstanceID = 0;  /* OK for present, have to worry about wrap-around later */
const WCHAR       WDML_szEventClass[] = {'W','i','n','e','D','d','e','E','v','e','n','t','C','l','a','s','s',0};

static CRITICAL_SECTION_DEBUG critsect_debug =
{
    0, 0, &WDML_CritSect,
    { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
      0, 0, { 0, (DWORD)(__FILE__ ": WDML_CritSect") }
};
CRITICAL_SECTION WDML_CritSect = { &critsect_debug, -1, 0, 0, 0, 0 };

/* ================================================================
 *
 *                Pure DDE (non DDEML) management
 *
 * ================================================================ */


/*****************************************************************
 *            PackDDElParam (USER32.@)
 *
 * RETURNS
 *   the packed lParam
 */
LPARAM WINAPI PackDDElParam(UINT msg, UINT_PTR uiLo, UINT_PTR uiHi)
{
    HGLOBAL hMem;
    UINT_PTR *params;

    switch (msg)
    {
    case WM_DDE_ACK:
    case WM_DDE_ADVISE:
    case WM_DDE_DATA:
    case WM_DDE_POKE:
        if (!(hMem = GlobalAlloc(GMEM_DDESHARE, sizeof(UINT_PTR) * 2)))
        {
            ERR("GlobalAlloc failed\n");
            return 0;
        }
        if (!(params = GlobalLock(hMem)))
        {
            ERR("GlobalLock failed (%p)\n", hMem);
            return 0;
        }
        params[0] = uiLo;
        params[1] = uiHi;
        GlobalUnlock(hMem);
        return (LPARAM)hMem;

    case WM_DDE_EXECUTE:
        return uiHi;

    default:
        return MAKELPARAM(uiLo, uiHi);
    }
}


/*****************************************************************
 *            UnpackDDElParam (USER32.@)
 *
 * RETURNS
 *   success: nonzero
 *   failure: zero
 */
BOOL WINAPI UnpackDDElParam(UINT msg, LPARAM lParam,
                      PUINT_PTR uiLo, PUINT_PTR uiHi)
{
    UINT_PTR *params;

    switch (msg)
    {
    case WM_DDE_ACK:
    case WM_DDE_ADVISE:
    case WM_DDE_DATA:
    case WM_DDE_POKE:
        if (!lParam) return FALSE;
        if (!(params = GlobalLock( (HGLOBAL)lParam )))
        {
            ERR("GlobalLock failed (%lx)\n", lParam);
            return FALSE;
        }
        TRACE("unpacked: low %08x, high %08x\n", params[0], params[1]);
        if (uiLo) *uiLo = params[0];
        if (uiHi) *uiHi = params[1];
        GlobalUnlock( (HGLOBAL)lParam );
        return TRUE;

    case WM_DDE_EXECUTE:
        if (uiLo) *uiLo = 0;
        if (uiHi) *uiHi = lParam;
        return TRUE;

    default:
        if (uiLo) *uiLo = LOWORD(lParam);
        if (uiHi) *uiHi = HIWORD(lParam);
        return TRUE;
    }
}


/*****************************************************************
 *            FreeDDElParam (USER32.@)
 *
 * RETURNS
 *   success: nonzero
 *   failure: zero
 */
BOOL WINAPI FreeDDElParam(UINT msg, LPARAM lParam)
{
    switch (msg)
    {
    case WM_DDE_ACK:
    case WM_DDE_ADVISE:
    case WM_DDE_DATA:
    case WM_DDE_POKE:
        /* first check if it's a global handle */
        if (!GlobalHandle( (LPVOID)lParam )) return TRUE;
        return !GlobalFree( (HGLOBAL)lParam );

    default:
        return TRUE;
     }
}


/*****************************************************************
 *            ReuseDDElParam (USER32.@)
 *
 * RETURNS
 *   the packed lParam
 */
LPARAM WINAPI ReuseDDElParam(LPARAM lParam, UINT msgIn, UINT msgOut,
                             UINT_PTR uiLo, UINT_PTR uiHi)
{
    UINT_PTR *params;

    switch (msgIn)
    {
    case WM_DDE_ACK:
    case WM_DDE_ADVISE:
    case WM_DDE_DATA:
    case WM_DDE_POKE:
        switch(msgOut)
        {
        case WM_DDE_ACK:
        case WM_DDE_ADVISE:
        case WM_DDE_DATA:
        case WM_DDE_POKE:
            if (!lParam) return 0;
            if (!(params = GlobalLock( (HGLOBAL)lParam )))
            {
                ERR("GlobalLock failed\n");
                return 0;
            }
            params[0] = uiLo;
            params[1] = uiHi;
            TRACE("Reusing pack %08x %08x\n", uiLo, uiHi);
            GlobalLock( (HGLOBAL)lParam );
            return lParam;

        case WM_DDE_EXECUTE:
            FreeDDElParam( msgIn, lParam );
            return uiHi;

        default:
            FreeDDElParam( msgIn, lParam );
            return MAKELPARAM(uiLo, uiHi);
        }

    default:
        return PackDDElParam( msgOut, uiLo, uiHi );
    }
}

/*****************************************************************
 *            ImpersonateDdeClientWindow (USER32.@)
 *
 * PARAMS
 * hWndClient       [I] handle to DDE client window
 * hWndServer       [I] handle to DDE server window
 */
BOOL WINAPI ImpersonateDdeClientWindow(HWND hWndClient, HWND hWndServer)
{
     FIXME("(%p %p): stub\n", hWndClient, hWndServer);
     return FALSE;
}

/*****************************************************************
 *            DdeSetQualityOfService (USER32.@)
 */

BOOL WINAPI DdeSetQualityOfService(HWND hwndClient, CONST SECURITY_QUALITY_OF_SERVICE *pqosNew,
                           PSECURITY_QUALITY_OF_SERVICE pqosPrev)
{
     FIXME("(%p %p %p): stub\n", hwndClient, pqosNew, pqosPrev);
     return TRUE;
}

/* ================================================================
 *
 *                Instance management
 *
 * ================================================================ */

/******************************************************************************
 *          IncrementInstanceId
 *
 *    generic routine to increment the max instance Id and allocate a new application instance
 */
static void WDML_IncrementInstanceId(WDML_INSTANCE* pInstance)
{
    DWORD   id = InterlockedIncrement(&WDML_MaxInstanceID);

    pInstance->instanceID = id;
    TRACE("New instance id %ld allocated\n", id);
}

/******************************************************************
 *          WDML_EventProc
 *
 *
 */
static LRESULT CALLBACK WDML_EventProc(HWND hwndEvent, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    WDML_INSTANCE*      pInstance;
    HSZ                 hsz1, hsz2;

    switch (uMsg)
    {
    case WM_WDML_REGISTER:
      pInstance = WDML_GetInstanceFromWnd(hwndEvent);
        /* try calling the Callback */
      if (pInstance && !(pInstance->CBFflags & CBF_SKIP_REGISTRATIONS))
      {
          hsz1 = WDML_MakeHszFromAtom(pInstance, wParam);
          hsz2 = WDML_MakeHszFromAtom(pInstance, lParam);
          WDML_InvokeCallback(pInstance, XTYP_REGISTER, 0, 0, hsz1, hsz2, 0, 0, 0);
          WDML_DecHSZ(pInstance, hsz1);
          WDML_DecHSZ(pInstance, hsz2);
      }
      break;

    case WM_WDML_UNREGISTER:
      pInstance = WDML_GetInstanceFromWnd(hwndEvent);
      if (pInstance && !(pInstance->CBFflags & CBF_SKIP_UNREGISTRATIONS))
      {
          hsz1 = WDML_MakeHszFromAtom(pInstance, wParam);
          hsz2 = WDML_MakeHszFromAtom(pInstance, lParam);
          WDML_InvokeCallback(pInstance, XTYP_UNREGISTER, 0, 0, hsz1, hsz2, 0, 0, 0);
          WDML_DecHSZ(pInstance, hsz1);
          WDML_DecHSZ(pInstance, hsz2);
      }
      break;

    case WM_WDML_CONNECT_CONFIRM:
      pInstance = WDML_GetInstanceFromWnd(hwndEvent);
      if (pInstance && !(pInstance->CBFflags & CBF_SKIP_CONNECT_CONFIRMS))
      {
          WDML_CONV*    pConv;
          /* confirm connection...
           * lookup for this conv handle
           */
            HWND client = WIN_GetFullHandle( (HWND)wParam );
            HWND server = WIN_GetFullHandle( (HWND)lParam );
          for (pConv = pInstance->convs[WDML_SERVER_SIDE]; pConv != NULL; pConv = pConv->next)
          {
            if (pConv->hwndClient == client && pConv->hwndServer == server)
                break;
          }
          if (pConv)
          {
            pConv->wStatus |= ST_ISLOCAL;

            WDML_InvokeCallback(pInstance, XTYP_CONNECT_CONFIRM, 0, (HCONV)pConv,
                            pConv->hszTopic, pConv->hszService, 0, 0,
                            (pConv->wStatus & ST_ISSELF) ? 1 : 0);
          }
      }
      break;
    default:
      return DefWindowProcW(hwndEvent, uMsg, wParam, lParam);
    }
    return 0;
}

/******************************************************************
 *          WDML_Initialize
 *
 *
 */
UINT WDML_Initialize(LPDWORD pidInst, PFNCALLBACK pfnCallback,
                 DWORD afCmd, DWORD ulRes, BOOL b16)
{
    WDML_INSTANCE*            pInstance;
    WDML_INSTANCE*            reference_inst;
    UINT                ret;
    WNDCLASSEXW               wndclass;

    TRACE("(%p,%p,0x%lx,%ld)\n",
        pidInst, pfnCallback, afCmd, ulRes);

    if (ulRes)
    {
      ERR("Reserved value not zero?  What does this mean?\n");
      /* trap this and no more until we know more */
      return DMLERR_NO_ERROR;
    }

    /* grab enough heap for one control struct - not really necessary for re-initialise
     *      but allows us to use same validation routines */
    pInstance = (WDML_INSTANCE*)HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_INSTANCE));
    if (pInstance == NULL)
    {
      /* catastrophe !! warn user & abort */
      ERR("Instance create failed - out of memory\n");
      return DMLERR_SYS_ERROR;
    }
    pInstance->next = NULL;
    pInstance->monitor = (afCmd | APPCLASS_MONITOR);

    /* messy bit, spec implies that 'Client Only' can be set in 2 different ways, catch 1 here */

    pInstance->clientOnly = afCmd & APPCMD_CLIENTONLY;
    pInstance->instanceID = *pidInst; /* May need to add calling proc Id */
    pInstance->threadID = GetCurrentThreadId();
    pInstance->callback = *pfnCallback;
    pInstance->win16 = b16;
    pInstance->nodeList = NULL; /* node will be added later */
    pInstance->monitorFlags = afCmd & MF_MASK;
    pInstance->servers = NULL;
    pInstance->convs[0] = NULL;
    pInstance->convs[1] = NULL;
    pInstance->links[0] = NULL;
    pInstance->links[1] = NULL;

    /* isolate CBF flags in one go, expect this will go the way of all attempts to be clever !! */

    pInstance->CBFflags = afCmd^((afCmd&MF_MASK)|((afCmd&APPCMD_MASK)|(afCmd&APPCLASS_MASK)));

    if (!pInstance->clientOnly)
    {
      /* Check for other way of setting Client-only !! */
      pInstance->clientOnly =
          (pInstance->CBFflags & CBF_FAIL_ALLSVRXACTIONS) == CBF_FAIL_ALLSVRXACTIONS;
    }

    TRACE("instance created - checking validity \n");

    if (*pidInst == 0)
    {
      /*  Initialisation of new Instance Identifier */
      TRACE("new instance, callback %p flags %lX\n",pfnCallback,afCmd);

      EnterCriticalSection(&WDML_CritSect);

      if (WDML_InstanceList == NULL)
      {
          /* can't be another instance in this case, assign to the base pointer */
          WDML_InstanceList = pInstance;

          /* since first must force filter of XTYP_CONNECT and XTYP_WILDCONNECT for
           *            present
           *      -------------------------------      NOTE NOTE NOTE    --------------------------
           *
           *      the manual is not clear if this condition
           *      applies to the first call to DdeInitialize from an application, or the
           *      first call for a given callback !!!
           */

          pInstance->CBFflags = pInstance->CBFflags|APPCMD_FILTERINITS;
          TRACE("First application instance detected OK\n");
          /*      allocate new instance ID */
          WDML_IncrementInstanceId(pInstance);
      }
      else
      {
          /* really need to chain the new one in to the latest here, but after checking conditions
           *      such as trying to start a conversation from an application trying to monitor */
          reference_inst = WDML_InstanceList;
          TRACE("Subsequent application instance - starting checks\n");
          while (reference_inst->next != NULL)
          {
            /*
             *    This set of tests will work if application uses same instance Id
             *    at application level once allocated - which is what manual implies
             *    should happen. If someone tries to be
             *    clever (lazy ?) it will fail to pick up that later calls are for
             *    the same application - should we trust them ?
             */
            if (pInstance->instanceID == reference_inst->instanceID)
            {
                /* Check 1 - must be same Client-only state */

                if (pInstance->clientOnly != reference_inst->clientOnly)
                {
                  ret = DMLERR_DLL_USAGE;
                  goto theError;
                }

                /* Check 2 - cannot use 'Monitor' with any non-monitor modes */

                if (pInstance->monitor != reference_inst->monitor)
                {
                  ret = DMLERR_INVALIDPARAMETER;
                  goto theError;
                }

                /* Check 3 - must supply different callback address */

                if (pInstance->callback == reference_inst->callback)
                {
                  ret = DMLERR_DLL_USAGE;
                  goto theError;
                }
            }
            reference_inst = reference_inst->next;
          }
          /*  All cleared, add to chain */

          TRACE("Application Instance checks finished\n");
          WDML_IncrementInstanceId(pInstance);
          reference_inst->next = pInstance;
      }
      LeaveCriticalSection(&WDML_CritSect);

      *pidInst = pInstance->instanceID;

      /* for deadlock issues, windows must always be created when outside the critical section */
      wndclass.cbSize        = sizeof(wndclass);
      wndclass.style         = 0;
      wndclass.lpfnWndProc   = WDML_EventProc;
      wndclass.cbClsExtra    = 0;
      wndclass.cbWndExtra    = sizeof(ULONG_PTR);
      wndclass.hInstance     = 0;
      wndclass.hIcon         = 0;
      wndclass.hCursor       = 0;
      wndclass.hbrBackground = 0;
      wndclass.lpszMenuName  = NULL;
      wndclass.lpszClassName = WDML_szEventClass;
      wndclass.hIconSm       = 0;

      RegisterClassExW(&wndclass);

      pInstance->hwndEvent = CreateWindowW(WDML_szEventClass, NULL,
                                    WS_POPUP, 0, 0, 0, 0,
                                    0, 0, 0, 0);

      SetWindowLongPtrW(pInstance->hwndEvent, GWL_WDML_INSTANCE, (ULONG_PTR)pInstance);

      TRACE("New application instance processing finished OK\n");
    }
    else
    {
      /* Reinitialisation situation   --- FIX  */
      TRACE("reinitialisation of (%p,%p,0x%lx,%ld): stub\n", pidInst, pfnCallback, afCmd, ulRes);

      EnterCriticalSection(&WDML_CritSect);

      if (WDML_InstanceList == NULL)
      {
          ret = DMLERR_INVALIDPARAMETER;
          goto theError;
      }
      HeapFree(GetProcessHeap(), 0, pInstance); /* finished - release heap space used as work store */
      /* can't reinitialise if we have initialised nothing !! */
      reference_inst = WDML_InstanceList;
      /* must first check if we have been given a valid instance to re-initialise !!  how do we do that ? */
      /*
       *    MS allows initialisation without specifying a callback, should we allow addition of the
       *    callback by a later call to initialise ? - if so this lot will have to change
       */
      while (reference_inst->next != NULL)
      {
          if (*pidInst == reference_inst->instanceID && pfnCallback == reference_inst->callback)
          {
            /* Check 1 - cannot change client-only mode if set via APPCMD_CLIENTONLY */

            if (reference_inst->clientOnly)
            {
                if  ((reference_inst->CBFflags & CBF_FAIL_ALLSVRXACTIONS) != CBF_FAIL_ALLSVRXACTIONS)
                {
                        /* i.e. Was set to Client-only and through APPCMD_CLIENTONLY */

                  if (!(afCmd & APPCMD_CLIENTONLY))
                  {
                      ret = DMLERR_INVALIDPARAMETER;
                      goto theError;
                  }
                }
            }
            /* Check 2 - cannot change monitor modes */

            if (pInstance->monitor != reference_inst->monitor)
            {
                ret = DMLERR_INVALIDPARAMETER;
                goto theError;
            }

            /* Check 3 - trying to set Client-only via APPCMD when not set so previously */

            if ((afCmd&APPCMD_CLIENTONLY) && !reference_inst->clientOnly)
            {
                ret = DMLERR_INVALIDPARAMETER;
                goto theError;
            }
            break;
          }
          reference_inst = reference_inst->next;
      }
      if (reference_inst->next == NULL)
      {
          ret = DMLERR_INVALIDPARAMETER;
          goto theError;
      }
      /* All checked - change relevant flags */

      reference_inst->CBFflags = pInstance->CBFflags;
      reference_inst->clientOnly = pInstance->clientOnly;
      reference_inst->monitorFlags = pInstance->monitorFlags;
      LeaveCriticalSection(&WDML_CritSect);
    }

    return DMLERR_NO_ERROR;
 theError:
    HeapFree(GetProcessHeap(), 0, pInstance);
    LeaveCriticalSection(&WDML_CritSect);
    return ret;
}

/******************************************************************************
 *            DdeInitializeA   (USER32.@)
 */
UINT WINAPI DdeInitializeA(LPDWORD pidInst, PFNCALLBACK pfnCallback,
                     DWORD afCmd, DWORD ulRes)
{
    return WDML_Initialize(pidInst, pfnCallback, afCmd, ulRes, FALSE);
}

/******************************************************************************
 * DdeInitializeW [USER32.@]
 * Registers an application with the DDEML
 *
 * PARAMS
 *    pidInst     [I] Pointer to instance identifier
 *    pfnCallback [I] Pointer to callback function
 *    afCmd       [I] Set of command and filter flags
 *    ulRes       [I] Reserved
 *
 * RETURNS
 *    Success: DMLERR_NO_ERROR
 *    Failure: DMLERR_DLL_USAGE, DMLERR_INVALIDPARAMETER, DMLERR_SYS_ERROR
 */
UINT WINAPI DdeInitializeW(LPDWORD pidInst, PFNCALLBACK pfnCallback,
                     DWORD afCmd, DWORD ulRes)
{
    return WDML_Initialize(pidInst, pfnCallback, afCmd, ulRes, FALSE);
}

/*****************************************************************
 * DdeUninitialize [USER32.@]  Frees DDEML resources
 *
 * PARAMS
 *    idInst [I] Instance identifier
 *
 * RETURNS
 *    Success: TRUE
 *    Failure: FALSE
 */

BOOL WINAPI DdeUninitialize(DWORD idInst)
{
    /*  Stage one - check if we have a handle for this instance
     */
    WDML_INSTANCE*            pInstance;
    WDML_CONV*                pConv;
    WDML_CONV*                pConvNext;

    TRACE("(%ld)\n", idInst);

    EnterCriticalSection(&WDML_CritSect);

    /*  First check instance
     */
    pInstance = WDML_GetInstance(idInst);
    if (pInstance == NULL)
    {
      LeaveCriticalSection(&WDML_CritSect);
      /*
       *    Needs something here to record NOT_INITIALIZED ready for DdeGetLastError
       */
      return FALSE;
    }

    /* first terminate all conversations client side
     * this shall close existing links...
     */
    for (pConv = pInstance->convs[WDML_CLIENT_SIDE]; pConv != NULL; pConv = pConvNext)
    {
      pConvNext = pConv->next;
      DdeDisconnect((HCONV)pConv);
    }
    if (pInstance->convs[WDML_CLIENT_SIDE])
      FIXME("still pending conversations\n");

    /* then unregister all known service names */
    DdeNameService(idInst, 0, 0, DNS_UNREGISTER);

    /* Free the nodes that were not freed by this instance
     * and remove the nodes from the list of HSZ nodes.
     */
    WDML_FreeAllHSZ(pInstance);

    DestroyWindow(pInstance->hwndEvent);

    /* OK now delete the instance handle itself */

    if (WDML_InstanceList == pInstance)
    {
      /* special case - the first/only entry */
      WDML_InstanceList = pInstance->next;
    }
    else
    {
      /* general case, remove entry */
      WDML_INSTANCE*    inst;

      for (inst = WDML_InstanceList; inst->next != pInstance; inst = inst->next);
      inst->next = pInstance->next;
    }
    /* leave crit sect and release the heap entry
     */
    HeapFree(GetProcessHeap(), 0, pInstance);
    LeaveCriticalSection(&WDML_CritSect);
    return TRUE;
}

/******************************************************************
 *          WDML_NotifyThreadExit
 *
 *
 */
void WDML_NotifyThreadDetach(void)
{
    WDML_INSTANCE*      pInstance;
    WDML_INSTANCE*      next;
    DWORD         tid = GetCurrentThreadId();

    EnterCriticalSection(&WDML_CritSect);
    for (pInstance = WDML_InstanceList; pInstance != NULL; pInstance = next)
    {
      next = pInstance->next;
      if (pInstance->threadID == tid)
      {
          DdeUninitialize(pInstance->instanceID);
      }
    }
    LeaveCriticalSection(&WDML_CritSect);
}

/******************************************************************
 *          WDML_InvokeCallback
 *
 *
 */
HDDEDATA    WDML_InvokeCallback(WDML_INSTANCE* pInstance, UINT uType, UINT uFmt, HCONV hConv,
                            HSZ hsz1, HSZ hsz2, HDDEDATA hdata,
                            ULONG_PTR dwData1, ULONG_PTR dwData2)
{
    HDDEDATA      ret;

    if (pInstance == NULL)
      return NULL;
    TRACE("invoking CB%d[%p] (%x %x %p %p %p %p %lx %lx)\n",
        pInstance->win16 ? 16 : 32, pInstance->callback, uType, uFmt,
        hConv, hsz1, hsz2, hdata, dwData1, dwData2);
    if (pInstance->win16)
    {
      ret = WDML_InvokeCallback16(pInstance->callback, uType, uFmt, hConv,
                            hsz1, hsz2, hdata, dwData1, dwData2);
    }
    else
    {
      ret = pInstance->callback(uType, uFmt, hConv, hsz1, hsz2, hdata, dwData1, dwData2);
    }
    TRACE("done => %p\n", ret);
    return ret;
}

/*****************************************************************************
 *    WDML_GetInstance
 *
 *    generic routine to return a pointer to the relevant DDE_HANDLE_ENTRY
 *    for an instance Id, or NULL if the entry does not exist
 *
 */
WDML_INSTANCE*    WDML_GetInstance(DWORD instId)
{
    WDML_INSTANCE*      pInstance;

    for (pInstance = WDML_InstanceList; pInstance != NULL; pInstance = pInstance->next)
    {
      if (pInstance->instanceID == instId)
      {
          if (GetCurrentThreadId() != pInstance->threadID)
          {
            FIXME("Tried to get instance from wrong thread\n");
            continue;
          }
          return pInstance;
      }
    }
    TRACE("Instance entry missing\n");
    return NULL;
}

/******************************************************************
 *          WDML_GetInstanceFromWnd
 *
 *
 */
WDML_INSTANCE*    WDML_GetInstanceFromWnd(HWND hWnd)
{
    return (WDML_INSTANCE*)GetWindowLongPtrW(hWnd, GWL_WDML_INSTANCE);
}

/******************************************************************************
 * DdeGetLastError [USER32.@]  Gets most recent error code
 *
 * PARAMS
 *    idInst [I] Instance identifier
 *
 * RETURNS
 *    Last error code
 */
UINT WINAPI DdeGetLastError(DWORD idInst)
{
    DWORD         error_code;
    WDML_INSTANCE*      pInstance;

    EnterCriticalSection(&WDML_CritSect);

    /*  First check instance
     */
    pInstance = WDML_GetInstance(idInst);
    if  (pInstance == NULL)
    {
      error_code = DMLERR_INVALIDPARAMETER;
    }
    else
    {
      error_code = pInstance->lastError;
      pInstance->lastError = 0;
    }

    LeaveCriticalSection(&WDML_CritSect);
    return error_code;
}

/* ================================================================
 *
 *                String management
 *
 * ================================================================ */


/******************************************************************
 *          WDML_FindNode
 *
 *
 */
static HSZNode*   WDML_FindNode(WDML_INSTANCE* pInstance, HSZ hsz)
{
    HSZNode*      pNode;

    if (pInstance == NULL) return NULL;

    for (pNode = pInstance->nodeList; pNode != NULL; pNode = pNode->next)
    {
      if (pNode->hsz == hsz) break;
    }
    if (!pNode) WARN("HSZ %p not found\n", hsz);
    return pNode;
}

/******************************************************************
 *          WDML_MakeAtomFromHsz
 *
 * Creates a global atom from an existing HSZ
 * Generally used before sending an HSZ as an atom to a remote app
 */
ATOM  WDML_MakeAtomFromHsz(HSZ hsz)
{
    WCHAR nameBuffer[MAX_BUFFER_LEN];

    if (GetAtomNameW(HSZ2ATOM(hsz), nameBuffer, MAX_BUFFER_LEN))
      return GlobalAddAtomW(nameBuffer);
    WARN("HSZ %p not found\n", hsz);
    return 0;
}

/******************************************************************
 *          WDML_MakeHszFromAtom
 *
 * Creates a HSZ from an existing global atom
 * Generally used while receiving a global atom and transforming it
 * into an HSZ
 */
HSZ   WDML_MakeHszFromAtom(WDML_INSTANCE* pInstance, ATOM atom)
{
    WCHAR nameBuffer[MAX_BUFFER_LEN];

    if (!atom) return NULL;

    if (GlobalGetAtomNameW(atom, nameBuffer, MAX_BUFFER_LEN))
    {
      TRACE("%x => %s\n", atom, debugstr_w(nameBuffer));
      return DdeCreateStringHandleW(pInstance->instanceID, nameBuffer, CP_WINUNICODE);
    }
    WARN("ATOM 0x%x not found\n", atom);
    return 0;
}

/******************************************************************
 *          WDML_IncHSZ
 *
 *
 */
BOOL WDML_IncHSZ(WDML_INSTANCE* pInstance, HSZ hsz)
{
    HSZNode*      pNode;

    pNode = WDML_FindNode(pInstance, hsz);
    if (!pNode) return FALSE;

    pNode->refCount++;
    return TRUE;
}

/******************************************************************************
 *           WDML_DecHSZ    (INTERNAL)
 *
 * Decrease the ref count of an HSZ. If it reaches 0, the node is removed from the list
 * of HSZ nodes
 * Returns -1 is the HSZ isn't found, otherwise it's the current (after --) of the ref count
 */
BOOL WDML_DecHSZ(WDML_INSTANCE* pInstance, HSZ hsz)
{
    HSZNode*      pPrev = NULL;
    HSZNode*      pCurrent;

    for (pCurrent = pInstance->nodeList; pCurrent != NULL; pCurrent = (pPrev = pCurrent)->next)
    {
      /* If we found the node we were looking for and its ref count is one,
       * we can remove it
       */
      if (pCurrent->hsz == hsz)
      {
          if (--pCurrent->refCount == 0)
          {
            if (pCurrent == pInstance->nodeList)
            {
                pInstance->nodeList = pCurrent->next;
            }
            else
            {
                pPrev->next = pCurrent->next;
            }
            HeapFree(GetProcessHeap(), 0, pCurrent);
            DeleteAtom(HSZ2ATOM(hsz));
          }
          return TRUE;
      }
    }
    WARN("HSZ %p not found\n", hsz);

    return FALSE;
}

/******************************************************************************
 *            WDML_FreeAllHSZ    (INTERNAL)
 *
 * Frees up all the strings still allocated in the list and
 * remove all the nodes from the list of HSZ nodes.
 */
void WDML_FreeAllHSZ(WDML_INSTANCE* pInstance)
{
    /* Free any strings created in this instance.
     */
    while (pInstance->nodeList != NULL)
    {
      DdeFreeStringHandle(pInstance->instanceID, pInstance->nodeList->hsz);
    }
}

/******************************************************************************
 *            InsertHSZNode    (INTERNAL)
 *
 * Insert a node to the head of the list.
 */
static void WDML_InsertHSZNode(WDML_INSTANCE* pInstance, HSZ hsz)
{
    if (hsz != 0)
    {
      HSZNode* pNew = NULL;
      /* Create a new node for this HSZ.
       */
      pNew = (HSZNode*)HeapAlloc(GetProcessHeap(), 0, sizeof(HSZNode));
      if (pNew != NULL)
      {
          pNew->hsz      = hsz;
          pNew->next     = pInstance->nodeList;
          pNew->refCount = 1;
          pInstance->nodeList = pNew;
      }
      else
      {
          ERR("Primary HSZ Node allocation failed - out of memory\n");
      }
    }
}

/******************************************************************
 *          WDML_QueryString
 *
 *
 */
static int  WDML_QueryString(WDML_INSTANCE* pInstance, HSZ hsz, LPVOID ptr, DWORD cchMax,
                         int codepage)
{
    WCHAR   pString[MAX_BUFFER_LEN];
    int           ret;
    /* If psz is null, we have to return only the length
     * of the string.
     */
    if (ptr == NULL)
    {
      ptr = pString;
      cchMax = MAX_BUFFER_LEN;
    }

    switch (codepage)
    {
    case CP_WINANSI:
      ret = GetAtomNameA(HSZ2ATOM(hsz), ptr, cchMax);
      break;
    case CP_WINUNICODE:
      ret = GetAtomNameW(HSZ2ATOM(hsz), ptr, cchMax);
        break;
    default:
      ERR("Unknown code page %d\n", codepage);
      ret = 0;
    }
    return ret;
}

/*****************************************************************
 * DdeQueryStringA [USER32.@]
 */
DWORD WINAPI DdeQueryStringA(DWORD idInst, HSZ hsz, LPSTR psz, DWORD cchMax, INT iCodePage)
{
    DWORD         ret = 0;
    WDML_INSTANCE*      pInstance;

    TRACE("(%ld, %p, %p, %ld, %d)\n", idInst, hsz, psz, cchMax, iCodePage);

    EnterCriticalSection(&WDML_CritSect);

    /*  First check instance
     */
    pInstance = WDML_GetInstance(idInst);
    if (pInstance != NULL)
    {
      if (iCodePage == 0) iCodePage = CP_WINANSI;
      ret = WDML_QueryString(pInstance, hsz, psz, cchMax, iCodePage);
    }
    LeaveCriticalSection(&WDML_CritSect);

    TRACE("returning %ld (%s)\n", ret, debugstr_a(psz));
    return ret;
}

/*****************************************************************
 * DdeQueryStringW [USER32.@]
 */

DWORD WINAPI DdeQueryStringW(DWORD idInst, HSZ hsz, LPWSTR psz, DWORD cchMax, INT iCodePage)
{
    DWORD         ret = 0;
    WDML_INSTANCE*      pInstance;

    TRACE("(%ld, %p, %p, %ld, %d)\n", idInst, hsz, psz, cchMax, iCodePage);

    EnterCriticalSection(&WDML_CritSect);

    /*  First check instance
     */
    pInstance = WDML_GetInstance(idInst);
    if (pInstance != NULL)
    {
      if (iCodePage == 0) iCodePage = CP_WINUNICODE;
      ret = WDML_QueryString(pInstance, hsz, psz, cchMax, iCodePage);
    }
    LeaveCriticalSection(&WDML_CritSect);

    TRACE("returning %ld (%s)\n", ret, debugstr_w(psz));
    return ret;
}

/******************************************************************
 *          DML_CreateString
 *
 *
 */
static      HSZ   WDML_CreateString(WDML_INSTANCE* pInstance, LPCVOID ptr, int codepage)
{
    HSZ           hsz;

    switch (codepage)
    {
    case CP_WINANSI:
      hsz = ATOM2HSZ(AddAtomA(ptr));
      TRACE("added atom %s with HSZ %p, \n", debugstr_a(ptr), hsz);
      break;
    case CP_WINUNICODE:
      hsz = ATOM2HSZ(AddAtomW(ptr));
      TRACE("added atom %s with HSZ %p, \n", debugstr_w(ptr), hsz);
      break;
    default:
      ERR("Unknown code page %d\n", codepage);
      return 0;
    }
    WDML_InsertHSZNode(pInstance, hsz);
    return hsz;
}

/*****************************************************************
 * DdeCreateStringHandleA [USER32.@]
 *
 * RETURNS
 *    Success: String handle
 *    Failure: 0
 */
HSZ WINAPI DdeCreateStringHandleA(DWORD idInst, LPCSTR psz, INT codepage)
{
    HSZ                 hsz = 0;
    WDML_INSTANCE*      pInstance;

    TRACE("(%ld,%s,%d)\n", idInst, debugstr_a(psz), codepage);

    EnterCriticalSection(&WDML_CritSect);

    pInstance = WDML_GetInstance(idInst);
    if (pInstance)
    {
      if (codepage == 0) codepage = CP_WINANSI;
      hsz = WDML_CreateString(pInstance, psz, codepage);
    }

    LeaveCriticalSection(&WDML_CritSect);
    return hsz;
}


/******************************************************************************
 * DdeCreateStringHandleW [USER32.@]  Creates handle to identify string
 *
 * PARAMS
 *    idInst   [I] Instance identifier
 *    psz      [I] Pointer to string
 *    codepage [I] Code page identifier
 * RETURNS
 *    Success: String handle
 *    Failure: 0
 */
HSZ WINAPI DdeCreateStringHandleW(DWORD idInst, LPCWSTR psz, INT codepage)
{
    WDML_INSTANCE*      pInstance;
    HSZ                 hsz = 0;

    TRACE("(%ld,%s,%d)\n", idInst, debugstr_w(psz), codepage);

    EnterCriticalSection(&WDML_CritSect);

    pInstance = WDML_GetInstance(idInst);
    if (pInstance)
    {
      if (codepage == 0) codepage = CP_WINUNICODE;
      hsz = WDML_CreateString(pInstance, psz, codepage);
    }
    LeaveCriticalSection(&WDML_CritSect);

    return hsz;
}

/*****************************************************************
 *            DdeFreeStringHandle   (USER32.@)
 * RETURNS: success: nonzero
 *          fail:    zero
 */
BOOL WINAPI DdeFreeStringHandle(DWORD idInst, HSZ hsz)
{
    WDML_INSTANCE*      pInstance;
    BOOL          ret = FALSE;

    TRACE("(%ld,%p): \n", idInst, hsz);

    EnterCriticalSection(&WDML_CritSect);

    /*  First check instance
     */
    pInstance = WDML_GetInstance(idInst);
    if (pInstance)
      ret = WDML_DecHSZ(pInstance, hsz);

    LeaveCriticalSection(&WDML_CritSect);

    return ret;
}

/*****************************************************************
 *            DdeKeepStringHandle  (USER32.@)
 *
 * RETURNS: success: nonzero
 *          fail:    zero
 */
BOOL WINAPI DdeKeepStringHandle(DWORD idInst, HSZ hsz)
{
    WDML_INSTANCE*      pInstance;
    BOOL          ret = FALSE;

    TRACE("(%ld,%p): \n", idInst, hsz);

    EnterCriticalSection(&WDML_CritSect);

    /*  First check instance
     */
    pInstance = WDML_GetInstance(idInst);
    if (pInstance)
      ret = WDML_IncHSZ(pInstance, hsz);

    LeaveCriticalSection(&WDML_CritSect);
    return ret;
}

/*****************************************************************
 *            DdeCmpStringHandles (USER32.@)
 *
 * Compares the value of two string handles.  This comparison is
 * not case sensitive.
 *
 * Returns:
 * -1 The value of hsz1 is zero or less than hsz2
 * 0  The values of hsz 1 and 2 are the same or both zero.
 * 1  The value of hsz2 is zero of less than hsz1
 */
INT WINAPI DdeCmpStringHandles(HSZ hsz1, HSZ hsz2)
{
    WCHAR   psz1[MAX_BUFFER_LEN];
    WCHAR   psz2[MAX_BUFFER_LEN];
    int           ret = 0;
    int           ret1, ret2;

    ret1 = GetAtomNameW(HSZ2ATOM(hsz1), psz1, MAX_BUFFER_LEN);
    ret2 = GetAtomNameW(HSZ2ATOM(hsz2), psz2, MAX_BUFFER_LEN);

    TRACE("(%p<%s> %p<%s>);\n", hsz1, debugstr_w(psz1), hsz2, debugstr_w(psz2));

    /* Make sure we found both strings. */
    if (ret1 == 0 && ret2 == 0)
    {
      /* If both are not found, return both  "zero strings". */
      ret = 0;
    }
    else if (ret1 == 0)
    {
      /* If hsz1 is a not found, return hsz1 is "zero string". */
      ret = -1;
    }
    else if (ret2 == 0)
    {
      /* If hsz2 is a not found, return hsz2 is "zero string". */
      ret = 1;
    }
    else
    {
      /* Compare the two strings we got (case insensitive). */
      ret = lstrcmpiW(psz1, psz2);
      /* Since strcmp returns any number smaller than
       * 0 when the first string is found to be less than
       * the second one we must make sure we are returning
       * the proper values.
       */
      if (ret < 0)
      {
          ret = -1;
      }
      else if (ret > 0)
      {
          ret = 1;
      }
    }

    return ret;
}

/* ================================================================
 *
 *                Data handle management
 *
 * ================================================================ */

/*****************************************************************
 *            DdeCreateDataHandle (USER32.@)
 */
HDDEDATA WINAPI DdeCreateDataHandle(DWORD idInst, LPBYTE pSrc, DWORD cb, DWORD cbOff,
                                    HSZ hszItem, UINT wFmt, UINT afCmd)
{
    /* For now, we ignore idInst, hszItem.
     * The purpose of these arguments still need to be investigated.
     */

    HGLOBAL             hMem;
    LPBYTE              pByte;
    DDE_DATAHANDLE_HEAD*      pDdh;
    WCHAR psz[MAX_BUFFER_LEN];

    if (!GetAtomNameW(HSZ2ATOM(hszItem), psz, MAX_BUFFER_LEN))
    {
        psz[0] = HSZ2ATOM(hszItem);
        psz[1] = 0;
    }

    TRACE("(%ld,%p,cb %ld, cbOff %ld,%p <%s>,fmt %04x,%x)\n",
        idInst, pSrc, cb, cbOff, hszItem, debugstr_w(psz), wFmt, afCmd);

    if (afCmd != 0 && afCmd != HDATA_APPOWNED)
        return 0;

    /* we use the first 4 bytes to store the size */
    if (!(hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cb + cbOff + sizeof(DDE_DATAHANDLE_HEAD))))
    {
      ERR("GlobalAlloc failed\n");
      return 0;
    }

    pDdh = (DDE_DATAHANDLE_HEAD*)GlobalLock(hMem);
    if (!pDdh)
    {
        GlobalFree(hMem);
        return 0;
    }

    pDdh->cfFormat = wFmt;
    pDdh->bAppOwned = (afCmd == HDATA_APPOWNED);

    pByte = (LPBYTE)(pDdh + 1);
    if (pSrc)
    {
      memcpy(pByte, pSrc + cbOff, cb);
    }
    GlobalUnlock(hMem);

    TRACE("=> %p\n", hMem);
    return (HDDEDATA)hMem;
}

/*****************************************************************
 *
 *            DdeAddData (USER32.@)
 */
HDDEDATA WINAPI DdeAddData(HDDEDATA hData, LPBYTE pSrc, DWORD cb, DWORD cbOff)
{
    DWORD   old_sz, new_sz;
    LPBYTE  pDst;

    TRACE("(%p,%p,cb %ld, cbOff %ld)\n", hData, pSrc, cb, cbOff);

    pDst = DdeAccessData(hData, &old_sz);
    if (!pDst) return 0;

    new_sz = cb + cbOff;
    if (new_sz > old_sz)
    {
      DdeUnaccessData(hData);
      hData = GlobalReAlloc((HGLOBAL)hData, new_sz + sizeof(DDE_DATAHANDLE_HEAD),
                        GMEM_MOVEABLE | GMEM_DDESHARE);
      pDst = DdeAccessData(hData, &old_sz);
    }

    if (!pDst) return 0;

    memcpy(pDst + cbOff, pSrc, cb);
    DdeUnaccessData(hData);
    return hData;
}

/******************************************************************************
 * DdeGetData [USER32.@]  Copies data from DDE object to local buffer
 *
 *
 * PARAMS
 * hData    [I] Handle to DDE object
 * pDst           [I] Pointer to destination buffer
 * cbMax    [I] Amount of data to copy
 * cbOff    [I] Offset to beginning of data
 *
 * RETURNS
 *    Size of memory object associated with handle
 */
DWORD WINAPI DdeGetData(HDDEDATA hData, LPBYTE pDst, DWORD cbMax, DWORD cbOff)
{
    DWORD   dwSize, dwRet;
    LPBYTE  pByte;

    TRACE("(%p,%p,%ld,%ld)\n", hData, pDst, cbMax, cbOff);

    pByte = DdeAccessData(hData, &dwSize);

    if (pByte)
    {
        if (!pDst)
        {
            dwRet = dwSize;
        }
        else if (cbOff + cbMax < dwSize)
      {
          dwRet = cbMax;
      }
      else if (cbOff < dwSize)
      {
          dwRet = dwSize - cbOff;
      }
      else
      {
          dwRet = 0;
      }
      if (pDst && dwRet != 0)
      {
          memcpy(pDst, pByte + cbOff, dwRet);
      }
      DdeUnaccessData(hData);
    }
    else
    {
      dwRet = 0;
    }
    return dwRet;
}

/*****************************************************************
 *            DdeAccessData (USER32.@)
 */
LPBYTE WINAPI DdeAccessData(HDDEDATA hData, LPDWORD pcbDataSize)
{
    HGLOBAL             hMem = (HGLOBAL)hData;
    DDE_DATAHANDLE_HEAD*      pDdh;

    TRACE("(%p,%p)\n", hData, pcbDataSize);

    pDdh = (DDE_DATAHANDLE_HEAD*)GlobalLock(hMem);
    if (pDdh == NULL)
    {
      ERR("Failed on GlobalLock(%p)\n", hMem);
      return 0;
    }

    if (pcbDataSize != NULL)
    {
      *pcbDataSize = GlobalSize(hMem) - sizeof(DDE_DATAHANDLE_HEAD);
    }
    TRACE("=> %p (%lu) fmt %04x\n", pDdh + 1, GlobalSize(hMem) - sizeof(DDE_DATAHANDLE_HEAD), pDdh->cfFormat);
    return (LPBYTE)(pDdh + 1);
}

/*****************************************************************
 *            DdeUnaccessData (USER32.@)
 */
BOOL WINAPI DdeUnaccessData(HDDEDATA hData)
{
    HGLOBAL hMem = (HGLOBAL)hData;

    TRACE("(%p)\n", hData);

    GlobalUnlock(hMem);

    return TRUE;
}

/*****************************************************************
 *            DdeFreeDataHandle   (USER32.@)
 */
BOOL WINAPI DdeFreeDataHandle(HDDEDATA hData)
{
    TRACE("(%p)\n", hData);
    return GlobalFree((HGLOBAL)hData) == 0;
}

/******************************************************************
 *          WDML_IsAppOwned
 *
 *
 */
BOOL WDML_IsAppOwned(HDDEDATA hData)
{
    DDE_DATAHANDLE_HEAD*      pDdh;
    BOOL                        ret = FALSE;

    pDdh = (DDE_DATAHANDLE_HEAD*)GlobalLock((HGLOBAL)hData);
    if (pDdh != NULL)
    {
        ret = pDdh->bAppOwned;
        GlobalUnlock((HGLOBAL)hData);
    }
    return ret;
}

/* ================================================================
 *
 *                  Global <=> Data handle management
 *
 * ================================================================ */

/* Note: we use a DDEDATA, but layout of DDEDATA, DDEADVISE and DDEPOKE structures is similar:
 *    offset        size
 *    (bytes)      (bits)     comment
 *    0        16 bit fields for options (release, ackreq, response...)
 *    2        16 clipboard format
 *    4        ?  data to be used
 */
HDDEDATA        WDML_Global2DataHandle(HGLOBAL hMem, WINE_DDEHEAD* p)
{
    DDEDATA*    pDd;
    HDDEDATA      ret = 0;
    DWORD       size;

    if (hMem)
    {
        pDd = GlobalLock(hMem);
        size = GlobalSize(hMem) - sizeof(WINE_DDEHEAD);
        if (pDd)
        {
          if (p) memcpy(p, pDd, sizeof(WINE_DDEHEAD));
            switch (pDd->cfFormat)
            {
            default:
                FIXME("Unsupported format (%04x) for data %p, passing raw information\n",
                      pDd->cfFormat, hMem);
                /* fall thru */
            case 0:
            case CF_TEXT:
                ret = DdeCreateDataHandle(0, pDd->Value, size, 0, 0, pDd->cfFormat, 0);
                break;
            case CF_BITMAP:
                if (size >= sizeof(BITMAP))
                {
                    BITMAP*     bmp = (BITMAP*)pDd->Value;
                    int         count = bmp->bmWidthBytes * bmp->bmHeight * bmp->bmPlanes;
                    if (size >= sizeof(BITMAP) + count)
                    {
                        HBITMAP hbmp;

                        if ((hbmp = CreateBitmap(bmp->bmWidth, bmp->bmHeight,
                                                 bmp->bmPlanes, bmp->bmBitsPixel,
                                                 pDd->Value + sizeof(BITMAP))))
                        {
                            ret = DdeCreateDataHandle(0, (LPBYTE)&hbmp, sizeof(hbmp),
                                                      0, 0, CF_BITMAP, 0);
                        }
                        else ERR("Can't create bmp\n");
                    }
                    else
                    {
                        ERR("Wrong count: %lu / %d\n", size, sizeof(BITMAP) + count);
                    }
                } else ERR("No bitmap header\n");
                break;
            }
            GlobalUnlock(hMem);
        }
    }
    return ret;
}

/******************************************************************
 *          WDML_DataHandle2Global
 *
 *
 */
HGLOBAL WDML_DataHandle2Global(HDDEDATA hDdeData, BOOL fResponse, BOOL fRelease,
                         BOOL fDeferUpd, BOOL fAckReq)
{
    DDE_DATAHANDLE_HEAD*      pDdh;
    DWORD                       dwSize;
    HGLOBAL                     hMem = 0;

    dwSize = GlobalSize((HGLOBAL)hDdeData) - sizeof(DDE_DATAHANDLE_HEAD);
    pDdh = (DDE_DATAHANDLE_HEAD*)GlobalLock((HGLOBAL)hDdeData);
    if (dwSize && pDdh)
    {
        WINE_DDEHEAD*    wdh = NULL;

        switch (pDdh->cfFormat)
        {
        default:
            FIXME("Unsupported format (%04x) for data %p, passing raw information\n",
                   pDdh->cfFormat, hDdeData);
            /* fall thru */
        case 0:
        case CF_TEXT:
            hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(WINE_DDEHEAD) + dwSize);
            if (hMem && (wdh = GlobalLock(hMem)))
            {
                memcpy(wdh + 1, pDdh + 1, dwSize);
            }
            break;
        case CF_BITMAP:
            if (dwSize >= sizeof(HBITMAP))
            {
                BITMAP  bmp;
                DWORD   count;
                HBITMAP hbmp = *(HBITMAP*)(pDdh + 1);

                if (GetObjectW(hbmp, sizeof(bmp), &bmp))
                {
                    count = bmp.bmWidthBytes * bmp.bmHeight;
                    hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                       sizeof(WINE_DDEHEAD) + sizeof(bmp) + count);
                    if (hMem && (wdh = GlobalLock(hMem)))
                    {
                        memcpy(wdh + 1, &bmp, sizeof(bmp));
                        GetBitmapBits(hbmp, count, ((char*)(wdh + 1)) + sizeof(bmp));
                    }
                }
            }
            break;
        }
        if (wdh)
        {
            wdh->unused = 0;
            wdh->fResponse = fResponse;
            wdh->fRelease = fRelease;
            wdh->fDeferUpd = fDeferUpd;
            wdh->fAckReq = fAckReq;
            wdh->cfFormat = pDdh->cfFormat;
            GlobalUnlock(hMem);
        }
        GlobalUnlock((HGLOBAL)hDdeData);
    }

    return hMem;
}

/* ================================================================
 *
 *                Server management
 *
 * ================================================================ */

/******************************************************************
 *          WDML_AddServer
 *
 *
 */
WDML_SERVER*      WDML_AddServer(WDML_INSTANCE* pInstance, HSZ hszService, HSZ hszTopic)
{
    static const WCHAR fmtW[] = {'%','s','(','0','x','%','0','8','l','x',')',0};
    WDML_SERVER*  pServer;
    WCHAR         buf1[256];
    WCHAR         buf2[256];

    pServer = (WDML_SERVER*)HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_SERVER));
    if (pServer == NULL) return NULL;

    pServer->hszService = hszService;
    WDML_IncHSZ(pInstance, hszService);

    DdeQueryStringW(pInstance->instanceID, hszService, buf1, 256, CP_WINUNICODE);
    snprintfW(buf2, 256, fmtW, buf1, GetCurrentProcessId());
    pServer->hszServiceSpec = DdeCreateStringHandleW(pInstance->instanceID, buf2, CP_WINUNICODE);

    pServer->atomService = WDML_MakeAtomFromHsz(pServer->hszService);
    pServer->atomServiceSpec = WDML_MakeAtomFromHsz(pServer->hszServiceSpec);

    pServer->filterOn = TRUE;

    pServer->next = pInstance->servers;
    pInstance->servers = pServer;
    return pServer;
}

/******************************************************************
 *          WDML_RemoveServer
 *
 *
 */
void WDML_RemoveServer(WDML_INSTANCE* pInstance, HSZ hszService, HSZ hszTopic)
{
    WDML_SERVER*  pPrev = NULL;
    WDML_SERVER*  pServer = NULL;
    WDML_CONV*          pConv;
    WDML_CONV*          pConvNext;

    pServer = pInstance->servers;

    while (pServer != NULL)
    {
      if (DdeCmpStringHandles(pServer->hszService, hszService) == 0)
      {
          WDML_BroadcastDDEWindows(WDML_szEventClass, WM_WDML_UNREGISTER,
                             pServer->atomService, pServer->atomServiceSpec);
          /* terminate all conversations for given topic */
          for (pConv = pInstance->convs[WDML_SERVER_SIDE]; pConv != NULL; pConv = pConvNext)
          {
            pConvNext = pConv->next;
            if (DdeCmpStringHandles(pConv->hszService, hszService) == 0)
            {
                WDML_RemoveConv(pConv, WDML_SERVER_SIDE);
                /* don't care about return code (whether client window is present or not) */
                PostMessageW(pConv->hwndClient, WM_DDE_TERMINATE, (WPARAM)pConv->hwndServer, 0);
            }
          }
          if (pServer == pInstance->servers)
          {
            pInstance->servers = pServer->next;
          }
          else
          {
            pPrev->next = pServer->next;
          }

          DestroyWindow(pServer->hwndServer);
          WDML_DecHSZ(pInstance, pServer->hszServiceSpec);
          WDML_DecHSZ(pInstance, pServer->hszService);

          GlobalDeleteAtom(pServer->atomService);
          GlobalDeleteAtom(pServer->atomServiceSpec);

          HeapFree(GetProcessHeap(), 0, pServer);
          break;
      }

      pPrev = pServer;
      pServer = pServer->next;
    }
}

/*****************************************************************************
 *    WDML_FindServer
 *
 *    generic routine to return a pointer to the relevant ServiceNode
 *    for a given service name, or NULL if the entry does not exist
 *
 */
WDML_SERVER*      WDML_FindServer(WDML_INSTANCE* pInstance, HSZ hszService, HSZ hszTopic)
{
    WDML_SERVER*  pServer;

    for (pServer = pInstance->servers; pServer != NULL; pServer = pServer->next)
    {
      if (hszService == pServer->hszService)
      {
          return pServer;
      }
    }
    TRACE("Service name missing\n");
    return NULL;
}

/* ================================================================
 *
 *          Conversation management
 *
 * ================================================================ */

/******************************************************************
 *          WDML_AddConv
 *
 *
 */
WDML_CONV*  WDML_AddConv(WDML_INSTANCE* pInstance, WDML_SIDE side,
                       HSZ hszService, HSZ hszTopic, HWND hwndClient, HWND hwndServer)
{
    WDML_CONV*    pConv;

    /* no converstation yet, add it */
    pConv = HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_CONV));
    if (!pConv) return NULL;

    pConv->instance = pInstance;
    WDML_IncHSZ(pInstance, pConv->hszService = hszService);
    WDML_IncHSZ(pInstance, pConv->hszTopic = hszTopic);
    pConv->hwndServer = hwndServer;
    pConv->hwndClient = hwndClient;
    pConv->transactions = NULL;
    pConv->hUser = 0;
    pConv->wStatus = (side == WDML_CLIENT_SIDE) ? ST_CLIENT : 0L;
    /* check if both side of the conversation are of the same instance */
    if (GetWindowThreadProcessId(hwndClient, NULL) == GetWindowThreadProcessId(hwndServer, NULL) &&
      WDML_GetInstanceFromWnd(hwndClient) == WDML_GetInstanceFromWnd(hwndServer))
    {
      pConv->wStatus |= ST_ISSELF;
    }
    pConv->wConvst = XST_NULL;

    pConv->next = pInstance->convs[side];
    pInstance->convs[side] = pConv;

    return pConv;
}

/******************************************************************
 *          WDML_FindConv
 *
 *
 */
WDML_CONV*  WDML_FindConv(WDML_INSTANCE* pInstance, WDML_SIDE side,
                        HSZ hszService, HSZ hszTopic)
{
    WDML_CONV*    pCurrent = NULL;

    for (pCurrent = pInstance->convs[side]; pCurrent != NULL; pCurrent = pCurrent->next)
    {
      if (DdeCmpStringHandles(pCurrent->hszService, hszService) == 0 &&
          DdeCmpStringHandles(pCurrent->hszTopic, hszTopic) == 0)
      {
          return pCurrent;
      }

    }
    return NULL;
}

/******************************************************************
 *          WDML_RemoveConv
 *
 *
 */
void WDML_RemoveConv(WDML_CONV* pRef, WDML_SIDE side)
{
    WDML_CONV*    pPrev = NULL;
    WDML_CONV*    pCurrent;
    WDML_XACT*    pXAct;
    WDML_XACT*    pXActNext;
    HWND    hWnd;

    if (!pRef)
      return;

    /* remove any pending transaction */
    for (pXAct = pRef->transactions; pXAct != NULL; pXAct = pXActNext)
    {
      pXActNext = pXAct->next;
      WDML_FreeTransaction(pRef->instance, pXAct, TRUE);
    }

    WDML_RemoveAllLinks(pRef->instance, pRef, side);

    /* FIXME: should we keep the window around ? it seems so (at least on client side
     * to let QueryConvInfo work after conv termination, but also to implement
     * DdeReconnect...
     */
    /* destroy conversation window, but first remove pConv from hWnd.
     * this would help the wndProc do appropriate handling upon a WM_DESTROY message
     */
    hWnd = (side == WDML_CLIENT_SIDE) ? pRef->hwndClient : pRef->hwndServer;
    SetWindowLongPtrW(hWnd, GWL_WDML_CONVERSATION, 0);

    DestroyWindow((side == WDML_CLIENT_SIDE) ? pRef->hwndClient : pRef->hwndServer);

    WDML_DecHSZ(pRef->instance, pRef->hszService);
    WDML_DecHSZ(pRef->instance, pRef->hszTopic);

    for (pCurrent = pRef->instance->convs[side]; pCurrent != NULL; pCurrent = (pPrev = pCurrent)->next)
    {
      if (pCurrent == pRef)
      {
          if (pCurrent == pRef->instance->convs[side])
          {
            pRef->instance->convs[side] = pCurrent->next;
          }
          else
          {
            pPrev->next = pCurrent->next;
          }

          HeapFree(GetProcessHeap(), 0, pCurrent);
          break;
      }
    }
}

/******************************************************************
 *              WDML_EnableCallback
 */
static BOOL WDML_EnableCallback(WDML_CONV *pConv, UINT wCmd)
{
    if (wCmd == EC_DISABLE)
    {
        FIXME("EC_DISABLE is not implemented\n");
        return TRUE;
    }

    if (wCmd == EC_QUERYWAITING)
        return pConv->transactions ? TRUE : FALSE;

    if (wCmd != EC_ENABLEALL && wCmd != EC_ENABLEONE)
    {
        FIXME("Unknown command code %04x\n", wCmd);
        return FALSE;
    }

    while (pConv->transactions)
    {
        WDML_XACT *pXAct = pConv->transactions;
        WDML_UnQueueTransaction(pConv, pXAct);

        if (pConv->wStatus & ST_CLIENT)
        {
            /*WDML_ClientHandle(pConv, pXAct);*/
            FIXME("Client delayed transaction queue handling is not supported\n");
        }
        else
            WDML_ServerHandle(pConv, pXAct);

        WDML_FreeTransaction(pConv->instance, pXAct, TRUE);

        if (wCmd == EC_ENABLEONE) break;
    }
    return TRUE;
}

/*****************************************************************
 *            DdeEnableCallback (USER32.@)
 */
BOOL WINAPI DdeEnableCallback(DWORD idInst, HCONV hConv, UINT wCmd)
{
    BOOL ret = FALSE;
    WDML_CONV *pConv;

    TRACE("(%ld, %p, %04x)\n", idInst, hConv, wCmd);

    EnterCriticalSection(&WDML_CritSect);

    pConv = WDML_GetConv(hConv, TRUE);

    if (pConv && pConv->instance->instanceID == idInst)
        ret = WDML_EnableCallback(pConv, wCmd);

    LeaveCriticalSection(&WDML_CritSect);
    return ret;
}

/******************************************************************
 *          WDML_GetConv
 *
 *
 */
WDML_CONV*  WDML_GetConv(HCONV hConv, BOOL checkConnected)
{
    WDML_CONV*    pConv = (WDML_CONV*)hConv;

    /* FIXME: should do better checking */
    if (pConv == NULL) return NULL;

    if (checkConnected && !(pConv->wStatus & ST_CONNECTED))
    {
      FIXME("found conv but ain't connected\n");
      return NULL;
    }
    if (GetCurrentThreadId() != pConv->instance->threadID)
    {
      FIXME("wrong thread ID\n");
      return NULL;
    }

    return pConv;
}

/******************************************************************
 *          WDML_GetConvFromWnd
 *
 *
 */
WDML_CONV*  WDML_GetConvFromWnd(HWND hWnd)
{
    return (WDML_CONV*)GetWindowLongPtrW(hWnd, GWL_WDML_CONVERSATION);
}

/******************************************************************
 *          WDML_PostAck
 *
 *
 */
BOOL        WDML_PostAck(WDML_CONV* pConv, WDML_SIDE side, WORD appRetCode,
                       BOOL fBusy, BOOL fAck, UINT pmt, LPARAM lParam, UINT oldMsg)
{
    DDEACK  ddeAck;
    HWND    from, to;

    if (side == WDML_SERVER_SIDE)
    {
      from = pConv->hwndServer;
      to   = pConv->hwndClient;
    }
    else
    {
      to   = pConv->hwndServer;
      from = pConv->hwndClient;
    }

    ddeAck.bAppReturnCode = appRetCode;
    ddeAck.reserved       = 0;
    ddeAck.fBusy          = fBusy;
    ddeAck.fAck           = fAck;

    TRACE("Posting a %s ack\n", ddeAck.fAck ? "positive" : "negative");

    lParam = (lParam) ? ReuseDDElParam(lParam, oldMsg, WM_DDE_ACK, *(WORD*)&ddeAck, pmt) :
        PackDDElParam(WM_DDE_ACK, *(WORD*)&ddeAck, pmt);
    if (!PostMessageW(to, WM_DDE_ACK, (WPARAM)from, lParam))
    {
      pConv->wStatus &= ~ST_CONNECTED;
        FreeDDElParam(WM_DDE_ACK, lParam);
        return FALSE;
    }
    return TRUE;
}

/*****************************************************************
 *            DdeSetUserHandle (USER32.@)
 */
BOOL WINAPI DdeSetUserHandle(HCONV hConv, DWORD id, DWORD hUser)
{
    WDML_CONV*    pConv;
    BOOL    ret = TRUE;

    TRACE("(%p,%lx,%lx)\n", hConv, id, hUser);

    EnterCriticalSection(&WDML_CritSect);

    pConv = WDML_GetConv(hConv, FALSE);
    if (pConv == NULL)
    {
      ret = FALSE;
      goto theError;
    }
    if (id == QID_SYNC)
    {
      pConv->hUser = hUser;
    }
    else
    {
      WDML_XACT*  pXAct;

      pXAct = WDML_FindTransaction(pConv, id);
      if (pXAct)
      {
          pXAct->hUser = hUser;
      }
      else
      {
          pConv->instance->lastError = DMLERR_UNFOUND_QUEUE_ID;
          ret = FALSE;
      }
    }
 theError:
    LeaveCriticalSection(&WDML_CritSect);
    return ret;
}

/******************************************************************
 *          WDML_GetLocalConvInfo
 *
 *
 */
static      BOOL  WDML_GetLocalConvInfo(WDML_CONV* pConv, CONVINFO* ci, DWORD id)
{
    BOOL    ret = TRUE;
    WDML_LINK*    pLink;
    WDML_SIDE     side;

    ci->hConvPartner = (pConv->wStatus & ST_ISLOCAL) ? (HCONV)((ULONG_PTR)pConv | 1) : 0;
    ci->hszSvcPartner = pConv->hszService;
    ci->hszServiceReq = pConv->hszService; /* FIXME: they shouldn't be the same, should they ? */
    ci->hszTopic = pConv->hszTopic;
    ci->wStatus = pConv->wStatus;

    side = (pConv->wStatus & ST_CLIENT) ? WDML_CLIENT_SIDE : WDML_SERVER_SIDE;

    for (pLink = pConv->instance->links[side]; pLink != NULL; pLink = pLink->next)
    {
      if (pLink->hConv == (HCONV)pConv)
      {
          ci->wStatus |= ST_ADVISE;
          break;
      }
    }

    /* FIXME: non handled status flags:
       ST_BLOCKED
       ST_BLOCKNEXT
       ST_INLIST
    */

    ci->wConvst = pConv->wConvst; /* FIXME */

    ci->wLastError = 0; /* FIXME: note it's not the instance last error */
    ci->hConvList = 0;
    ci->ConvCtxt = pConv->convContext;
    if (ci->wStatus & ST_CLIENT)
    {
      ci->hwnd = pConv->hwndClient;
      ci->hwndPartner = pConv->hwndServer;
    }
    else
    {
      ci->hwnd = pConv->hwndServer;
      ci->hwndPartner = pConv->hwndClient;
    }
    if (id == QID_SYNC)
    {
      ci->hUser = pConv->hUser;
      ci->hszItem = 0;
      ci->wFmt = 0;
      ci->wType = 0;
    }
    else
    {
      WDML_XACT*  pXAct;

      pXAct = WDML_FindTransaction(pConv, id);
      if (pXAct)
      {
          ci->hUser = pXAct->hUser;
          ci->hszItem = pXAct->hszItem;
          ci->wFmt = pXAct->wFmt;
          ci->wType = pXAct->wType;
      }
      else
      {
          ret = 0;
          pConv->instance->lastError = DMLERR_UNFOUND_QUEUE_ID;
      }
    }
    return ret;
}

/******************************************************************
 *          DdeQueryConvInfo (USER32.@)
 *
 */
UINT WINAPI DdeQueryConvInfo(HCONV hConv, DWORD id, PCONVINFO lpConvInfo)
{
    UINT    ret = lpConvInfo->cb;
    CONVINFO      ci;
    WDML_CONV*    pConv;

    TRACE("(%p,%lx,%p)\n", hConv, id, lpConvInfo);

    if (!hConv)
    {
        FIXME("hConv is NULL\n");
        return 0;
    }

    EnterCriticalSection(&WDML_CritSect);

    pConv = WDML_GetConv(hConv, FALSE);
    if (pConv != NULL && !WDML_GetLocalConvInfo(pConv, &ci, id))
    {
      ret = 0;
    }
    else if ((ULONG_PTR)hConv & 1)
    {
      pConv = WDML_GetConv((HCONV)((ULONG_PTR)hConv & ~1), FALSE);
      if (pConv != NULL)
      {
          FIXME("Request on remote conversation information is not implemented yet\n");
          ret = 0;
      }
    }
    LeaveCriticalSection(&WDML_CritSect);
    if (ret != 0)
      memcpy(lpConvInfo, &ci, min((size_t)lpConvInfo->cb, sizeof(ci)));
    return ret;
}

/* ================================================================
 *
 *                Link (hot & warm) management
 *
 * ================================================================ */

/******************************************************************
 *          WDML_AddLink
 *
 *
 */
void WDML_AddLink(WDML_INSTANCE* pInstance, HCONV hConv, WDML_SIDE side,
              UINT wType, HSZ hszItem, UINT wFmt)
{
    WDML_LINK*    pLink;

    pLink = HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_LINK));
    if (pLink == NULL)
    {
      ERR("OOM\n");
      return;
    }

    pLink->hConv = hConv;
    pLink->transactionType = wType;
    WDML_IncHSZ(pInstance, pLink->hszItem = hszItem);
    pLink->uFmt = wFmt;
    pLink->next = pInstance->links[side];
    pInstance->links[side] = pLink;
}

/******************************************************************
 *          WDML_RemoveLink
 *
 *
 */
void WDML_RemoveLink(WDML_INSTANCE* pInstance, HCONV hConv, WDML_SIDE side,
                 HSZ hszItem, UINT uFmt)
{
    WDML_LINK* pPrev = NULL;
    WDML_LINK* pCurrent = NULL;

    pCurrent = pInstance->links[side];

    while (pCurrent != NULL)
    {
      if (pCurrent->hConv == hConv &&
          DdeCmpStringHandles(pCurrent->hszItem, hszItem) == 0 &&
          pCurrent->uFmt == uFmt)
      {
          if (pCurrent == pInstance->links[side])
          {
            pInstance->links[side] = pCurrent->next;
          }
          else
          {
            pPrev->next = pCurrent->next;
          }

          WDML_DecHSZ(pInstance, pCurrent->hszItem);
          HeapFree(GetProcessHeap(), 0, pCurrent);
          break;
      }

      pPrev = pCurrent;
      pCurrent = pCurrent->next;
    }
}

/* this function is called to remove all links related to the conv.
   It should be called from both client and server when terminating
   the conversation.
*/
/******************************************************************
 *          WDML_RemoveAllLinks
 *
 *
 */
void WDML_RemoveAllLinks(WDML_INSTANCE* pInstance, WDML_CONV* pConv, WDML_SIDE side)
{
    WDML_LINK* pPrev = NULL;
    WDML_LINK* pCurrent = NULL;
    WDML_LINK* pNext = NULL;

    pCurrent = pInstance->links[side];

    while (pCurrent != NULL)
    {
      if (pCurrent->hConv == (HCONV)pConv)
      {
          if (pCurrent == pInstance->links[side])
          {
            pInstance->links[side] = pCurrent->next;
            pNext = pCurrent->next;
          }
          else
          {
            pPrev->next = pCurrent->next;
            pNext = pCurrent->next;
          }

          WDML_DecHSZ(pInstance, pCurrent->hszItem);

          HeapFree(GetProcessHeap(), 0, pCurrent);
          pCurrent = NULL;
      }

      if (pCurrent)
      {
          pPrev = pCurrent;
          pCurrent = pCurrent->next;
      }
      else
      {
          pCurrent = pNext;
      }
    }
}

/******************************************************************
 *          WDML_FindLink
 *
 *
 */
WDML_LINK*  WDML_FindLink(WDML_INSTANCE* pInstance, HCONV hConv, WDML_SIDE side,
                        HSZ hszItem, BOOL use_fmt, UINT uFmt)
{
    WDML_LINK*    pCurrent = NULL;

    for (pCurrent = pInstance->links[side]; pCurrent != NULL; pCurrent = pCurrent->next)
    {
      /* we don't need to check for transaction type as it can be altered */

      if (pCurrent->hConv == hConv &&
          DdeCmpStringHandles(pCurrent->hszItem, hszItem) == 0 &&
          (!use_fmt || pCurrent->uFmt == uFmt))
      {
          break;
      }

    }

    return pCurrent;
}

/* ================================================================
 *
 *                Transaction management
 *
 * ================================================================ */

/******************************************************************
 *          WDML_AllocTransaction
 *
 * Alloc a transaction structure for handling the message ddeMsg
 */
WDML_XACT*  WDML_AllocTransaction(WDML_INSTANCE* pInstance, UINT ddeMsg,
                              UINT wFmt, HSZ hszItem)
{
    WDML_XACT*          pXAct;
    static WORD         tid = 1;    /* FIXME: wrap around */

    pXAct = HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_XACT));
    if (!pXAct)
    {
      pInstance->lastError = DMLERR_MEMORY_ERROR;
      return NULL;
    }

    pXAct->xActID = tid++;
    pXAct->ddeMsg = ddeMsg;
    pXAct->hDdeData = 0;
    pXAct->hUser = 0;
    pXAct->next = NULL;
    pXAct->wType = 0;
    pXAct->wFmt = wFmt;
    if ((pXAct->hszItem = hszItem)) WDML_IncHSZ(pInstance, pXAct->hszItem);
    pXAct->atom = 0;
    pXAct->hMem = 0;
    pXAct->lParam = 0;

    return pXAct;
}

/******************************************************************
 *          WDML_QueueTransaction
 *
 * Adds a transaction to the list of transaction
 */
void  WDML_QueueTransaction(WDML_CONV* pConv, WDML_XACT* pXAct)
{
    WDML_XACT**   pt;

    /* advance to last in queue */
    for (pt = &pConv->transactions; *pt != NULL; pt = &(*pt)->next);
    *pt = pXAct;
}

/******************************************************************
 *          WDML_UnQueueTransaction
 *
 *
 */
BOOL  WDML_UnQueueTransaction(WDML_CONV* pConv, WDML_XACT*  pXAct)
{
    WDML_XACT**   pt;

    for (pt = &pConv->transactions; *pt; pt = &(*pt)->next)
    {
      if (*pt == pXAct)
      {
          *pt = pXAct->next;
          return TRUE;
      }
    }
    return FALSE;
}

/******************************************************************
 *          WDML_FreeTransaction
 *
 *
 */
void  WDML_FreeTransaction(WDML_INSTANCE* pInstance, WDML_XACT* pXAct, BOOL doFreePmt)
{
    /* free pmt(s) in pXAct too. check against one for not deleting TRUE return values */
    if (doFreePmt && (ULONG_PTR)pXAct->hMem > 1)
    {
      GlobalFree(pXAct->hMem);
    }
    if (pXAct->hszItem) WDML_DecHSZ(pInstance, pXAct->hszItem);

    HeapFree(GetProcessHeap(), 0, pXAct);
}

/******************************************************************
 *          WDML_FindTransaction
 *
 *
 */
WDML_XACT*  WDML_FindTransaction(WDML_CONV* pConv, DWORD tid)
{
    WDML_XACT* pXAct;

    tid = HIWORD(tid);
    for (pXAct = pConv->transactions; pXAct; pXAct = pXAct->next)
    {
      if (pXAct->xActID == tid)
          break;
    }
    return pXAct;
}

/* ================================================================
 *
 *       Information broadcast across DDEML implementations
 *
 * ================================================================ */

struct tagWDML_BroadcastPmt
{
    LPCWSTR clsName;
    UINT    uMsg;
    WPARAM  wParam;
    LPARAM  lParam;
};

/******************************************************************
 *          WDML_BroadcastEnumProc
 *
 *
 */
static      BOOL CALLBACK WDML_BroadcastEnumProc(HWND hWnd, LPARAM lParam)
{
    struct tagWDML_BroadcastPmt*    s = (struct tagWDML_BroadcastPmt*)lParam;
    WCHAR                     buffer[128];

    if (GetClassNameW(hWnd, buffer, 128) > 0 &&
      lstrcmpiW(buffer, s->clsName) == 0)
    {
      PostMessageW(hWnd, s->uMsg, s->wParam, s->lParam);
    }
    return TRUE;
}

/******************************************************************
 *          WDML_BroadcastDDEWindows
 *
 *
 */
void WDML_BroadcastDDEWindows(LPCWSTR clsName, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    struct tagWDML_BroadcastPmt     s;

    s.clsName = clsName;
    s.uMsg    = uMsg;
    s.wParam  = wParam;
    s.lParam  = lParam;
    EnumWindows(WDML_BroadcastEnumProc, (LPARAM)&s);
}

Generated by  Doxygen 1.6.0   Back to index