/////////////////////////////////////////////////////////////////////////////
// Name:        msw/dde.cpp
// Purpose:     DDE classes
// Author:      Julian Smart
// Modified by:
// Created:     01/02/97
// RCS-ID:      $Id: dde.cpp,v 1.42 2004/08/24 10:31:41 ABX Exp $
// Copyright:   (c) Julian Smart
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
    #pragma implementation "dde.h"
#endif

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#if wxUSE_IPC

#ifndef WX_PRECOMP
    #include "wx/utils.h"
    #include "wx/app.h"
#endif

#include "wx/module.h"
#include "wx/dde.h"
#include "wx/intl.h"
#include "wx/hashmap.h"

#include "wx/msw/private.h"

#include <string.h>
#include <ddeml.h>

#ifdef __GNUWIN32_OLD__
    #include "wx/msw/gnuwin32/extra.h"
#endif

// ----------------------------------------------------------------------------
// macros and constants
// ----------------------------------------------------------------------------

#ifdef __WIN32__
    #define _EXPORT
#else
    #define _EXPORT _export
#endif

#if wxUSE_UNICODE
    #define DDE_CP      CP_WINUNICODE
#else
    #define DDE_CP      CP_WINANSI
#endif

#define GetHConv()       ((HCONV)m_hConv)

// default timeout for DDE operations (5sec)
#define DDE_TIMEOUT     5000

// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------

static wxDDEConnection *DDEFindConnection(HCONV hConv);
static void DDEDeleteConnection(HCONV hConv);
static wxDDEServer *DDEFindServer(const wxString& s);

extern "C" HDDEDATA EXPENTRY _EXPORT _DDECallback(WORD wType,
                                                  WORD wFmt,
                                                  HCONV hConv,
                                                  HSZ hsz1,
                                                  HSZ hsz2,
                                                  HDDEDATA hData,
                                                  DWORD lData1,
                                                  DWORD lData2);

// Add topic name to atom table before using in conversations
static HSZ DDEAddAtom(const wxString& string);
static HSZ DDEGetAtom(const wxString& string);

// string handles
static HSZ DDEAtomFromString(const wxString& s);
static wxString DDEStringFromAtom(HSZ hsz);
static void DDEFreeString(HSZ hsz);

// error handling
static wxString DDEGetErrorMsg(UINT error);
static void DDELogError(const wxString& s, UINT error = DMLERR_NO_ERROR);

// ----------------------------------------------------------------------------
// global variables
// ----------------------------------------------------------------------------

WX_DECLARE_STRING_HASH_MAP( HSZ, wxAtomMap );

static DWORD DDEIdInst = 0L;
static wxDDEConnection *DDECurrentlyConnecting = NULL;
static wxAtomMap wxAtomTable;

#include "wx/listimpl.cpp"

WX_DEFINE_LIST(wxDDEClientList);
WX_DEFINE_LIST(wxDDEServerList);
WX_DEFINE_LIST(wxDDEConnectionList);

static wxDDEClientList wxDDEClientObjects;
static wxDDEServerList wxDDEServerObjects;

static bool DDEInitialized = false;

// ----------------------------------------------------------------------------
// private classes
// ----------------------------------------------------------------------------

// A module to allow DDE cleanup without calling these functions
// from app.cpp or from the user's application.

class wxDDEModule : public wxModule
{
public:
    wxDDEModule() {}
    bool OnInit() { return true; }
    void OnExit() { wxDDECleanUp(); }

private:
    DECLARE_DYNAMIC_CLASS(wxDDEModule)
};

// ----------------------------------------------------------------------------
// wxWin macros
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS(wxDDEServer, wxServerBase)
IMPLEMENT_DYNAMIC_CLASS(wxDDEClient, wxClientBase)
IMPLEMENT_CLASS(wxDDEConnection, wxConnectionBase)
IMPLEMENT_DYNAMIC_CLASS(wxDDEModule, wxModule)

// ============================================================================
// implementation
// ============================================================================

// ----------------------------------------------------------------------------
// initialization and cleanup
// ----------------------------------------------------------------------------

extern void wxDDEInitialize()
{
    if ( !DDEInitialized )
    {
        // Should insert filter flags
        PFNCALLBACK callback = (PFNCALLBACK)
            MakeProcInstance((FARPROC)_DDECallback, wxGetInstance());
        UINT rc = DdeInitialize(&DDEIdInst, callback, APPCLASS_STANDARD, 0L);
        if ( rc != DMLERR_NO_ERROR )
        {
            DDELogError(_T("Failed to initialize DDE"), rc);
        }
        else
        {
            DDEInitialized = true;
        }
    }
}

void wxDDECleanUp()
{
    // deleting them later won't work as DDE won't be initialized any more
    wxASSERT_MSG( wxDDEServerObjects.empty() &&
                    wxDDEClientObjects.empty(),
                    _T("all DDE objects should be deleted by now") );

    wxAtomTable.clear();

    if ( DDEIdInst != 0 )
    {
        DdeUninitialize(DDEIdInst);
        DDEIdInst = 0;
    }
}

// ----------------------------------------------------------------------------
// functions working with the global connection list(s)
// ----------------------------------------------------------------------------

// Global find connection
static wxDDEConnection *DDEFindConnection(HCONV hConv)
{
    wxDDEServerList::compatibility_iterator serverNode = wxDDEServerObjects.GetFirst();
    wxDDEConnection *found = NULL;
    while (serverNode && !found)
    {
        wxDDEServer *object = serverNode->GetData();
        found = object->FindConnection((WXHCONV) hConv);
        serverNode = serverNode->GetNext();
    }

    if (found)
    {
        return found;
    }

    wxDDEClientList::compatibility_iterator clientNode = wxDDEClientObjects.GetFirst();
    while (clientNode && !found)
    {
        wxDDEClient *object = clientNode->GetData();
        found = object->FindConnection((WXHCONV) hConv);
        clientNode = clientNode->GetNext();
    }
    return found;
}

// Global delete connection
static void DDEDeleteConnection(HCONV hConv)
{
    wxDDEServerList::compatibility_iterator serverNode = wxDDEServerObjects.GetFirst();
    bool found = false;
    while (serverNode && !found)
    {
        wxDDEServer *object = serverNode->GetData();
        found = object->DeleteConnection((WXHCONV) hConv);
        serverNode = serverNode->GetNext();
    }
    if (found)
    {
        return;
    }

    wxDDEClientList::compatibility_iterator clientNode = wxDDEClientObjects.GetFirst();
    while (clientNode && !found)
    {
        wxDDEClient *object = clientNode->GetData();
        found = object->DeleteConnection((WXHCONV) hConv);
        clientNode = clientNode->GetNext();
    }
}

// Find a server from a service name
static wxDDEServer *DDEFindServer(const wxString& s)
{
    wxDDEServerList::compatibility_iterator node = wxDDEServerObjects.GetFirst();
    wxDDEServer *found = NULL;
    while (node && !found)
    {
        wxDDEServer *object = node->GetData();

        if (object->GetServiceName() == s)
        {
            found = object;
        }
        else
        {
            node = node->GetNext();
        }
    }

    return found;
}

// ----------------------------------------------------------------------------
// wxDDEServer
// ----------------------------------------------------------------------------

wxDDEServer::wxDDEServer()
{
    wxDDEInitialize();

    wxDDEServerObjects.Append(this);
}

bool wxDDEServer::Create(const wxString& server)
{
    m_serviceName = server;

    HSZ hsz = DDEAtomFromString(server);

    if ( !hsz )
    {
        return false;
    }


    bool success = (DdeNameService(DDEIdInst, hsz, (HSZ) NULL, DNS_REGISTER)
        != NULL);

    if (!success)
    {
        DDELogError(wxString::Format(_("Failed to register DDE server '%s'"),
            server.c_str()));
    }

    DDEFreeString(hsz);

    return success;
}

wxDDEServer::~wxDDEServer()
{
    if ( !m_serviceName.IsEmpty() )
    {
        HSZ hsz = DDEAtomFromString(m_serviceName);

        if (hsz)
        {
            if ( !DdeNameService(DDEIdInst, hsz,
                (HSZ) NULL, DNS_UNREGISTER) )
            {
                DDELogError(wxString::Format(
                    _("Failed to unregister DDE server '%s'"),
                    m_serviceName.c_str()));
            }

            DDEFreeString(hsz);
        }
    }

    wxDDEServerObjects.DeleteObject(this);

    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        wxDDEConnectionList::compatibility_iterator next = node->GetNext();
        connection->SetConnected(false);
        connection->OnDisconnect(); // May delete the node implicitly
        node = next;
    }

    // If any left after this, delete them
    node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        wxDDEConnectionList::compatibility_iterator next = node->GetNext();
        delete connection;
        node = next;
    }
}

wxConnectionBase *wxDDEServer::OnAcceptConnection(const wxString& /* topic */)
{
    return new wxDDEConnection;
}

wxDDEConnection *wxDDEServer::FindConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    wxDDEConnection *found = NULL;
    while (node && !found)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
            found = connection;
        else node = node->GetNext();
    }
    return found;
}

// Only delete the entry in the map, not the actual connection
bool wxDDEServer::DeleteConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
        {
            m_connections.Erase(node);
            return true;
        }
        else
        {
            node = node->GetNext();
        }
    }
    return false;
}

// ----------------------------------------------------------------------------
// wxDDEClient
// ----------------------------------------------------------------------------

wxDDEClient::wxDDEClient()
{
    wxDDEInitialize();

    wxDDEClientObjects.Append(this);
}

wxDDEClient::~wxDDEClient()
{
    wxDDEClientObjects.DeleteObject(this);
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        delete connection;  // Deletes the node implicitly (see ~wxDDEConnection)
        node = m_connections.GetFirst();
    }
}

bool wxDDEClient::ValidHost(const wxString& /* host */)
{
    return true;
}

wxConnectionBase *wxDDEClient::MakeConnection(const wxString& WXUNUSED(host),
                                              const wxString& server,
                                              const wxString& topic)
{
    HSZ hszServer = DDEAtomFromString(server);

    if ( !hszServer )
    {
        return (wxConnectionBase*) NULL;
    }


    HSZ hszTopic = DDEAtomFromString(topic);

    if ( !hszTopic )
    {
        DDEFreeString(hszServer);
        return (wxConnectionBase*) NULL;
    }


    HCONV hConv = ::DdeConnect(DDEIdInst, hszServer, hszTopic,
        (PCONVCONTEXT) NULL);

    DDEFreeString(hszServer);
    DDEFreeString(hszTopic);


    if ( !hConv )
    {
        DDELogError( wxString::Format(
            _("Failed to create connection to server '%s' on topic '%s'"),
            server.c_str(), topic.c_str()) );
    }
    else
    {
        wxDDEConnection *connection = (wxDDEConnection*) OnMakeConnection();
        if (connection)
        {
            connection->m_hConv = (WXHCONV) hConv;
            connection->m_topicName = topic;
            connection->m_client = this;
            m_connections.Append(connection);
            return connection;
        }
    }

    return (wxConnectionBase*) NULL;
}

wxConnectionBase *wxDDEClient::OnMakeConnection()
{
    return new wxDDEConnection;
}

wxDDEConnection *wxDDEClient::FindConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    wxDDEConnection *found = NULL;
    while (node && !found)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
            found = connection;
        else node = node->GetNext();
    }
    return found;
}

// Only delete the entry in the map, not the actual connection
bool wxDDEClient::DeleteConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
        {
            m_connections.Erase(node);
            return true;
        }
        else node = node->GetNext();
    }
    return false;
}

// ----------------------------------------------------------------------------
// wxDDEConnection
// ----------------------------------------------------------------------------

wxDDEConnection::wxDDEConnection(wxChar *buffer, int size)
     : wxConnectionBase(buffer, size)
{
    m_client = NULL;
    m_server = NULL;

    m_hConv = 0;
    m_sendingData = NULL;
}

wxDDEConnection::wxDDEConnection()
     : wxConnectionBase()
{
    m_hConv = 0;
    m_sendingData = NULL;
    m_server = NULL;
    m_client = NULL;
}

wxDDEConnection::~wxDDEConnection()
{
    Disconnect();
    if (m_server)
        m_server->GetConnections().DeleteObject(this);
    else
        m_client->GetConnections().DeleteObject(this);
}

// Calls that CLIENT can make
bool wxDDEConnection::Disconnect()
{
    if ( !GetConnected() )
        return true;

    DDEDeleteConnection(GetHConv());

    bool ok = DdeDisconnect(GetHConv()) != 0;
    if ( !ok )
    {
        DDELogError(_T("Failed to disconnect from DDE server gracefully"));
    }

    SetConnected( false );  // so we don't try and disconnect again

    return ok;
}

bool wxDDEConnection::Execute(const wxChar *data, int size, wxIPCFormat format)
{
    DWORD result;
    if (size < 0)
    {
        size = wxStrlen(data) + 1;
    }

    bool ok = DdeClientTransaction((LPBYTE)data,
                                    size * sizeof(wxChar),
                                    GetHConv(),
                                    NULL,
                                    format,
                                    XTYP_EXECUTE,
                                    DDE_TIMEOUT,
                                    &result) != 0;

    if ( !ok )
    {
        DDELogError(_T("DDE execute request failed"));
    }

    return ok;
}

wxChar *wxDDEConnection::Request(const wxString& item, int *size, wxIPCFormat format)
{
    DWORD result;

    HSZ atom = DDEGetAtom(item);

    HDDEDATA returned_data = DdeClientTransaction(NULL, 0,
                                                  GetHConv(),
                                                  atom, format,
                                                  XTYP_REQUEST,
                                                  DDE_TIMEOUT,
                                                  &result);
    if ( !returned_data )
    {
        DDELogError(_T("DDE data request failed"));

        return NULL;
    }

    DWORD len = DdeGetData(returned_data, NULL, 0, 0);

    wxChar *data = GetBufferAtLeast( len/sizeof(wxChar) );
    wxASSERT_MSG(data != NULL,
                 _T("Buffer too small in wxDDEConnection::Request") );
    (void) DdeGetData(returned_data, (LPBYTE)data, len, 0);

    (void) DdeFreeDataHandle(returned_data);

    if (size)
        *size = (int)len/sizeof(wxChar);

    return data;
}

bool wxDDEConnection::Poke(const wxString& item, wxChar *data, int size, wxIPCFormat format)
{
    DWORD result;
    if (size < 0)
    {
        size = wxStrlen(data) + 1;
    }

    HSZ item_atom = DDEGetAtom(item);
    bool ok = DdeClientTransaction((LPBYTE)data,
                                   size * sizeof(wxChar),
                                   GetHConv(),
                                   item_atom, format,
                                   XTYP_POKE,
                                   DDE_TIMEOUT,
                                   &result) != 0;
    if ( !ok )
    {
        DDELogError(_("DDE poke request failed"));
    }

    return ok;
}

bool wxDDEConnection::StartAdvise(const wxString& item)
{
    DWORD result;
    HSZ atom = DDEGetAtom(item);

    bool ok = DdeClientTransaction(NULL, 0,
                                   GetHConv(),
                                   atom, CF_TEXT,
                                   XTYP_ADVSTART,
                                   DDE_TIMEOUT,
                                   &result) != 0;
    if ( !ok )
    {
        DDELogError(_("Failed to establish an advise loop with DDE server"));
    }

    return ok;
}

bool wxDDEConnection::StopAdvise(const wxString& item)
{
    DWORD result;
    HSZ atom = DDEGetAtom(item);

    bool ok = DdeClientTransaction(NULL, 0,
                                   GetHConv(),
                                   atom, CF_TEXT,
                                   XTYP_ADVSTOP,
                                   DDE_TIMEOUT,
                                   &result) != 0;
    if ( !ok )
    {
        DDELogError(_("Failed to terminate the advise loop with DDE server"));
    }

    return ok;
}

// Calls that SERVER can make
bool wxDDEConnection::Advise(const wxString& item,
                             wxChar *data,
                             int size,
                             wxIPCFormat format)
{
    if (size < 0)
    {
        size = wxStrlen(data) + 1;
    }

    HSZ item_atom = DDEGetAtom(item);
    HSZ topic_atom = DDEGetAtom(m_topicName);
    m_sendingData = data;  // mrf: potential for scope problems here?
    m_dataSize = size;
    m_dataType = format;

    bool ok = DdePostAdvise(DDEIdInst, topic_atom, item_atom) != 0;
    if ( !ok )
    {
        DDELogError(_("Failed to send DDE advise notification"));
    }

    return ok;
}

bool wxDDEConnection::OnDisconnect()
{
    delete this;
    return true;
}

// ----------------------------------------------------------------------------
// _DDECallback
// ----------------------------------------------------------------------------

#define DDERETURN HDDEDATA

HDDEDATA EXPENTRY _EXPORT
_DDECallback(WORD wType,
             WORD wFmt,
             HCONV hConv,
             HSZ hsz1,
             HSZ hsz2,
             HDDEDATA hData,
             DWORD WXUNUSED(lData1),
             DWORD WXUNUSED(lData2))
{
    switch (wType)
    {
        case XTYP_CONNECT:
            {
                wxString topic = DDEStringFromAtom(hsz1),
                         srv = DDEStringFromAtom(hsz2);
                wxDDEServer *server = DDEFindServer(srv);
                if (server)
                {
                    wxDDEConnection *connection =
                        (wxDDEConnection*) server->OnAcceptConnection(topic);
                    if (connection)
                    {
                        connection->m_server = server;
                        server->GetConnections().Append(connection);
                        connection->m_hConv = 0;
                        connection->m_topicName = topic;
                        DDECurrentlyConnecting = connection;
                        return (DDERETURN)(DWORD)true;
                    }
                }
                break;
            }

        case XTYP_CONNECT_CONFIRM:
            {
                if (DDECurrentlyConnecting)
                {
                    DDECurrentlyConnecting->m_hConv = (WXHCONV) hConv;
                    DDECurrentlyConnecting = NULL;
                    return (DDERETURN)(DWORD)true;
                }
                break;
            }

        case XTYP_DISCONNECT:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);
                if (connection)
                {
                    connection->SetConnected( false );
                    if (connection->OnDisconnect())
                    {
                        DDEDeleteConnection(hConv);  // Delete mapping: hConv => connection
                        return (DDERETURN)(DWORD)true;
                    }
                }
                break;
            }

        case XTYP_EXECUTE:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    DWORD len = DdeGetData(hData, NULL, 0, 0);

                    wxChar *data = connection->GetBufferAtLeast( len/sizeof(wxChar) );
                    wxASSERT_MSG(data != NULL,
                                 _T("Buffer too small in _DDECallback (XTYP_EXECUTE)") );

                    DdeGetData(hData, (LPBYTE)data, len, 0);

                    DdeFreeDataHandle(hData);

                    if ( connection->OnExecute(connection->m_topicName,
                                               data,
                                               (int)len/sizeof(wxChar),
                                               (wxIPCFormat) wFmt) )
                    {
                        return (DDERETURN)(DWORD)DDE_FACK;
                    }
                }

                return (DDERETURN)DDE_FNOTPROCESSED;
            }

        case XTYP_REQUEST:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    int user_size = -1;
                    wxChar *data = connection->OnRequest(connection->m_topicName,
                                                       item_name,
                                                       &user_size,
                                                       (wxIPCFormat) wFmt);
                    if (data)
                    {
                        if (user_size < 0)
                            user_size = wxStrlen((wxChar*)data) + 1;

                        HDDEDATA handle = DdeCreateDataHandle(DDEIdInst,
                                                              (LPBYTE)data,
                                                              user_size*sizeof(wxChar),
                                                              0,
                                                              hsz2,
                                                              wFmt,
                                                              0);
                        return (DDERETURN)handle;
                    }
                }
                break;
            }

        case XTYP_POKE:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    DWORD len = DdeGetData(hData, NULL, 0, 0);

                    wxChar *data = connection->GetBufferAtLeast( len/sizeof(wxChar) );
                    wxASSERT_MSG(data != NULL,
                                 _T("Buffer too small in _DDECallback (XTYP_EXECUTE)") );

                    DdeGetData(hData, (LPBYTE)data, len, 0);

                    DdeFreeDataHandle(hData);

                    connection->OnPoke(connection->m_topicName,
                                       item_name,
                                       data,
                                       (int)len/sizeof(wxChar),
                                       (wxIPCFormat) wFmt);

                    return (DDERETURN)DDE_FACK;
                }
                else
                {
                    return (DDERETURN)DDE_FNOTPROCESSED;
                }
            }

        case XTYP_ADVSTART:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    return (DDERETURN)connection->
                                OnStartAdvise(connection->m_topicName, item_name);
                }

                break;
            }

        case XTYP_ADVSTOP:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    return (DDERETURN)connection->
                        OnStopAdvise(connection->m_topicName, item_name);
                }

                break;
            }

        case XTYP_ADVREQ:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection && connection->m_sendingData)
                {
                    HDDEDATA data = DdeCreateDataHandle
                                    (
                                        DDEIdInst,
                                        (LPBYTE)connection->m_sendingData,
                                        connection->m_dataSize*sizeof(wxChar),
                                        0,
                                        hsz2,
                                        connection->m_dataType,
                                        0
                                    );

                    connection->m_sendingData = NULL;

                    return (DDERETURN)data;
                }

                break;
            }

        case XTYP_ADVDATA:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    DWORD len = DdeGetData(hData, NULL, 0, 0);

                    wxChar *data = connection->GetBufferAtLeast( len/sizeof(wxChar) );
                    wxASSERT_MSG(data != NULL,
                                 _T("Buffer too small in _DDECallback (XTYP_ADVDATA)") );

                    DdeGetData(hData, (LPBYTE)data, len, 0);

                    DdeFreeDataHandle(hData);
                    if ( connection->OnAdvise(connection->m_topicName,
                                              item_name,
                                              data,
                                              (int)len/sizeof(wxChar),
                                              (wxIPCFormat) wFmt) )
                    {
                        return (DDERETURN)(DWORD)DDE_FACK;
                    }
                }

                return (DDERETURN)DDE_FNOTPROCESSED;
            }
    }

    return (DDERETURN)0;
}

// ----------------------------------------------------------------------------
// DDE strings and atoms
// ----------------------------------------------------------------------------

// Atom table stuff
static HSZ DDEAddAtom(const wxString& str)
{
    HSZ atom = DDEAtomFromString(str);
    wxAtomTable[str] = atom;
    return atom;
}

static HSZ DDEGetAtom(const wxString& str)
{
    wxAtomMap::iterator it = wxAtomTable.find(str);

    if (it != wxAtomTable.end())
        return it->second;

    return DDEAddAtom(str);
}

/* atom <-> strings
The returned handle has to be freed by the caller (using
(static) DDEFreeString).
*/
static HSZ DDEAtomFromString(const wxString& s)
{
    wxASSERT_MSG( DDEIdInst, _T("DDE not initialized") );

    HSZ hsz = DdeCreateStringHandle(DDEIdInst, (wxChar*) s.c_str(), DDE_CP);
    if ( !hsz )
    {
        DDELogError(_("Failed to create DDE string"));
    }

    return hsz;
}

static wxString DDEStringFromAtom(HSZ hsz)
{
    // all DDE strings are normally limited to 255 bytes
    static const size_t len = 256;

    wxString s;
    (void)DdeQueryString(DDEIdInst, hsz, wxStringBuffer(s, len), len, DDE_CP);

    return s;
}

static void DDEFreeString(HSZ hsz)
{
    // DS: Failure to free a string handle might indicate there's
    // some other severe error.
    bool ok = (::DdeFreeStringHandle(DDEIdInst, hsz) != 0);
    wxASSERT_MSG( ok, wxT("Failed to free DDE string handle") );
    wxUnusedVar(ok);
}

// ----------------------------------------------------------------------------
// error handling
// ----------------------------------------------------------------------------

static void DDELogError(const wxString& s, UINT error)
{
    if ( !error )
    {
        error = DdeGetLastError(DDEIdInst);
    }

    wxLogError(s + _T(": ") + DDEGetErrorMsg(error));
}

static wxString DDEGetErrorMsg(UINT error)
{
    wxString err;
    switch ( error )
    {
        case DMLERR_NO_ERROR:
            err = _("no DDE error.");
            break;

        case DMLERR_ADVACKTIMEOUT:
            err = _("a request for a synchronous advise transaction has timed out.");
            break;
        case DMLERR_BUSY:
            err = _("the response to the transaction caused the DDE_FBUSY bit to be set.");
            break;
        case DMLERR_DATAACKTIMEOUT:
            err = _("a request for a synchronous data transaction has timed out.");
            break;
        case DMLERR_DLL_NOT_INITIALIZED:
            err = _("a DDEML function was called without first calling the DdeInitialize function,\nor an invalid instance identifier\nwas passed to a DDEML function.");
            break;
        case DMLERR_DLL_USAGE:
            err = _("an application initialized as APPCLASS_MONITOR has\nattempted to perform a DDE transaction,\nor an application initialized as APPCMD_CLIENTONLY has \nattempted to perform server transactions.");
            break;
        case DMLERR_EXECACKTIMEOUT:
            err = _("a request for a synchronous execute transaction has timed out.");
            break;
        case DMLERR_INVALIDPARAMETER:
            err = _("a parameter failed to be validated by the DDEML.");
            break;
        case DMLERR_LOW_MEMORY:
            err = _("a DDEML application has created a prolonged race condition.");
            break;
        case DMLERR_MEMORY_ERROR:
            err = _("a memory allocation failed.");
            break;
        case DMLERR_NO_CONV_ESTABLISHED:
            err = _("a client's attempt to establish a conversation has failed.");
            break;
        case DMLERR_NOTPROCESSED:
            err = _("a transaction failed.");
            break;
        case DMLERR_POKEACKTIMEOUT:
            err = _("a request for a synchronous poke transaction has timed out.");
            break;
        case DMLERR_POSTMSG_FAILED:
            err = _("an internal call to the PostMessage function has failed. ");
            break;
        case DMLERR_REENTRANCY:
            err = _("reentrancy problem.");
            break;
        case DMLERR_SERVER_DIED:
            err = _("a server-side transaction was attempted on a conversation\nthat was terminated by the client, or the server\nterminated before completing a transaction.");
            break;
        case DMLERR_SYS_ERROR:
            err = _("an internal error has occurred in the DDEML.");
            break;
        case DMLERR_UNADVACKTIMEOUT:
            err = _("a request to end an advise transaction has timed out.");
            break;
        case DMLERR_UNFOUND_QUEUE_ID:
            err = _("an invalid transaction identifier was passed to a DDEML function.\nOnce the application has returned from an XTYP_XACT_COMPLETE callback,\nthe transaction identifier for that callback is no longer valid.");
            break;
        default:
            err.Printf(_("Unknown DDE error %08x"), error);
    }

    return err;
}

#endif
  // wxUSE_IPC
