1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/dde.cpp
3 // Purpose: DDE classes
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
32 #include "wx/hashmap.h"
33 #include "wx/module.h"
39 #include "wx/msw/private.h"
44 // ----------------------------------------------------------------------------
45 // macros and constants
46 // ----------------------------------------------------------------------------
51 #define _EXPORT _export
55 #define DDE_CP CP_WINUNICODE
57 #define DDE_CP CP_WINANSI
60 #define GetHConv() ((HCONV)m_hConv)
62 // default timeout for DDE operations (5sec)
63 #define DDE_TIMEOUT 5000
65 // ----------------------------------------------------------------------------
67 // ----------------------------------------------------------------------------
69 static wxDDEConnection
*DDEFindConnection(HCONV hConv
);
70 static void DDEDeleteConnection(HCONV hConv
);
71 static wxDDEServer
*DDEFindServer(const wxString
& s
);
73 extern "C" HDDEDATA EXPENTRY _EXPORT
_DDECallback(WORD wType
,
82 // Add topic name to atom table before using in conversations
83 static HSZ
DDEAddAtom(const wxString
& string
);
84 static HSZ
DDEGetAtom(const wxString
& string
);
87 static HSZ
DDEAtomFromString(const wxString
& s
);
88 static wxString
DDEStringFromAtom(HSZ hsz
);
89 static void DDEFreeString(HSZ hsz
);
92 static wxString
DDEGetErrorMsg(UINT error
);
93 static void DDELogError(const wxString
& s
, UINT error
= DMLERR_NO_ERROR
);
95 // ----------------------------------------------------------------------------
97 // ----------------------------------------------------------------------------
99 WX_DECLARE_STRING_HASH_MAP( HSZ
, wxAtomMap
);
101 static DWORD DDEIdInst
= 0L;
102 static wxDDEConnection
*DDECurrentlyConnecting
= NULL
;
103 static wxAtomMap wxAtomTable
;
105 #include "wx/listimpl.cpp"
107 WX_DEFINE_LIST(wxDDEClientList
)
108 WX_DEFINE_LIST(wxDDEServerList
)
109 WX_DEFINE_LIST(wxDDEConnectionList
)
111 static wxDDEClientList wxDDEClientObjects
;
112 static wxDDEServerList wxDDEServerObjects
;
114 static bool DDEInitialized
= false;
116 // ----------------------------------------------------------------------------
118 // ----------------------------------------------------------------------------
120 // A module to allow DDE cleanup without calling these functions
121 // from app.cpp or from the user's application.
123 class wxDDEModule
: public wxModule
127 bool OnInit() { return true; }
128 void OnExit() { wxDDECleanUp(); }
131 DECLARE_DYNAMIC_CLASS(wxDDEModule
)
134 // ----------------------------------------------------------------------------
136 // ----------------------------------------------------------------------------
138 IMPLEMENT_DYNAMIC_CLASS(wxDDEServer
, wxServerBase
)
139 IMPLEMENT_DYNAMIC_CLASS(wxDDEClient
, wxClientBase
)
140 IMPLEMENT_CLASS(wxDDEConnection
, wxConnectionBase
)
141 IMPLEMENT_DYNAMIC_CLASS(wxDDEModule
, wxModule
)
143 // ============================================================================
145 // ============================================================================
147 // ----------------------------------------------------------------------------
148 // initialization and cleanup
149 // ----------------------------------------------------------------------------
151 extern void wxDDEInitialize()
153 if ( !DDEInitialized
)
155 // Should insert filter flags
156 PFNCALLBACK callback
= (PFNCALLBACK
)
157 MakeProcInstance((FARPROC
)_DDECallback
, wxGetInstance());
158 UINT rc
= DdeInitialize(&DDEIdInst
, callback
, APPCLASS_STANDARD
, 0L);
159 if ( rc
!= DMLERR_NO_ERROR
)
161 DDELogError(_T("Failed to initialize DDE"), rc
);
165 DDEInitialized
= true;
172 // deleting them later won't work as DDE won't be initialized any more
173 wxASSERT_MSG( wxDDEServerObjects
.empty() &&
174 wxDDEClientObjects
.empty(),
175 _T("all DDE objects should be deleted by now") );
179 if ( DDEIdInst
!= 0 )
181 DdeUninitialize(DDEIdInst
);
186 // ----------------------------------------------------------------------------
187 // functions working with the global connection list(s)
188 // ----------------------------------------------------------------------------
190 // Global find connection
191 static wxDDEConnection
*DDEFindConnection(HCONV hConv
)
193 wxDDEServerList::compatibility_iterator serverNode
= wxDDEServerObjects
.GetFirst();
194 wxDDEConnection
*found
= NULL
;
195 while (serverNode
&& !found
)
197 wxDDEServer
*object
= serverNode
->GetData();
198 found
= object
->FindConnection((WXHCONV
) hConv
);
199 serverNode
= serverNode
->GetNext();
207 wxDDEClientList::compatibility_iterator clientNode
= wxDDEClientObjects
.GetFirst();
208 while (clientNode
&& !found
)
210 wxDDEClient
*object
= clientNode
->GetData();
211 found
= object
->FindConnection((WXHCONV
) hConv
);
212 clientNode
= clientNode
->GetNext();
217 // Global delete connection
218 static void DDEDeleteConnection(HCONV hConv
)
220 wxDDEServerList::compatibility_iterator serverNode
= wxDDEServerObjects
.GetFirst();
222 while (serverNode
&& !found
)
224 wxDDEServer
*object
= serverNode
->GetData();
225 found
= object
->DeleteConnection((WXHCONV
) hConv
);
226 serverNode
= serverNode
->GetNext();
233 wxDDEClientList::compatibility_iterator clientNode
= wxDDEClientObjects
.GetFirst();
234 while (clientNode
&& !found
)
236 wxDDEClient
*object
= clientNode
->GetData();
237 found
= object
->DeleteConnection((WXHCONV
) hConv
);
238 clientNode
= clientNode
->GetNext();
242 // Find a server from a service name
243 static wxDDEServer
*DDEFindServer(const wxString
& s
)
245 wxDDEServerList::compatibility_iterator node
= wxDDEServerObjects
.GetFirst();
246 wxDDEServer
*found
= NULL
;
247 while (node
&& !found
)
249 wxDDEServer
*object
= node
->GetData();
251 if (object
->GetServiceName() == s
)
257 node
= node
->GetNext();
264 // ----------------------------------------------------------------------------
266 // ----------------------------------------------------------------------------
268 wxDDEServer::wxDDEServer()
272 wxDDEServerObjects
.Append(this);
275 bool wxDDEServer::Create(const wxString
& server
)
277 m_serviceName
= server
;
279 HSZ hsz
= DDEAtomFromString(server
);
287 bool success
= (DdeNameService(DDEIdInst
, hsz
, (HSZ
) NULL
, DNS_REGISTER
)
292 DDELogError(wxString::Format(_("Failed to register DDE server '%s'"),
301 wxDDEServer::~wxDDEServer()
303 if ( !m_serviceName
.empty() )
305 HSZ hsz
= DDEAtomFromString(m_serviceName
);
309 if ( !DdeNameService(DDEIdInst
, hsz
,
310 (HSZ
) NULL
, DNS_UNREGISTER
) )
312 DDELogError(wxString::Format(
313 _("Failed to unregister DDE server '%s'"),
314 m_serviceName
.c_str()));
321 wxDDEServerObjects
.DeleteObject(this);
323 wxDDEConnectionList::compatibility_iterator node
= m_connections
.GetFirst();
326 wxDDEConnection
*connection
= node
->GetData();
327 wxDDEConnectionList::compatibility_iterator next
= node
->GetNext();
328 connection
->SetConnected(false);
329 connection
->OnDisconnect(); // May delete the node implicitly
333 // If any left after this, delete them
334 node
= m_connections
.GetFirst();
337 wxDDEConnection
*connection
= node
->GetData();
338 wxDDEConnectionList::compatibility_iterator next
= node
->GetNext();
344 wxConnectionBase
*wxDDEServer::OnAcceptConnection(const wxString
& /* topic */)
346 return new wxDDEConnection
;
349 wxDDEConnection
*wxDDEServer::FindConnection(WXHCONV conv
)
351 wxDDEConnectionList::compatibility_iterator node
= m_connections
.GetFirst();
352 wxDDEConnection
*found
= NULL
;
353 while (node
&& !found
)
355 wxDDEConnection
*connection
= node
->GetData();
356 if (connection
->m_hConv
== conv
)
358 else node
= node
->GetNext();
363 // Only delete the entry in the map, not the actual connection
364 bool wxDDEServer::DeleteConnection(WXHCONV conv
)
366 wxDDEConnectionList::compatibility_iterator node
= m_connections
.GetFirst();
369 wxDDEConnection
*connection
= node
->GetData();
370 if (connection
->m_hConv
== conv
)
372 m_connections
.Erase(node
);
377 node
= node
->GetNext();
383 // ----------------------------------------------------------------------------
385 // ----------------------------------------------------------------------------
387 wxDDEClient::wxDDEClient()
391 wxDDEClientObjects
.Append(this);
394 wxDDEClient::~wxDDEClient()
396 wxDDEClientObjects
.DeleteObject(this);
397 wxDDEConnectionList::compatibility_iterator node
= m_connections
.GetFirst();
400 wxDDEConnection
*connection
= node
->GetData();
401 delete connection
; // Deletes the node implicitly (see ~wxDDEConnection)
402 node
= m_connections
.GetFirst();
406 bool wxDDEClient::ValidHost(const wxString
& /* host */)
411 wxConnectionBase
*wxDDEClient::MakeConnection(const wxString
& WXUNUSED(host
),
412 const wxString
& server
,
413 const wxString
& topic
)
415 HSZ hszServer
= DDEAtomFromString(server
);
419 return (wxConnectionBase
*) NULL
;
423 HSZ hszTopic
= DDEAtomFromString(topic
);
427 DDEFreeString(hszServer
);
428 return (wxConnectionBase
*) NULL
;
432 HCONV hConv
= ::DdeConnect(DDEIdInst
, hszServer
, hszTopic
,
433 (PCONVCONTEXT
) NULL
);
435 DDEFreeString(hszServer
);
436 DDEFreeString(hszTopic
);
441 DDELogError( wxString::Format(
442 _("Failed to create connection to server '%s' on topic '%s'"),
443 server
.c_str(), topic
.c_str()) );
447 wxDDEConnection
*connection
= (wxDDEConnection
*) OnMakeConnection();
450 connection
->m_hConv
= (WXHCONV
) hConv
;
451 connection
->m_topicName
= topic
;
452 connection
->m_client
= this;
453 m_connections
.Append(connection
);
458 return (wxConnectionBase
*) NULL
;
461 wxConnectionBase
*wxDDEClient::OnMakeConnection()
463 return new wxDDEConnection
;
466 wxDDEConnection
*wxDDEClient::FindConnection(WXHCONV conv
)
468 wxDDEConnectionList::compatibility_iterator node
= m_connections
.GetFirst();
469 wxDDEConnection
*found
= NULL
;
470 while (node
&& !found
)
472 wxDDEConnection
*connection
= node
->GetData();
473 if (connection
->m_hConv
== conv
)
475 else node
= node
->GetNext();
480 // Only delete the entry in the map, not the actual connection
481 bool wxDDEClient::DeleteConnection(WXHCONV conv
)
483 wxDDEConnectionList::compatibility_iterator node
= m_connections
.GetFirst();
486 wxDDEConnection
*connection
= node
->GetData();
487 if (connection
->m_hConv
== conv
)
489 m_connections
.Erase(node
);
492 else node
= node
->GetNext();
497 // ----------------------------------------------------------------------------
499 // ----------------------------------------------------------------------------
501 wxDDEConnection::wxDDEConnection(void *buffer
, size_t size
)
502 : wxConnectionBase(buffer
, size
)
508 m_sendingData
= NULL
;
511 wxDDEConnection::wxDDEConnection()
515 m_sendingData
= NULL
;
520 wxDDEConnection::~wxDDEConnection()
524 m_server
->GetConnections().DeleteObject(this);
526 m_client
->GetConnections().DeleteObject(this);
529 // Calls that CLIENT can make
530 bool wxDDEConnection::Disconnect()
532 if ( !GetConnected() )
535 DDEDeleteConnection(GetHConv());
537 bool ok
= DdeDisconnect(GetHConv()) != 0;
540 DDELogError(_T("Failed to disconnect from DDE server gracefully"));
543 SetConnected( false ); // so we don't try and disconnect again
548 bool wxDDEConnection::DoExecute(const void *data
, size_t size
, wxIPCFormat
WXUNUSED(format
))
552 bool ok
= DdeClientTransaction((LPBYTE
)data
,
556 // If the transaction specified by the wType parameter does not pass data or is XTYP_EXECUTE,
557 // wFmt should be zero.
565 DDELogError(_T("DDE execute request failed"));
571 const void *wxDDEConnection::Request(const wxString
& item
, size_t *size
, wxIPCFormat format
)
575 HSZ atom
= DDEGetAtom(item
);
577 HDDEDATA returned_data
= DdeClientTransaction(NULL
, 0,
583 if ( !returned_data
)
585 DDELogError(_T("DDE data request failed"));
590 DWORD len
= DdeGetData(returned_data
, NULL
, 0, 0);
592 void *data
= GetBufferAtLeast(len
);
593 wxASSERT_MSG(data
!= NULL
,
594 _T("Buffer too small in wxDDEConnection::Request") );
595 (void) DdeGetData(returned_data
, (LPBYTE
)data
, len
, 0);
597 (void) DdeFreeDataHandle(returned_data
);
605 bool wxDDEConnection::DoPoke(const wxString
& item
, const void *data
, size_t size
, wxIPCFormat format
)
609 HSZ item_atom
= DDEGetAtom(item
);
610 bool ok
= DdeClientTransaction((LPBYTE
)data
,
619 DDELogError(_("DDE poke request failed"));
625 bool wxDDEConnection::StartAdvise(const wxString
& item
)
628 HSZ atom
= DDEGetAtom(item
);
630 bool ok
= DdeClientTransaction(NULL
, 0,
638 DDELogError(_("Failed to establish an advise loop with DDE server"));
644 bool wxDDEConnection::StopAdvise(const wxString
& item
)
647 HSZ atom
= DDEGetAtom(item
);
649 bool ok
= DdeClientTransaction(NULL
, 0,
657 DDELogError(_("Failed to terminate the advise loop with DDE server"));
663 // Calls that SERVER can make
664 bool wxDDEConnection::DoAdvise(const wxString
& item
,
669 HSZ item_atom
= DDEGetAtom(item
);
670 HSZ topic_atom
= DDEGetAtom(m_topicName
);
671 m_sendingData
= data
; // mrf: potential for scope problems here?
673 // wxIPC_PRIVATE does not succeed, so use text instead
674 m_dataType
= format
== wxIPC_PRIVATE
? wxIPC_TEXT
: format
;
676 bool ok
= DdePostAdvise(DDEIdInst
, topic_atom
, item_atom
) != 0;
679 DDELogError(_("Failed to send DDE advise notification"));
685 // ----------------------------------------------------------------------------
687 // ----------------------------------------------------------------------------
689 #define DDERETURN HDDEDATA
691 HDDEDATA EXPENTRY _EXPORT
692 _DDECallback(WORD wType
,
698 DWORD
WXUNUSED(lData1
),
699 DWORD
WXUNUSED(lData2
))
705 wxString topic
= DDEStringFromAtom(hsz1
),
706 srv
= DDEStringFromAtom(hsz2
);
707 wxDDEServer
*server
= DDEFindServer(srv
);
710 wxDDEConnection
*connection
=
711 (wxDDEConnection
*) server
->OnAcceptConnection(topic
);
714 connection
->m_server
= server
;
715 server
->GetConnections().Append(connection
);
716 connection
->m_hConv
= 0;
717 connection
->m_topicName
= topic
;
718 DDECurrentlyConnecting
= connection
;
719 return (DDERETURN
)(DWORD
)true;
725 case XTYP_CONNECT_CONFIRM
:
727 if (DDECurrentlyConnecting
)
729 DDECurrentlyConnecting
->m_hConv
= (WXHCONV
) hConv
;
730 DDECurrentlyConnecting
= NULL
;
731 return (DDERETURN
)(DWORD
)true;
736 case XTYP_DISCONNECT
:
738 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
741 connection
->SetConnected( false );
742 if (connection
->OnDisconnect())
744 DDEDeleteConnection(hConv
); // Delete mapping: hConv => connection
745 return (DDERETURN
)(DWORD
)true;
753 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
757 DWORD len
= DdeGetData(hData
, NULL
, 0, 0);
759 void *data
= connection
->GetBufferAtLeast(len
);
760 wxASSERT_MSG(data
!= NULL
,
761 _T("Buffer too small in _DDECallback (XTYP_EXECUTE)") );
763 DdeGetData(hData
, (LPBYTE
)data
, len
, 0);
765 DdeFreeDataHandle(hData
);
767 // XTYP_EXECUTE cannot be used for arbitrary data, but only for text
768 if ( connection
->OnExecute(connection
->m_topicName
,
773 return (DDERETURN
)(DWORD
)DDE_FACK
;
777 return (DDERETURN
)DDE_FNOTPROCESSED
;
782 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
786 wxString item_name
= DDEStringFromAtom(hsz2
);
788 size_t user_size
= wxNO_LEN
;
789 const void *data
= connection
->OnRequest(connection
->m_topicName
,
795 if (user_size
== wxNO_LEN
)
800 user_size
= strlen((const char*)data
) + 1; // includes final NUL
802 case wxIPC_UNICODETEXT
:
803 user_size
= (wcslen((const wchar_t*)data
) + 1) * sizeof(wchar_t); // includes final NUL
809 HDDEDATA handle
= DdeCreateDataHandle(DDEIdInst
,
816 return (DDERETURN
)handle
;
824 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
828 wxString item_name
= DDEStringFromAtom(hsz2
);
830 DWORD len
= DdeGetData(hData
, NULL
, 0, 0);
832 void *data
= connection
->GetBufferAtLeast(len
);
833 wxASSERT_MSG(data
!= NULL
,
834 _T("Buffer too small in _DDECallback (XTYP_POKE)") );
836 DdeGetData(hData
, (LPBYTE
)data
, len
, 0);
838 DdeFreeDataHandle(hData
);
840 connection
->OnPoke(connection
->m_topicName
,
846 return (DDERETURN
)DDE_FACK
;
850 return (DDERETURN
)DDE_FNOTPROCESSED
;
856 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
860 wxString item_name
= DDEStringFromAtom(hsz2
);
862 return (DDERETURN
)connection
->
863 OnStartAdvise(connection
->m_topicName
, item_name
);
871 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
875 wxString item_name
= DDEStringFromAtom(hsz2
);
877 return (DDERETURN
)connection
->
878 OnStopAdvise(connection
->m_topicName
, item_name
);
886 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
888 if (connection
&& connection
->m_sendingData
)
890 HDDEDATA data
= DdeCreateDataHandle
893 (LPBYTE
)connection
->m_sendingData
,
894 connection
->m_dataSize
,
897 connection
->m_dataType
,
901 connection
->m_sendingData
= NULL
;
903 return (DDERETURN
)data
;
911 wxDDEConnection
*connection
= DDEFindConnection(hConv
);
915 wxString item_name
= DDEStringFromAtom(hsz2
);
917 DWORD len
= DdeGetData(hData
, NULL
, 0, 0);
919 void *data
= connection
->GetBufferAtLeast(len
);
920 wxASSERT_MSG(data
!= NULL
,
921 _T("Buffer too small in _DDECallback (XTYP_ADVDATA)") );
923 DdeGetData(hData
, (LPBYTE
)data
, len
, 0);
925 DdeFreeDataHandle(hData
);
926 if ( connection
->OnAdvise(connection
->m_topicName
,
930 (wxIPCFormat
) wFmt
) )
932 return (DDERETURN
)(DWORD
)DDE_FACK
;
936 return (DDERETURN
)DDE_FNOTPROCESSED
;
943 // ----------------------------------------------------------------------------
944 // DDE strings and atoms
945 // ----------------------------------------------------------------------------
948 static HSZ
DDEAddAtom(const wxString
& str
)
950 HSZ atom
= DDEAtomFromString(str
);
951 wxAtomTable
[str
] = atom
;
955 static HSZ
DDEGetAtom(const wxString
& str
)
957 wxAtomMap::iterator it
= wxAtomTable
.find(str
);
959 if (it
!= wxAtomTable
.end())
962 return DDEAddAtom(str
);
966 The returned handle has to be freed by the caller (using
967 (static) DDEFreeString).
969 static HSZ
DDEAtomFromString(const wxString
& s
)
971 wxASSERT_MSG( DDEIdInst
, _T("DDE not initialized") );
973 HSZ hsz
= DdeCreateStringHandle(DDEIdInst
, (wxChar
*)s
.wx_str(), DDE_CP
);
976 DDELogError(_("Failed to create DDE string"));
982 static wxString
DDEStringFromAtom(HSZ hsz
)
984 // all DDE strings are normally limited to 255 bytes
985 static const size_t len
= 256;
988 (void)DdeQueryString(DDEIdInst
, hsz
, wxStringBuffer(s
, len
), len
, DDE_CP
);
993 static void DDEFreeString(HSZ hsz
)
995 // DS: Failure to free a string handle might indicate there's
996 // some other severe error.
997 bool ok
= (::DdeFreeStringHandle(DDEIdInst
, hsz
) != 0);
998 wxASSERT_MSG( ok
, wxT("Failed to free DDE string handle") );
1002 // ----------------------------------------------------------------------------
1004 // ----------------------------------------------------------------------------
1006 static void DDELogError(const wxString
& s
, UINT error
)
1010 error
= DdeGetLastError(DDEIdInst
);
1013 wxLogError(s
+ _T(": ") + DDEGetErrorMsg(error
));
1016 static wxString
DDEGetErrorMsg(UINT error
)
1021 case DMLERR_NO_ERROR
:
1022 err
= _("no DDE error.");
1025 case DMLERR_ADVACKTIMEOUT
:
1026 err
= _("a request for a synchronous advise transaction has timed out.");
1029 err
= _("the response to the transaction caused the DDE_FBUSY bit to be set.");
1031 case DMLERR_DATAACKTIMEOUT
:
1032 err
= _("a request for a synchronous data transaction has timed out.");
1034 case DMLERR_DLL_NOT_INITIALIZED
:
1035 err
= _("a DDEML function was called without first calling the DdeInitialize function,\nor an invalid instance identifier\nwas passed to a DDEML function.");
1037 case DMLERR_DLL_USAGE
:
1038 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.");
1040 case DMLERR_EXECACKTIMEOUT
:
1041 err
= _("a request for a synchronous execute transaction has timed out.");
1043 case DMLERR_INVALIDPARAMETER
:
1044 err
= _("a parameter failed to be validated by the DDEML.");
1046 case DMLERR_LOW_MEMORY
:
1047 err
= _("a DDEML application has created a prolonged race condition.");
1049 case DMLERR_MEMORY_ERROR
:
1050 err
= _("a memory allocation failed.");
1052 case DMLERR_NO_CONV_ESTABLISHED
:
1053 err
= _("a client's attempt to establish a conversation has failed.");
1055 case DMLERR_NOTPROCESSED
:
1056 err
= _("a transaction failed.");
1058 case DMLERR_POKEACKTIMEOUT
:
1059 err
= _("a request for a synchronous poke transaction has timed out.");
1061 case DMLERR_POSTMSG_FAILED
:
1062 err
= _("an internal call to the PostMessage function has failed. ");
1064 case DMLERR_REENTRANCY
:
1065 err
= _("reentrancy problem.");
1067 case DMLERR_SERVER_DIED
:
1068 err
= _("a server-side transaction was attempted on a conversation\nthat was terminated by the client, or the server\nterminated before completing a transaction.");
1070 case DMLERR_SYS_ERROR
:
1071 err
= _("an internal error has occurred in the DDEML.");
1073 case DMLERR_UNADVACKTIMEOUT
:
1074 err
= _("a request to end an advise transaction has timed out.");
1076 case DMLERR_UNFOUND_QUEUE_ID
:
1077 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.");
1080 err
.Printf(_("Unknown DDE error %08x"), error
);