Documentation fixes.
[wine] / dlls / user / dde / misc.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2
3 /*
4  * DDEML library
5  *
6  * Copyright 1997 Alexandre Julliard
7  * Copyright 1997 Len White
8  * Copyright 1999 Keith Matthews
9  * Copyright 2000 Corel
10  * Copyright 2001 Eric Pouech
11  */
12
13 #include <string.h>
14 #include "winbase.h"
15 #include "windef.h"
16 #include "wingdi.h"
17 #include "winuser.h"
18 #include "winerror.h"
19 #include "dde.h"
20 #include "ddeml.h"
21 #include "debugtools.h"
22 #include "dde/dde_private.h"
23
24 DEFAULT_DEBUG_CHANNEL(ddeml);
25
26 WDML_INSTANCE*          WDML_InstanceList = NULL;
27 DWORD                   WDML_MaxInstanceID = 0;  /* OK for present, may have to worry about wrap-around later */
28 static const char       DDEInstanceAccess[] = "DDEMaxInstance";
29 static const char       DDEHandleAccess[] = "DDEHandleAccess";
30 HANDLE                  handle_mutex = 0;
31 const char              WDML_szEventClass[] = "DdeEventClass";
32
33 /* FIXME
34  * currently the msg parameter is not used in the packing functions.
35  * it should be used to identify messages which don't actually require the packing operation
36  * but would do with the simple DWORD for lParam
37  */
38
39 static BOOL DDE_RequirePacking(UINT msg)
40 {
41     BOOL        ret;
42
43     switch (msg)
44     {
45     case WM_DDE_ACK:
46     case WM_DDE_ADVISE:
47     case WM_DDE_DATA:
48     case WM_DDE_POKE:
49         ret = TRUE;
50         break;
51     case WM_DDE_EXECUTE:        /* strange, NT 2000 (at least) really uses packing here... */
52     case WM_DDE_INITIATE:
53     case WM_DDE_REQUEST:        /* assuming clipboard formats are 16 bit */
54     case WM_DDE_TERMINATE:
55     case WM_DDE_UNADVISE:       /* assuming clipboard formats are 16 bit */
56         ret = FALSE;
57         break;
58     default:
59         TRACE("Unknown message %04x\n", msg);
60         ret = FALSE;
61         break;
62     }
63     return ret;
64 }
65
66 /*****************************************************************
67  *            PackDDElParam (USER32.@)
68  *
69  * RETURNS
70  *   the packed lParam
71  */
72 LPARAM WINAPI PackDDElParam(UINT msg, UINT uiLo, UINT uiHi)
73 {
74      HGLOBAL    hMem;
75      UINT*      params;
76      
77      if (!DDE_RequirePacking(msg))
78          return MAKELONG(uiLo, uiHi);
79
80      if (!(hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(UINT) * 2)))
81      {
82           ERR("GlobalAlloc failed\n");
83           return 0;
84      }
85      
86      params = GlobalLock(hMem);
87      if (params == NULL)
88      {
89           ERR("GlobalLock failed\n");
90           return 0;
91      }
92      
93      params[0] = uiLo;
94      params[1] = uiHi;
95      
96      GlobalUnlock(hMem);
97
98      return (LPARAM)hMem;
99 }
100
101
102 /*****************************************************************
103  *            UnpackDDElParam (USER32.@)
104  *
105  * RETURNS
106  *   success: nonzero
107  *   failure: zero
108  */
109 BOOL WINAPI UnpackDDElParam(UINT msg, LPARAM lParam,
110                             PUINT uiLo, PUINT uiHi)
111 {
112      HGLOBAL hMem;
113      UINT *params;
114
115      if (!DDE_RequirePacking(msg))
116      {
117          *uiLo = LOWORD(lParam);
118          *uiHi = HIWORD(lParam);
119
120          return TRUE;
121      }
122      
123      if (lParam == 0)
124      {
125           return FALSE;
126      }
127      
128      hMem = (HGLOBAL)lParam;
129      
130      params = GlobalLock(hMem);
131      if (params == NULL)
132      {
133           ERR("GlobalLock failed\n");
134           return FALSE;
135      }
136      
137      *uiLo = params[0];
138      *uiHi = params[1];
139
140      GlobalUnlock(hMem);
141      
142      return TRUE;
143 }
144
145
146 /*****************************************************************
147  *            FreeDDElParam (USER32.@)
148  *
149  * RETURNS
150  *   success: nonzero
151  *   failure: zero
152  */
153 BOOL WINAPI FreeDDElParam(UINT msg, LPARAM lParam)
154 {
155      HGLOBAL hMem = (HGLOBAL)lParam;
156
157      if (!DDE_RequirePacking(msg))
158          return TRUE;
159
160      if (lParam == 0)
161      {
162           return FALSE;
163      }
164      return GlobalFree(hMem) == (HGLOBAL)NULL;
165 }
166
167
168 /*****************************************************************
169  *            ReuseDDElParam (USER32.@)
170  *
171  * RETURNS
172  *   the packed lParam
173  */
174 LPARAM WINAPI ReuseDDElParam(LPARAM lParam, UINT msgIn, UINT msgOut,
175                              UINT uiLo, UINT uiHi)
176 {
177      HGLOBAL    hMem;
178      UINT*      params;
179      BOOL       in, out;
180
181      in = DDE_RequirePacking(msgIn);
182      out = DDE_RequirePacking(msgOut);
183
184      if (!in)
185      {
186          return PackDDElParam(msgOut, uiLo, uiHi);
187      }
188
189      if (lParam == 0)
190      {
191           return FALSE;
192      }
193
194      if (!out)
195      {
196          FreeDDElParam(msgIn, lParam);
197          return MAKELONG(uiLo, uiHi);
198      }
199
200      hMem = (HGLOBAL)lParam;
201      
202      params = GlobalLock(hMem);
203      if (params == NULL)
204      {
205           ERR("GlobalLock failed\n");
206           return 0;
207      }
208      
209      params[0] = uiLo;
210      params[1] = uiHi;
211
212      TRACE("Reusing pack %08x %08x\n", uiLo, uiHi);
213
214      GlobalLock(hMem);
215      return lParam;
216 }
217
218 /*****************************************************************
219  *            ImpersonateDdeClientWindow (USER32.@)
220  *
221  */
222 BOOL WINAPI ImpersonateDdeClientWindow(
223      HWND hWndClient,  /* [in] handle to DDE client window */
224      HWND hWndServer   /* [in] handle to DDE server window */
225 )
226 {
227      FIXME("(%04x %04x): stub\n", hWndClient, hWndServer);
228      return FALSE;
229 }
230
231 /*****************************************************************
232  *            DdeSetQualityOfService (USER32.@)
233  */
234
235 BOOL WINAPI DdeSetQualityOfService(HWND hwndClient, CONST SECURITY_QUALITY_OF_SERVICE *pqosNew,
236                                    PSECURITY_QUALITY_OF_SERVICE pqosPrev)
237 {
238      FIXME("(%04x %p %p): stub\n", hwndClient, pqosNew, pqosPrev);
239      return TRUE;
240 }
241
242
243 /******************************************************************************
244  *              IncrementInstanceId
245  *
246  *      generic routine to increment the max instance Id and allocate a new application instance
247  */
248 static DWORD WDML_IncrementInstanceId(WDML_INSTANCE* thisInstance)
249 {
250     DWORD       id = InterlockedIncrement(&WDML_MaxInstanceID);
251
252     thisInstance->instanceID = id;
253     TRACE("New instance id %ld allocated\n", id);
254     return DMLERR_NO_ERROR;
255 }
256
257 /******************************************************************************
258  *            DdeInitializeA   (USER32.@)
259  */
260 UINT WINAPI DdeInitializeA(LPDWORD pidInst, PFNCALLBACK pfnCallback,
261                            DWORD afCmd, DWORD ulRes)
262 {
263     UINT        ret = DdeInitializeW(pidInst, pfnCallback, afCmd, ulRes);
264     
265     if (ret == DMLERR_NO_ERROR) {
266         WDML_INSTANCE*  thisInstance = WDML_FindInstance(*pidInst);
267         if (thisInstance)
268             thisInstance->unicode = FALSE;
269     }
270     return ret;
271 }
272
273 static LRESULT CALLBACK WDML_EventProc(HWND hwndEvent, UINT uMsg, WPARAM wParam, LPARAM lParam)
274 {
275     WDML_INSTANCE*      thisInstance;
276     HDDEDATA            hDdeData;
277
278     switch (uMsg)
279     {
280     case WM_WDML_REGISTER:
281         thisInstance = (WDML_INSTANCE*)GetWindowLongA(hwndEvent, 0);
282         
283         /* try calling the Callback */
284         if (thisInstance->callback != NULL /* && thisInstance->Process_id == GetCurrentProcessId()*/)
285         {
286             TRACE("Calling the callback, type=XTYP_REGISTER, CB=0x%lx\n",
287                   (DWORD)thisInstance->callback);
288                 
289             hDdeData = (thisInstance->callback)(XTYP_REGISTER, 0, 0,
290                                                 (HSZ)wParam, (HSZ)lParam, 0, 0, 0);
291                 
292             TRACE("Callback function called - result=%d\n", (INT)hDdeData);
293         }
294         break;
295
296     case WM_WDML_UNREGISTER:
297         thisInstance = (WDML_INSTANCE*)GetWindowLongA(hwndEvent, 0);
298         
299         if (thisInstance && thisInstance->callback != NULL)
300         {
301             if (thisInstance->CBFflags & CBF_SKIP_DISCONNECTS)
302             {
303                 FIXME("skip callback XTYP_UNREGISTER\n");
304             }
305             else
306             {
307                 TRACE("calling callback XTYP_UNREGISTER, idInst=%ld\n", 
308                       thisInstance->instanceID);
309                 (thisInstance->callback)(XTYP_UNREGISTER, 0, 0,
310                                          (HSZ)wParam, (HSZ)lParam, 0, 0, 0);
311             }
312         }
313     }
314     return DefWindowProcA(hwndEvent, uMsg, wParam, lParam);
315 }
316
317 /******************************************************************************
318  * DdeInitializeW [USER32.@]
319  * Registers an application with the DDEML
320  *
321  * PARAMS
322  *    pidInst     [I] Pointer to instance identifier
323  *    pfnCallback [I] Pointer to callback function
324  *    afCmd       [I] Set of command and filter flags
325  *    ulRes       [I] Reserved
326  *
327  * RETURNS
328  *    Success: DMLERR_NO_ERROR
329  *    Failure: DMLERR_DLL_USAGE, DMLERR_INVALIDPARAMETER, DMLERR_SYS_ERROR
330  */
331 UINT WINAPI DdeInitializeW(LPDWORD pidInst, PFNCALLBACK pfnCallback,
332                            DWORD afCmd, DWORD ulRes)
333 {
334     
335 /*  probably not really capable of handling multiple processes, but should handle
336  *      multiple instances within one process */
337     
338     SECURITY_ATTRIBUTES         s_attrib;
339     DWORD                       err_no = 0;
340     WDML_INSTANCE*              thisInstance;
341     WDML_INSTANCE*              reference_inst;
342     UINT                        ret;
343     WNDCLASSEXA                 wndclass;
344
345     if (ulRes)
346     {
347         ERR("Reserved value not zero?  What does this mean?\n");
348         FIXME("(%p,%p,0x%lx,%ld): stub\n", pidInst, pfnCallback,
349               afCmd,ulRes);
350         /* trap this and no more until we know more */
351         return DMLERR_NO_ERROR;
352     }
353     if (!pfnCallback) 
354     {
355         /*  this one may be wrong - MS dll seems to accept the condition, 
356             leave this until we find out more !! */
357         
358         
359         /* can't set up the instance with nothing to act as a callback */
360         TRACE("No callback provided\n");
361         return DMLERR_INVALIDPARAMETER; /* might be DMLERR_DLL_USAGE */
362     }
363     
364     /* grab enough heap for one control struct - not really necessary for re-initialise
365      *  but allows us to use same validation routines */
366     thisInstance = (WDML_INSTANCE*)HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_INSTANCE));
367     if (thisInstance == NULL)
368     {
369         /* catastrophe !! warn user & abort */
370         ERR("Instance create failed - out of memory\n");
371         return DMLERR_SYS_ERROR;
372     }
373     thisInstance->next = NULL;
374     thisInstance->monitor = (afCmd | APPCLASS_MONITOR);
375     
376     /* messy bit, spec implies that 'Client Only' can be set in 2 different ways, catch 1 here */
377     
378     thisInstance->clientOnly = afCmd & APPCMD_CLIENTONLY;
379     thisInstance->instanceID = *pidInst; /* May need to add calling proc Id */
380     thisInstance->callback = *pfnCallback;
381     thisInstance->txnCount = 0;
382     thisInstance->unicode = TRUE;
383     thisInstance->win16 = FALSE;
384     thisInstance->nodeList = NULL; /* node will be added later */
385     thisInstance->monitorFlags = afCmd & MF_MASK;
386     thisInstance->servers = NULL;
387     thisInstance->convs[0] = NULL;
388     thisInstance->convs[1] = NULL;
389     thisInstance->links[0] = NULL;
390     thisInstance->links[1] = NULL;
391
392     wndclass.cbSize        = sizeof(wndclass);
393     wndclass.style         = 0;
394     wndclass.lpfnWndProc   = WDML_EventProc;
395     wndclass.cbClsExtra    = 0;
396     wndclass.cbWndExtra    = sizeof(DWORD);
397     wndclass.hInstance     = 0;
398     wndclass.hIcon         = 0;
399     wndclass.hCursor       = 0;
400     wndclass.hbrBackground = 0;
401     wndclass.lpszMenuName  = NULL;
402     wndclass.lpszClassName = WDML_szEventClass;
403     wndclass.hIconSm       = 0;
404         
405     RegisterClassExA(&wndclass);
406         
407     thisInstance->hwndEvent = CreateWindowA(WDML_szEventClass, NULL,
408                                             WS_POPUP, 0, 0, 0, 0,
409                                             0, 0, 0, 0);
410         
411     SetWindowLongA(thisInstance->hwndEvent, 0, (DWORD)thisInstance);
412
413     /* isolate CBF flags in one go, expect this will go the way of all attempts to be clever !! */
414     
415     thisInstance->CBFflags = afCmd^((afCmd&MF_MASK)|((afCmd&APPCMD_MASK)|(afCmd&APPCLASS_MASK)));
416     
417     if (!thisInstance->clientOnly)
418     {
419         
420         /* Check for other way of setting Client-only !! */
421         
422         thisInstance->clientOnly = 
423             (thisInstance->CBFflags & CBF_FAIL_ALLSVRXACTIONS) == CBF_FAIL_ALLSVRXACTIONS;
424     }
425     
426     TRACE("instance created - checking validity \n");
427     
428     if (*pidInst == 0) 
429     {
430         /*  Initialisation of new Instance Identifier */
431         TRACE("new instance, callback %p flags %lX\n",pfnCallback,afCmd);
432         if (WDML_MaxInstanceID == 0)
433         {
434             /*  Nothing has been initialised - exit now ! can return TRUE since effect is the same */
435             /*  Need to set up Mutex in case it is not already present */
436             s_attrib.bInheritHandle = TRUE;
437             s_attrib.lpSecurityDescriptor = NULL;
438             s_attrib.nLength = sizeof(s_attrib);
439             handle_mutex = CreateMutexA(&s_attrib,0,DDEHandleAccess);
440             if (!handle_mutex) 
441             {
442                 ERR("CreateMutex failed - handle list  %li\n",GetLastError());
443                 HeapFree(GetProcessHeap(), 0, thisInstance);
444                 return DMLERR_SYS_ERROR;
445             }
446         }
447         if (!WDML_WaitForMutex(handle_mutex))
448         {
449             return DMLERR_SYS_ERROR;
450         }
451         
452         if (WDML_InstanceList == NULL) 
453         {
454             /* can't be another instance in this case, assign to the base pointer */
455             WDML_InstanceList = thisInstance;
456             
457             /* since first must force filter of XTYP_CONNECT and XTYP_WILDCONNECT for
458              *          present 
459              *  -------------------------------      NOTE NOTE NOTE    --------------------------
460              *          
461              *  the manual is not clear if this condition
462              *  applies to the first call to DdeInitialize from an application, or the 
463              *  first call for a given callback !!!
464              */
465             
466             thisInstance->CBFflags = thisInstance->CBFflags|APPCMD_FILTERINITS;
467             TRACE("First application instance detected OK\n");
468             /*  allocate new instance ID */
469             if ((err_no = WDML_IncrementInstanceId(thisInstance))) return err_no;
470             
471         } 
472         else 
473         {
474             /* really need to chain the new one in to the latest here, but after checking conditions
475              *  such as trying to start a conversation from an application trying to monitor */
476             reference_inst = WDML_InstanceList;
477             TRACE("Subsequent application instance - starting checks\n");
478             while (reference_inst->next != NULL) 
479             {
480                 /*
481                  *      This set of tests will work if application uses same instance Id
482                  *      at application level once allocated - which is what manual implies
483                  *      should happen. If someone tries to be 
484                  *      clever (lazy ?) it will fail to pick up that later calls are for
485                  *      the same application - should we trust them ?
486                  */
487                 if (thisInstance->instanceID == reference_inst->instanceID) 
488                 {
489                                 /* Check 1 - must be same Client-only state */
490                     
491                     if (thisInstance->clientOnly != reference_inst->clientOnly)
492                     {
493                         ret = DMLERR_DLL_USAGE;
494                         goto theError;
495                     }
496                     
497                                 /* Check 2 - cannot use 'Monitor' with any non-monitor modes */
498                     
499                     if (thisInstance->monitor != reference_inst->monitor) 
500                     {
501                         ret = DMLERR_INVALIDPARAMETER;
502                         goto theError;
503                     }
504                     
505                                 /* Check 3 - must supply different callback address */
506                     
507                     if (thisInstance->callback == reference_inst->callback)
508                     {
509                         ret = DMLERR_DLL_USAGE;
510                         goto theError;
511                     }
512                 }
513                 reference_inst = reference_inst->next;
514             }
515             /*  All cleared, add to chain */
516             
517             TRACE("Application Instance checks finished\n");
518             if ((err_no = WDML_IncrementInstanceId(thisInstance))) return err_no;
519             reference_inst->next = thisInstance;
520         }
521         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) return DMLERR_SYS_ERROR;
522         *pidInst = thisInstance->instanceID;
523         TRACE("New application instance processing finished OK\n");
524     } 
525     else 
526     {
527         /* Reinitialisation situation   --- FIX  */
528         TRACE("reinitialisation of (%p,%p,0x%lx,%ld): stub\n",pidInst,pfnCallback,afCmd,ulRes);
529         
530         if (!WDML_WaitForMutex(handle_mutex))
531         {
532             HeapFree(GetProcessHeap(), 0, thisInstance);
533             return DMLERR_SYS_ERROR;
534         }
535         
536         if (WDML_InstanceList == NULL) 
537         {
538             ret = DMLERR_DLL_USAGE;
539             goto theError;
540         }
541         HeapFree(GetProcessHeap(), 0, thisInstance); /* finished - release heap space used as work store */
542         /* can't reinitialise if we have initialised nothing !! */
543         reference_inst = WDML_InstanceList;
544         /* must first check if we have been given a valid instance to re-initialise !!  how do we do that ? */
545         /*
546          *      MS allows initialisation without specifying a callback, should we allow addition of the
547          *      callback by a later call to initialise ? - if so this lot will have to change
548          */
549         while (reference_inst->next != NULL)
550         {
551             if (*pidInst == reference_inst->instanceID && pfnCallback == reference_inst->callback)
552             {
553                 /* Check 1 - cannot change client-only mode if set via APPCMD_CLIENTONLY */
554                 
555                 if (reference_inst->clientOnly)
556                 {
557                     if  ((reference_inst->CBFflags & CBF_FAIL_ALLSVRXACTIONS) != CBF_FAIL_ALLSVRXACTIONS) 
558                     {
559                                 /* i.e. Was set to Client-only and through APPCMD_CLIENTONLY */
560                         
561                         if (!(afCmd & APPCMD_CLIENTONLY))
562                         {
563                             ret = DMLERR_DLL_USAGE;
564                             goto theError;
565                         }
566                     }
567                 }
568                 /* Check 2 - cannot change monitor modes */
569                 
570                 if (thisInstance->monitor != reference_inst->monitor) 
571                 {
572                     ret = DMLERR_DLL_USAGE;
573                     goto theError;
574                 }
575                 
576                 /* Check 3 - trying to set Client-only via APPCMD when not set so previously */
577                 
578                 if ((afCmd&APPCMD_CLIENTONLY) && !reference_inst->clientOnly)
579                 {
580                     ret = DMLERR_DLL_USAGE;
581                     goto theError;
582                 }
583                 break;
584             }
585             reference_inst = reference_inst->next;
586         }
587         if (reference_inst->next == NULL)
588         {
589             /* Crazy situation - trying to re-initialize something that has not beeen initialized !! 
590              *  
591              *  Manual does not say what we do, cannot return DMLERR_NOT_INITIALIZED so what ?
592              */
593             ret = DMLERR_INVALIDPARAMETER;
594             goto theError;
595         }
596         /*              All checked - change relevant flags */
597         
598         reference_inst->CBFflags = thisInstance->CBFflags;
599         reference_inst->clientOnly = thisInstance->clientOnly;
600         reference_inst->monitorFlags = thisInstance->monitorFlags;
601         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE))
602         {
603             HeapFree(GetProcessHeap(), 0, thisInstance);
604             return DMLERR_SYS_ERROR;
605         }
606     }
607     
608     return DMLERR_NO_ERROR;
609  theError:
610     HeapFree(GetProcessHeap(), 0, thisInstance);
611     if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", 0))
612         return DMLERR_SYS_ERROR;
613     return ret;
614 }
615
616 /*****************************************************************
617  * DdeUninitialize [USER32.@]  Frees DDEML resources
618  *
619  * PARAMS
620  *    idInst [I] Instance identifier
621  *
622  * RETURNS
623  *    Success: TRUE
624  *    Failure: FALSE
625  */
626
627 BOOL WINAPI DdeUninitialize(DWORD idInst)
628 {
629     /*  Stage one - check if we have a handle for this instance
630      */
631     WDML_INSTANCE*              thisInstance;
632     WDML_INSTANCE*              reference_inst;
633
634     if (WDML_MaxInstanceID == 0)
635     {
636         /*  Nothing has been initialised - exit now ! can return TRUE since effect is the same */
637         return TRUE;
638     }
639     
640     if (!WDML_WaitForMutex(handle_mutex))
641     {
642         return DMLERR_SYS_ERROR;
643     }
644     /*  First check instance 
645      */
646     thisInstance = WDML_FindInstance(idInst);
647     if (thisInstance == NULL)
648     {
649         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) return FALSE;
650         /*
651          *      Needs something here to record NOT_INITIALIZED ready for DdeGetLastError
652          */
653         return FALSE;
654     }
655     FIXME("(%ld): partial stub\n", idInst);
656     
657     /*   FIXME  ++++++++++++++++++++++++++++++++++++++++++
658      *  Needs to de-register all service names
659      *  
660      */
661     
662     /* Free the nodes that were not freed by this instance
663      * and remove the nodes from the list of HSZ nodes.
664      */
665     WDML_FreeAllHSZ(thisInstance);
666
667     DestroyWindow(thisInstance->hwndEvent);
668     
669     /* OK now delete the instance handle itself */
670     
671     if (WDML_InstanceList == thisInstance)
672     {
673         /* special case - the first/only entry
674          */
675         WDML_InstanceList = thisInstance->next;
676     }
677     else
678     {
679         /* general case
680          */
681         reference_inst = WDML_InstanceList;
682         while (reference_inst->next != thisInstance)
683         {
684             reference_inst = thisInstance->next;
685         }
686         reference_inst->next = thisInstance->next;
687     }
688     /* release the mutex and the heap entry
689      */
690     HeapFree(GetProcessHeap(), 0, thisInstance);
691     if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) 
692     {
693         /* should record something here, but nothing left to hang it from !!
694          */
695         return FALSE;
696     }
697     return TRUE;
698 }
699
700 /******************************************************************************
701  *            RemoveHSZNodes    (INTERNAL)
702  *
703  * Remove a node from the list of HSZ nodes.
704  */
705 static void WDML_RemoveHSZNode(WDML_INSTANCE* thisInstance, HSZ hsz)
706 {
707     HSZNode* pPrev = NULL;
708     HSZNode* pCurrent = NULL;
709     
710     /* Set the current node at the start of the list.
711      */
712     pCurrent = thisInstance->nodeList;
713     /* While we have more nodes.
714      */
715     while (pCurrent != NULL)
716     {
717         /* If we found the node we were looking for.
718          */
719         if (pCurrent->hsz == hsz)
720         {
721             /* Remove the node.
722              */
723             /* If the first node in the list is to to be removed.
724              * Set the global list pointer to the next node.
725              */
726             if (pCurrent == thisInstance->nodeList)
727             {
728                 thisInstance->nodeList = pCurrent->next;
729             }
730             /* Just fix the pointers has to skip the current
731              * node so we can delete it.
732              */
733             else
734             {
735                 pPrev->next = pCurrent->next;
736             }
737             /* Destroy this node.
738              */
739             HeapFree(GetProcessHeap(), 0, pCurrent);
740             break;
741         }
742         /* Save the previous node pointer.
743          */
744         pPrev = pCurrent;
745         /* Move on to the next node.
746          */
747         pCurrent = pCurrent->next;
748     }
749 }
750
751 /******************************************************************************
752  *            FreeAndRemoveHSZNodes    (INTERNAL)
753  *
754  * Frees up all the strings still allocated in the list and
755  * remove all the nodes from the list of HSZ nodes.
756  */
757 void WDML_FreeAllHSZ(WDML_INSTANCE* thisInstance)
758 {
759     /* Free any strings created in this instance.
760      */
761     while (thisInstance->nodeList != NULL)
762     {
763         DdeFreeStringHandle(thisInstance->instanceID, thisInstance->nodeList->hsz);
764     }
765 }
766
767 /******************************************************************************
768  *            GetSecondaryHSZValue    (INTERNAL)
769  *
770  * Insert a node to the head of the list.
771  */
772 static HSZ WDML_GetSecondaryHSZValue(WDML_INSTANCE* thisInstance, HSZ hsz)
773 {
774     HSZ hsz2 = 0;
775     
776     if (hsz != 0)
777     {
778         /* Create and set the Secondary handle */
779         if (thisInstance->unicode)
780         {
781             WCHAR wSecondaryString[MAX_BUFFER_LEN];
782             WCHAR wUniqueNum[MAX_BUFFER_LEN];
783     
784             if (DdeQueryStringW(thisInstance->instanceID, hsz,
785                                 wSecondaryString,
786                                 MAX_BUFFER_LEN, CP_WINUNICODE))
787             {
788                 wsprintfW(wUniqueNum,"(%ld)",
789                           (DWORD)thisInstance->instanceID);
790                 lstrcatW(wSecondaryString, wUniqueNum);
791                 
792                 hsz2 = GlobalAddAtomW(wSecondaryString);
793             }
794         }
795         else
796         {
797             CHAR SecondaryString[MAX_BUFFER_LEN];
798             CHAR UniqueNum[MAX_BUFFER_LEN];
799     
800             if (DdeQueryStringA(thisInstance->instanceID, hsz,
801                                 SecondaryString,
802                                 MAX_BUFFER_LEN, CP_WINANSI))
803             {
804                 wsprintfA(UniqueNum,"(%ld)", thisInstance->instanceID);
805                 lstrcatA(SecondaryString, UniqueNum);
806                 
807                 hsz2 = GlobalAddAtomA(SecondaryString);
808             }
809         }
810     }
811     return hsz2;
812 }
813
814 /******************************************************************************
815  *            InsertHSZNode    (INTERNAL)
816  *
817  * Insert a node to the head of the list.
818  */
819 static void WDML_InsertHSZNode(WDML_INSTANCE* thisInstance, HSZ hsz)
820 {
821     if (hsz != 0)
822     {
823         HSZNode* pNew = NULL;
824         /* Create a new node for this HSZ.
825          */
826         pNew = (HSZNode*)HeapAlloc(GetProcessHeap(), 0, sizeof(HSZNode));
827         if (pNew != NULL)
828         {
829             /* Set the handle value.
830              */
831             pNew->hsz = hsz;
832             
833             /* Create and set the Secondary handle */
834             pNew->hsz2 = WDML_GetSecondaryHSZValue(thisInstance, hsz);
835             
836             /* Attach the node to the head of the list. i.e most recently added is first
837              */
838             pNew->next = thisInstance->nodeList;
839             
840             /* The new node is now at the head of the list
841              * so set the global list pointer to it.
842              */
843             thisInstance->nodeList = pNew;
844         }
845         else
846         {
847             ERR("Primary HSZ Node allocation failed - out of memory\n");
848         }
849     }
850 }
851
852 /*****************************************************************************
853  *      Find_Instance_Entry
854  *
855  *      generic routine to return a pointer to the relevant DDE_HANDLE_ENTRY
856  *      for an instance Id, or NULL if the entry does not exist
857  *
858  *      ASSUMES the mutex protecting the handle entry list is reserved before calling
859  */
860 WDML_INSTANCE*  WDML_FindInstance(DWORD InstId)
861 {
862     WDML_INSTANCE*      thisInstance;
863     
864     thisInstance = WDML_InstanceList;
865     while (thisInstance != NULL)
866     {
867         if (thisInstance->instanceID == InstId)
868         {
869             return thisInstance;
870         }
871         thisInstance = thisInstance->next;
872     }
873     TRACE("Instance entry missing\n");
874     return NULL;
875 }
876
877 /******************************************************************************
878  *      WDML_ReleaseMutex
879  *
880  *      generic routine to release a reserved mutex
881  */
882 DWORD WDML_ReleaseMutex(HANDLE mutex, LPSTR mutex_name, BOOL release_handle_m)
883 {
884     if (!ReleaseMutex(mutex))
885     {
886         ERR("ReleaseMutex failed - %s mutex %li\n", mutex_name, GetLastError());
887         if (release_handle_m)
888         {
889             ReleaseMutex(handle_mutex);
890         }
891         return DMLERR_SYS_ERROR;
892     }
893     return DMLERR_NO_ERROR;
894 }
895
896 /******************************************************************************
897  *      WDML_WaitForMutex
898  *
899  *      generic routine to wait for the mutex
900  */
901 BOOL WDML_WaitForMutex(HANDLE mutex)
902 {
903     DWORD result;
904     
905     result = WaitForSingleObject(mutex, INFINITE);
906     
907     /* both errors should never occur */
908     if (WAIT_TIMEOUT == result)
909     {
910         ERR("WaitForSingleObject timed out\n");
911         return FALSE;
912     }
913     
914     if (WAIT_FAILED == result)
915     {
916         ERR("WaitForSingleObject failed - error %li\n", GetLastError());
917         return FALSE;
918     }
919    /* TRACE("Handle Mutex created/reserved\n"); */
920     
921     return TRUE;
922 }
923
924 /******************************************************************************
925  *              WDML_ReserveAtom
926  *
927  *      Routine to make an extra Add on an atom to reserve it a bit longer
928  */
929
930 void WDML_ReserveAtom(WDML_INSTANCE* thisInstance, HSZ hsz)
931 {
932     if (thisInstance->unicode)
933     {
934         WCHAR SNameBuffer[MAX_BUFFER_LEN];
935         GlobalGetAtomNameW(hsz, SNameBuffer, MAX_BUFFER_LEN);
936         GlobalAddAtomW(SNameBuffer);
937     } else {
938         CHAR SNameBuffer[MAX_BUFFER_LEN];
939         GlobalGetAtomNameA(hsz, SNameBuffer, MAX_BUFFER_LEN);
940         GlobalAddAtomA(SNameBuffer);
941     }
942 }
943
944
945 /******************************************************************************
946  *              WDML_ReleaseAtom
947  *
948  *      Routine to make a delete on an atom to release it a bit sooner
949  */
950
951 void WDML_ReleaseAtom(WDML_INSTANCE* thisInstance, HSZ hsz)
952 {
953     GlobalDeleteAtom(hsz);
954 }
955
956
957 /*****************************************************************
958  * DdeQueryStringA [USER32.@]
959  */
960 DWORD WINAPI DdeQueryStringA(DWORD idInst, HSZ hsz, LPSTR psz, DWORD cchMax, INT iCodePage)
961 {
962     DWORD               ret = 0;
963     CHAR                pString[MAX_BUFFER_LEN];
964     WDML_INSTANCE*      thisInstance;
965     
966     TRACE("(%ld, 0x%x, %p, %ld, %d): partial stub\n",
967           idInst, hsz, psz, cchMax, iCodePage);
968     
969     if (WDML_MaxInstanceID == 0)
970     {
971         /*  Nothing has been initialised - exit now ! */
972         /*  needs something for DdeGetLAstError even if the manual doesn't say so */
973         return FALSE;
974     }
975     
976     if (!WDML_WaitForMutex(handle_mutex))
977     {
978         return FALSE;
979     }
980     
981     /*  First check instance 
982      */
983     thisInstance = WDML_FindInstance(idInst);
984     if (thisInstance == NULL)
985     {
986         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) return FALSE;
987         /*
988           Needs something here to record NOT_INITIALIZED ready for DdeGetLastError
989         */
990         return FALSE;
991     }
992     
993     if (iCodePage == CP_WINANSI)
994     {
995         /* If psz is null, we have to return only the length
996          * of the string.
997          */
998         if (psz == NULL)
999         {
1000             psz = pString;
1001             cchMax = MAX_BUFFER_LEN;
1002         }
1003         
1004         ret = GlobalGetAtomNameA(hsz, (LPSTR)psz, cchMax);
1005     }
1006     
1007     WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE);
1008     
1009     TRACE("returning pointer\n"); 
1010     return ret;
1011 }
1012
1013 /*****************************************************************
1014  * DdeQueryStringW [USER32.@]
1015  */
1016
1017 DWORD WINAPI DdeQueryStringW(DWORD idInst, HSZ hsz, LPWSTR psz, DWORD cchMax, INT iCodePage)
1018 {
1019     DWORD       ret = 0;
1020     WCHAR       pString[MAX_BUFFER_LEN];
1021     int         factor = 1;
1022     
1023     TRACE("(%ld, 0x%x, %p, %ld, %d): partial-stub\n",
1024           idInst, hsz, psz, cchMax, iCodePage);
1025     
1026     if (iCodePage == CP_WINUNICODE)
1027     {
1028         /* If psz is null, we have to return only the length
1029          * of the string.
1030          */
1031         if (psz == NULL)
1032         {
1033             psz = pString;
1034             cchMax = MAX_BUFFER_LEN;
1035             /* Note: According to documentation if the psz parameter
1036              * was NULL this API must return the length of the string in bytes.
1037              */
1038             factor = (int)sizeof(WCHAR)/sizeof(BYTE);
1039         }
1040         ret = GlobalGetAtomNameW(hsz, (LPWSTR)psz, cchMax) * factor;
1041     }
1042     return ret;
1043 }
1044
1045 /*****************************************************************
1046  * DdeCreateStringHandleA [USER32.@]
1047  *
1048  * RETURNS
1049  *    Success: String handle
1050  *    Failure: 0
1051  */
1052 HSZ WINAPI DdeCreateStringHandleA(DWORD idInst, LPCSTR psz, INT codepage)
1053 {
1054     HSZ                 hsz = 0;
1055     WDML_INSTANCE*      thisInstance;
1056     
1057     TRACE("(%ld,%s,%d): partial stub\n",idInst,debugstr_a(psz),codepage);
1058     
1059     if (WDML_MaxInstanceID == 0)
1060     {
1061         /*  Nothing has been initialised - exit now ! can return FALSE since effect is the same */
1062         return FALSE;
1063     }
1064     
1065     if (!WDML_WaitForMutex(handle_mutex))
1066     {
1067         return DMLERR_SYS_ERROR;
1068     }
1069     
1070     
1071     /*  First check instance 
1072      */
1073     thisInstance = WDML_FindInstance(idInst);
1074     if (thisInstance == NULL)
1075     {
1076         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) return 0;
1077         /*
1078           Needs something here to record NOT_INITIALIZED ready for DdeGetLastError
1079         */
1080         return 0;
1081     }
1082     
1083     if (codepage == CP_WINANSI)
1084     {
1085         hsz = GlobalAddAtomA(psz);
1086         /* Save the handle so we know to clean it when
1087          * uninitialize is called.
1088          */
1089         TRACE("added atom %s with HSZ 0x%x, \n",debugstr_a(psz),hsz);
1090         WDML_InsertHSZNode(thisInstance, hsz);
1091         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) 
1092         {
1093             thisInstance->lastError = DMLERR_SYS_ERROR;
1094             return 0;
1095         }
1096         TRACE("Returning pointer\n");
1097         return hsz;
1098     } 
1099     else 
1100     {
1101         WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE);
1102     }
1103     TRACE("Returning error\n");
1104     return 0;  
1105 }
1106
1107
1108 /******************************************************************************
1109  * DdeCreateStringHandleW [USER32.@]  Creates handle to identify string
1110  *
1111  * RETURNS
1112  *    Success: String handle
1113  *    Failure: 0
1114  */
1115 HSZ WINAPI DdeCreateStringHandleW(
1116     DWORD idInst,   /* [in] Instance identifier */
1117     LPCWSTR psz,    /* [in] Pointer to string */
1118     INT codepage)   /* [in] Code page identifier */
1119 {
1120     WDML_INSTANCE*      thisInstance;
1121     HSZ                 hsz = 0;
1122     
1123     TRACE("(%ld,%s,%d): partial stub\n",idInst,debugstr_w(psz),codepage);
1124     
1125     
1126     if (WDML_MaxInstanceID == 0)
1127     {
1128         /*  Nothing has been initialised - exit now ! can return FALSE since effect is the same */
1129         return FALSE;
1130     }
1131     
1132     if (!WDML_WaitForMutex(handle_mutex))
1133     {
1134         return DMLERR_SYS_ERROR;
1135     }
1136     
1137     /*  First check instance 
1138      */
1139     thisInstance = WDML_FindInstance(idInst);
1140     if (thisInstance == NULL)
1141     {
1142         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) return 0;
1143         /*
1144           Needs something here to record NOT_INITIALIZED ready for DdeGetLastError
1145         */
1146         return 0;
1147     }
1148     
1149     FIXME("(%ld,%s,%d): partial stub\n",idInst,debugstr_w(psz),codepage);
1150     
1151     if (codepage == CP_WINUNICODE)
1152     {
1153         /*
1154          *  Should we be checking this against the unicode/ascii nature of the call to DdeInitialize ?
1155          */
1156         hsz = GlobalAddAtomW(psz);
1157         /* Save the handle so we know to clean it when
1158          * uninitialize is called.
1159          */
1160         WDML_InsertHSZNode(thisInstance, hsz);
1161         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) 
1162         {
1163             thisInstance->lastError = DMLERR_SYS_ERROR;
1164             return 0;
1165         }
1166         TRACE("Returning pointer\n");
1167         return hsz;
1168     } 
1169     else 
1170     {
1171         WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE);
1172     }
1173     TRACE("Returning error\n");
1174     return 0;
1175 }
1176
1177 /*****************************************************************
1178  *            DdeFreeStringHandle   (USER32.@)
1179  * RETURNS: success: nonzero
1180  *          fail:    zero
1181  */
1182 BOOL WINAPI DdeFreeStringHandle(DWORD idInst, HSZ hsz)
1183 {
1184     WDML_INSTANCE*      thisInstance;
1185     HSZ                 hsz2;
1186     
1187     TRACE("(%ld,%d): \n",idInst,hsz);
1188     
1189     if (WDML_MaxInstanceID == 0)
1190     {
1191         /*  Nothing has been initialised - exit now ! can return TRUE since effect is the same */
1192         return TRUE;
1193     }
1194     
1195     if (!WDML_WaitForMutex(handle_mutex))
1196     {
1197         return DMLERR_SYS_ERROR;
1198     }
1199     
1200     /*  First check instance 
1201      */
1202     thisInstance = WDML_FindInstance(idInst);
1203     if ((thisInstance == NULL) || (thisInstance->nodeList == NULL))
1204     {
1205         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) return TRUE;
1206         /*  Nothing has been initialised - exit now ! can return TRUE since effect is the same */
1207         return TRUE;
1208     }
1209     
1210     /* Remove the node associated with this HSZ.
1211      */
1212     hsz2 = thisInstance->nodeList->hsz2;        /* save this value first */
1213     
1214     WDML_RemoveHSZNode(thisInstance, hsz);
1215     /* Free the string associated with this HSZ.
1216      */
1217     WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE);
1218     if (hsz2 != 0)
1219     {
1220         GlobalDeleteAtom(hsz2);
1221     }
1222     return GlobalDeleteAtom(hsz) ? 0 : hsz;
1223 }
1224
1225 /*****************************************************************
1226  *            DdeKeepStringHandle  (USER32.@)
1227  *
1228  * RETURNS: success: nonzero
1229  *          fail:    zero
1230  */
1231 BOOL WINAPI DdeKeepStringHandle(DWORD idInst, HSZ hsz)
1232 {
1233     
1234     WDML_INSTANCE*      thisInstance;
1235     
1236     TRACE("(%ld,%d): \n",idInst,hsz);
1237     
1238     if (WDML_MaxInstanceID == 0)
1239     {
1240         /*  Nothing has been initialised - exit now ! can return FALSE since effect is the same */
1241         return FALSE;
1242     }
1243     
1244     
1245     if (!WDML_WaitForMutex(handle_mutex))
1246     {
1247         return FALSE;
1248     }
1249     
1250     /*  First check instance
1251      */
1252     thisInstance = WDML_FindInstance(idInst);
1253     if ((thisInstance == NULL) || (thisInstance->nodeList == NULL))
1254     {
1255         if (WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE)) return FALSE;
1256         /*  Nothing has been initialised - exit now ! can return FALSE since effect is the same */
1257         return FALSE;
1258         return FALSE;
1259     }
1260     WDML_ReserveAtom(thisInstance, hsz);
1261     WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE);
1262     return TRUE;
1263 }
1264
1265 /*****************************************************************
1266  *            DdeCreateDataHandle (USER32.@)
1267  */
1268 HDDEDATA WINAPI DdeCreateDataHandle(DWORD idInst, LPBYTE pSrc, DWORD cb, 
1269                                     DWORD cbOff, HSZ hszItem, UINT wFmt, 
1270                                     UINT afCmd)
1271 {
1272     /*
1273       For now, we ignore idInst, hszItem, wFmt, and afCmd.
1274       The purpose of these arguments still need to be investigated.
1275     */
1276     
1277     HGLOBAL                     hMem;
1278     LPBYTE                      pByte;
1279     DDE_DATAHANDLE_HEAD*        pDdh;
1280     
1281     TRACE("(%ld,%p,%ld,%ld,0x%lx,%d,%d): semi-stub\n",
1282           idInst,pSrc,cb,cbOff,(DWORD)hszItem,wFmt,afCmd);
1283     
1284     /* we use the first 4 bytes to store the size */
1285     if (!(hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cb + sizeof(DDE_DATAHANDLE_HEAD))))
1286     {
1287         ERR("GlobalAlloc failed\n");
1288         return 0;
1289     }   
1290     
1291     pDdh = (DDE_DATAHANDLE_HEAD*)GlobalLock(hMem);
1292     pDdh->cfFormat = wFmt;
1293     
1294     pByte = (LPBYTE)(pDdh + 1);
1295     if (pSrc)
1296     {
1297         memcpy(pByte, pSrc + cbOff, cb);
1298     }
1299     GlobalUnlock(hMem);
1300     
1301     return (HDDEDATA)hMem;
1302 }
1303
1304 /*****************************************************************
1305  *
1306  *            DdeAddData (USER32.@)
1307  */
1308 HDDEDATA WINAPI DdeAddData(HDDEDATA hData, LPBYTE pSrc, DWORD cb, DWORD cbOff)
1309 {
1310     DWORD       old_sz, new_sz;
1311     LPBYTE      pDst; 
1312     
1313     pDst = DdeAccessData(hData, &old_sz);
1314     if (!pDst) return 0;
1315     
1316     new_sz = cb + cbOff;
1317     if (new_sz > old_sz)
1318     {
1319         DdeUnaccessData(hData);
1320         hData = GlobalReAlloc((HGLOBAL)hData, new_sz + sizeof(DDE_DATAHANDLE_HEAD), 
1321                               GMEM_MOVEABLE | GMEM_DDESHARE);
1322         pDst = DdeAccessData(hData, &old_sz);
1323     }
1324     
1325     if (!pDst) return 0;
1326     
1327     memcpy(pDst + cbOff, pSrc, cb);
1328     DdeUnaccessData(hData);
1329     return hData;
1330 }
1331
1332 /*****************************************************************
1333  *            DdeSetUserHandle (USER32.@)
1334  */
1335 BOOL WINAPI DdeSetUserHandle(HCONV hConv, DWORD id, DWORD hUser)
1336 {
1337     WDML_CONV*  pConv;
1338     BOOL        ret = TRUE;
1339
1340     WDML_WaitForMutex(handle_mutex);
1341
1342     pConv = WDML_GetConv(hConv);
1343     if (pConv == NULL)
1344     {
1345         ret = FALSE;
1346         goto theError;
1347     }
1348     if (id == QID_SYNC) 
1349     {
1350         pConv->hUser = hUser;
1351     }
1352     else
1353     {
1354         WDML_XACT*      pXAct;
1355
1356         pXAct = WDML_FindTransaction(pConv, id);
1357         if (pXAct)
1358         {
1359             pXAct->hUser = hUser;
1360         }
1361         else
1362         {
1363             pConv->thisInstance->lastError = DMLERR_UNFOUND_QUEUE_ID;
1364             ret = FALSE;
1365         }
1366     }
1367  theError:
1368     WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE);
1369     return ret;
1370 }
1371
1372 /******************************************************************************
1373  * DdeGetData [USER32.@]  Copies data from DDE object to local buffer
1374  *
1375  * RETURNS
1376  *    Size of memory object associated with handle
1377  */
1378 DWORD WINAPI DdeGetData(
1379     HDDEDATA hData, /* [in] Handle to DDE object */
1380     LPBYTE pDst,    /* [in] Pointer to destination buffer */
1381     DWORD cbMax,    /* [in] Amount of data to copy */
1382     DWORD cbOff)    /* [in] Offset to beginning of data */
1383 {
1384     DWORD   dwSize, dwRet;
1385     LPBYTE  pByte;
1386     
1387     TRACE("(%08lx,%p,%ld,%ld)\n",(DWORD)hData,pDst,cbMax,cbOff);
1388     
1389     pByte = DdeAccessData(hData, &dwSize);
1390     
1391     if (pByte) 
1392     {
1393         if (cbOff + cbMax < dwSize)
1394         {
1395             dwRet = cbMax;
1396         }
1397         else if (cbOff < dwSize)
1398         {
1399             dwRet = dwSize - cbOff;
1400         }
1401         else
1402         {
1403             dwRet = 0;
1404         }
1405         if (pDst && dwRet != 0)
1406         {
1407             memcpy(pDst, pByte + cbOff, dwRet);
1408         }
1409         DdeUnaccessData(hData);
1410     }
1411     else
1412     {
1413         dwRet = 0;
1414     }
1415     return dwRet;
1416 }
1417
1418 /*****************************************************************
1419  *            DdeAccessData (USER32.@)
1420  */
1421 LPBYTE WINAPI DdeAccessData(HDDEDATA hData, LPDWORD pcbDataSize)
1422 {
1423     HGLOBAL                     hMem = (HGLOBAL)hData;
1424     DDE_DATAHANDLE_HEAD*        pDdh;
1425     
1426     TRACE("(%08lx,%p)\n", (DWORD)hData, pcbDataSize);
1427     
1428     pDdh = (DDE_DATAHANDLE_HEAD*)GlobalLock(hMem);
1429     if (pDdh == NULL)
1430     {
1431         ERR("Failed on GlobalLock(%04x)\n", hMem);
1432         return 0;
1433     }
1434     
1435     if (pcbDataSize != NULL)
1436     {
1437         *pcbDataSize = GlobalSize(hMem) - sizeof(DDE_DATAHANDLE_HEAD);
1438     }
1439     
1440     return (LPBYTE)(pDdh + 1);
1441 }
1442
1443 /*****************************************************************
1444  *            DdeUnaccessData (USER32.@)
1445  */
1446 BOOL WINAPI DdeUnaccessData(HDDEDATA hData)
1447 {
1448     HGLOBAL hMem = (HGLOBAL)hData;
1449     
1450     TRACE("(0x%lx)\n", (DWORD)hData);
1451     
1452     GlobalUnlock(hMem);
1453     
1454     return TRUE;
1455 }
1456
1457 /*****************************************************************
1458  *            DdeFreeDataHandle   (USER32.@)
1459  */
1460 BOOL WINAPI DdeFreeDataHandle(HDDEDATA hData)
1461 {       
1462     return GlobalFree((HGLOBAL)hData) == 0;
1463 }
1464
1465 /* ================================================================
1466  *
1467  *                  Global <=> Data handle management
1468  *
1469  * ================================================================ */
1470  
1471 /* Note: we use a DDEDATA, but layout of DDEDATA, DDEADVISE and DDEPOKE structures is similar:
1472  *    offset      size 
1473  *    (bytes)    (bits) comment
1474  *      0          16   bit fields for options (release, ackreq, response...)
1475  *      2          16   clipboard format
1476  *      4          ?    data to be used
1477  */
1478 HDDEDATA        WDML_Global2DataHandle(HGLOBAL hMem)
1479 {
1480     DDEDATA*    pDd;
1481  
1482     if (hMem)
1483     {
1484         pDd = GlobalLock(hMem);
1485         if (pDd)
1486         {
1487             return DdeCreateDataHandle(0, pDd->Value,
1488                                        GlobalSize(hMem) - (sizeof(DDEDATA) - 1),
1489                                        0, 0, pDd->cfFormat, 0);
1490         }
1491     }
1492     return 0;
1493 }
1494  
1495 HGLOBAL WDML_DataHandle2Global(HDDEDATA hDdeData, BOOL fResponse, BOOL fRelease, 
1496                                BOOL fDeferUpd, BOOL fAckReq)
1497 {
1498     DDE_DATAHANDLE_HEAD*        pDdh;
1499     DWORD                       dwSize;
1500     HGLOBAL                     hMem = 0;
1501  
1502     dwSize = GlobalSize(hDdeData) - sizeof(DDE_DATAHANDLE_HEAD);
1503     pDdh = (DDE_DATAHANDLE_HEAD*)GlobalLock(hDdeData);
1504     if (dwSize && pDdh)
1505     {
1506         hMem = GlobalAlloc(sizeof(DDEDATA) - 1 + dwSize,
1507                            GMEM_MOVEABLE | GMEM_DDESHARE);
1508         if (hMem)
1509         {
1510             DDEDATA*    ddeData;
1511  
1512             ddeData = GlobalLock(hMem);
1513             if (ddeData)
1514             {
1515                 ddeData->fResponse = fResponse;
1516                 ddeData->fRelease = fRelease;
1517                 ddeData->reserved /*fDeferUpd*/ = fDeferUpd;
1518                 ddeData->fAckReq = fAckReq;
1519                 ddeData->cfFormat = pDdh->cfFormat;
1520                 memcpy(ddeData->Value, pDdh + 1, dwSize);
1521                 GlobalUnlock(hMem);
1522             }
1523         }
1524         GlobalUnlock(hDdeData);
1525     }
1526  
1527     return hMem;
1528 }                                                                                                                                                                                 
1529
1530 /*****************************************************************
1531  *            DdeEnableCallback (USER32.@)
1532  */
1533 BOOL WINAPI DdeEnableCallback(DWORD idInst, HCONV hConv, UINT wCmd)
1534 {
1535     FIXME("(%ld, 0x%x, %d) stub\n", idInst, hConv, wCmd);
1536     
1537     return 0;
1538 }
1539
1540 /******************************************************************************
1541  * DdeGetLastError [USER32.@]  Gets most recent error code
1542  *
1543  * PARAMS
1544  *    idInst [I] Instance identifier
1545  *
1546  * RETURNS
1547  *    Last error code
1548  */
1549 UINT WINAPI DdeGetLastError(DWORD idInst)
1550 {
1551     DWORD               error_code;
1552     WDML_INSTANCE*      thisInstance;
1553     
1554     FIXME("(%ld): error reporting is weakly implemented\n",idInst);
1555     
1556     if (WDML_MaxInstanceID == 0)
1557     {
1558         /*  Nothing has been initialised - exit now ! */
1559         return DMLERR_DLL_NOT_INITIALIZED;
1560     }
1561     
1562     if (!WDML_WaitForMutex(handle_mutex))
1563     {
1564         return DMLERR_SYS_ERROR;
1565     }
1566     
1567     /*  First check instance
1568      */
1569     thisInstance = WDML_FindInstance(idInst);
1570     if  (thisInstance == NULL) 
1571     {
1572         error_code = DMLERR_DLL_NOT_INITIALIZED;
1573     }
1574     else
1575     {
1576         error_code = thisInstance->lastError;
1577         thisInstance->lastError = 0;
1578     }
1579     
1580     WDML_ReleaseMutex(handle_mutex, "handle_mutex", FALSE);
1581     return error_code;
1582 }
1583
1584 /*****************************************************************
1585  *            DdeCmpStringHandles (USER32.@)
1586  *
1587  * Compares the value of two string handles.  This comparison is
1588  * not case sensitive.
1589  *
1590  * Returns:
1591  * -1 The value of hsz1 is zero or less than hsz2
1592  * 0  The values of hsz 1 and 2 are the same or both zero.
1593  * 1  The value of hsz2 is zero of less than hsz1
1594  */
1595 INT WINAPI DdeCmpStringHandles(HSZ hsz1, HSZ hsz2)
1596 {
1597     CHAR psz1[MAX_BUFFER_LEN];
1598     CHAR psz2[MAX_BUFFER_LEN];
1599     int ret = 0;
1600     int ret1, ret2;
1601     
1602     ret1 = GlobalGetAtomNameA(hsz1, psz1, MAX_BUFFER_LEN);
1603     ret2 = GlobalGetAtomNameA(hsz2, psz2, MAX_BUFFER_LEN);
1604     TRACE("(%04lx<%s> %04lx<%s>);\n", (DWORD)hsz1, psz1, (DWORD)hsz2, psz2);
1605     
1606     /* Make sure we found both strings.
1607      */
1608     if (ret1 == 0 && ret2 == 0)
1609     {
1610         /* If both are not found, return both  "zero strings".
1611          */
1612         ret = 0;
1613     }
1614     else if (ret1 == 0)
1615     {
1616         /* If hsz1 is a not found, return hsz1 is "zero string".
1617          */
1618         ret = -1;
1619     }
1620     else if (ret2 == 0)
1621     {
1622         /* If hsz2 is a not found, return hsz2 is "zero string".
1623          */
1624         ret = 1;
1625     }
1626     else
1627     {
1628         /* Compare the two strings we got (case insensitive).
1629          */
1630         ret = strcasecmp(psz1, psz2);
1631         /* Since strcmp returns any number smaller than
1632          * 0 when the first string is found to be less than
1633          * the second one we must make sure we are returning
1634          * the proper values.
1635          */
1636         if (ret < 0)
1637         {
1638             ret = -1;
1639         }
1640         else if (ret > 0)
1641         {
1642             ret = 1;
1643         }
1644     }
1645     
1646     return ret;
1647 }
1648
1649 /******************************************************************
1650  *              DdeQueryConvInfo (USER32.@)
1651  *
1652  */
1653 UINT WINAPI DdeQueryConvInfo(HCONV hConv, DWORD id, LPCONVINFO lpConvInfo)
1654 {
1655     UINT        ret = lpConvInfo->cb;
1656     CONVINFO    ci;
1657     WDML_CONV*  pConv;
1658
1659     FIXME("semi-stub.\n");
1660
1661     WDML_WaitForMutex(handle_mutex);
1662
1663     pConv = WDML_GetConv(hConv);
1664     if (pConv == NULL)
1665     {
1666         WDML_ReleaseMutex(handle_mutex, "hande_mutex", FALSE);
1667         return 0;
1668     }
1669
1670     ci.hConvPartner = 0; /* FIXME */
1671     ci.hszSvcPartner = pConv->hszService;
1672     ci.hszServiceReq = pConv->hszService; /* FIXME: they shouldn't be the same, should they ? */
1673     ci.hszTopic = pConv->hszTopic;
1674     ci.wStatus = ST_CLIENT /* FIXME */ | ST_CONNECTED;
1675     ci.wConvst = 0; /* FIXME */
1676     ci.wLastError = 0; /* FIXME: note it's not the instance last error */
1677     ci.hConvList = 0;
1678     ci.ConvCtxt = pConv->convContext;
1679     if (ci.wStatus & ST_CLIENT)
1680     {
1681         ci.hwnd = pConv->hwndClient;
1682         ci.hwndPartner = pConv->hwndServer;
1683     }
1684     else
1685     {
1686         ci.hwnd = pConv->hwndServer;
1687         ci.hwndPartner = pConv->hwndClient;
1688     }
1689     if (id == QID_SYNC)
1690     {
1691         ci.hUser = pConv->hUser;
1692         ci.hszItem = 0;
1693         ci.wFmt = 0;
1694         ci.wType = 0;
1695     }
1696     else
1697     {
1698         WDML_XACT*      pXAct;
1699
1700         pXAct = WDML_FindTransaction(pConv, id);
1701         if (pXAct)
1702         {
1703             ci.hUser = pXAct->hUser;
1704             ci.hszItem = 0; /* FIXME */
1705             ci.wFmt = 0; /* FIXME */
1706             ci.wType = 0; /* FIXME */
1707         }
1708         else
1709         {
1710             ret = 0;
1711             pConv->thisInstance->lastError = DMLERR_UNFOUND_QUEUE_ID;
1712         }
1713     }
1714
1715     WDML_ReleaseMutex(handle_mutex, "hande_mutex", FALSE);
1716     memcpy(lpConvInfo, &ci, min(lpConvInfo->cb, sizeof(ci)));
1717     return ret;
1718 }
1719
1720 /* ================================================================
1721  *
1722  *                      Server management
1723  *
1724  * ================================================================ */
1725
1726 /******************************************************************
1727  *              WDML_AddServer
1728  *
1729  *
1730  */
1731 WDML_SERVER*    WDML_AddServer(WDML_INSTANCE* thisInstance, HSZ hszService, HSZ hszTopic)
1732 {
1733     WDML_SERVER* pServer;
1734     
1735     pServer = (WDML_SERVER*)HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_SERVER));
1736     if (pServer == NULL) return NULL;
1737     
1738     pServer->hszService = hszService;
1739     pServer->hszTopic = 0;
1740     pServer->filterOn = TRUE;
1741     
1742     pServer->next = thisInstance->servers;
1743     thisInstance->servers = pServer;
1744     return pServer;
1745 }
1746
1747 /******************************************************************
1748  *              WDML_RemoveServer
1749  *
1750  *
1751  */
1752 void WDML_RemoveServer(WDML_INSTANCE* thisInstance, HSZ hszService, HSZ hszTopic)
1753 {
1754     WDML_SERVER* pPrev = NULL;
1755     WDML_SERVER* pCurrent = NULL;
1756     
1757     pCurrent = thisInstance->servers;
1758     
1759     while (pCurrent != NULL)
1760     {
1761         if (DdeCmpStringHandles(pCurrent->hszService, hszService) == 0)
1762         {
1763             if (pCurrent == thisInstance->servers)
1764             {
1765                 thisInstance->servers = pCurrent->next;
1766             }
1767             else
1768             {
1769                 pPrev->next = pCurrent->next;
1770             }
1771             
1772             DestroyWindow(pCurrent->hwndServer);
1773             
1774             HeapFree(GetProcessHeap(), 0, pCurrent);
1775             break;
1776         }
1777         
1778         pPrev = pCurrent;
1779         pCurrent = pCurrent->next;
1780     }
1781 }
1782
1783 /*****************************************************************************
1784  *      WDML_FindServer
1785  *
1786  *      generic routine to return a pointer to the relevant ServiceNode
1787  *      for a given service name, or NULL if the entry does not exist
1788  *
1789  *      ASSUMES the mutex protecting the handle entry list is reserved before calling
1790  */
1791 WDML_SERVER*    WDML_FindServer(WDML_INSTANCE* thisInstance, HSZ hszService, HSZ hszTopic)
1792 {
1793     WDML_SERVER*        pServer;
1794     
1795     for (pServer = thisInstance->servers; pServer != NULL; pServer = pServer->next)
1796     {
1797         if (hszService == pServer->hszService)
1798         {
1799             return pServer;
1800         }
1801     }
1802     TRACE("Service name missing\n");
1803     return NULL;
1804 }
1805
1806 /* ================================================================
1807  *
1808  *              Conversation management
1809  *
1810  * ================================================================ */
1811
1812 /******************************************************************
1813  *              WDML_AddConv
1814  *
1815  *
1816  */
1817 WDML_CONV*      WDML_AddConv(WDML_INSTANCE* thisInstance, WDML_SIDE side,
1818                              HSZ hszService, HSZ hszTopic, HWND hwndClient, HWND hwndServer)
1819 {
1820     WDML_CONV*  pConv;
1821     
1822     /* no converstation yet, add it */
1823     pConv = HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_CONV));
1824     if (!pConv) return NULL;
1825     
1826     pConv->thisInstance = thisInstance;
1827     pConv->hszService = hszService;
1828     pConv->hszTopic = hszTopic;
1829     pConv->hwndServer = hwndServer;
1830     pConv->hwndClient = hwndClient;
1831     pConv->transactions = NULL;
1832     pConv->hUser = 0;
1833
1834     pConv->next = thisInstance->convs[side];
1835     thisInstance->convs[side] = pConv;
1836     
1837     return pConv;
1838 }
1839
1840 /******************************************************************
1841  *              WDML_FindConv
1842  *
1843  *
1844  */
1845 WDML_CONV*      WDML_FindConv(WDML_INSTANCE* thisInstance, WDML_SIDE side, 
1846                               HSZ hszService, HSZ hszTopic)
1847 {
1848     WDML_CONV*  pCurrent = NULL;
1849     
1850     for (pCurrent = thisInstance->convs[side]; pCurrent != NULL; pCurrent = pCurrent->next)
1851     {
1852         if (DdeCmpStringHandles(pCurrent->hszService, hszService) == 0 &&
1853             DdeCmpStringHandles(pCurrent->hszTopic, hszTopic) == 0)
1854         {
1855             return pCurrent;
1856         }
1857         
1858     }
1859     return NULL;
1860 }
1861
1862 /******************************************************************
1863  *              WDML_RemoveConv
1864  *
1865  *
1866  */
1867 void WDML_RemoveConv(WDML_INSTANCE* thisInstance, WDML_SIDE side, HCONV hConv)
1868 {
1869     WDML_CONV* pPrev = NULL;
1870     WDML_CONV* pRef = WDML_GetConv(hConv);
1871     WDML_CONV* pCurrent = NULL;
1872
1873     if (!pRef)
1874         return;
1875     for (pCurrent = thisInstance->convs[side]; pCurrent != NULL; pCurrent = (pPrev = pCurrent)->next)
1876     {
1877         if (pCurrent == pRef)
1878         {
1879             if (pCurrent == thisInstance->convs[side])
1880             {
1881                 thisInstance->convs[side] = pCurrent->next;
1882             }
1883             else
1884             {
1885                 pPrev->next = pCurrent->next;
1886             }
1887             
1888             HeapFree(GetProcessHeap(), 0, pCurrent);
1889             break;
1890         }
1891     }
1892 }
1893
1894 /******************************************************************
1895  *              WDML_GetConv
1896  *
1897  * 
1898  */
1899 WDML_CONV*      WDML_GetConv(HCONV hConv)
1900 {
1901     /* FIXME: should do better checking */
1902     return (WDML_CONV*)hConv;
1903 }
1904
1905 /* ================================================================
1906  *
1907  *                      Link (hot & warm) management
1908  *
1909  * ================================================================ */
1910
1911 /******************************************************************
1912  *              WDML_AddLink
1913  *
1914  *
1915  */
1916 void WDML_AddLink(WDML_INSTANCE* thisInstance, HCONV hConv, WDML_SIDE side, 
1917                   UINT wType, HSZ hszItem, UINT wFmt)
1918 {
1919     WDML_LINK*  pLink;
1920     
1921     TRACE("AddDdeLink was called...\n");
1922     
1923     pLink = HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_LINK));
1924     if (pLink == NULL)
1925     {
1926         ERR("OOM\n");
1927         return;
1928     }
1929
1930     pLink->hConv = hConv;
1931     pLink->transactionType = wType;
1932     pLink->hszItem = hszItem;
1933     pLink->uFmt = wFmt;
1934     pLink->hDdeData = 0;
1935     pLink->next = thisInstance->links[side];
1936     thisInstance->links[side] = pLink;
1937 }
1938
1939 /******************************************************************
1940  *              WDML_RemoveLink
1941  *
1942  *
1943  */
1944 void WDML_RemoveLink(WDML_INSTANCE* thisInstance, HCONV hConv, WDML_SIDE side, 
1945                      HSZ hszItem, UINT uFmt)
1946 {
1947     WDML_LINK* pPrev = NULL;
1948     WDML_LINK* pCurrent = NULL;
1949     
1950     pCurrent = thisInstance->links[side];
1951     
1952     while (pCurrent != NULL)
1953     {
1954         if (pCurrent->hConv == hConv &&
1955             DdeCmpStringHandles(pCurrent->hszItem, hszItem) == 0 &&
1956             pCurrent->uFmt == uFmt)
1957         {
1958             if (pCurrent == thisInstance->links[side])
1959             {
1960                 thisInstance->links[side] = pCurrent->next;
1961             }
1962             else
1963             {
1964                 pPrev->next = pCurrent->next;
1965             }
1966             
1967             if (pCurrent->hDdeData)
1968             {
1969                 DdeFreeDataHandle(pCurrent->hDdeData);
1970             }
1971             
1972             HeapFree(GetProcessHeap(), 0, pCurrent);
1973             break;
1974         }
1975         
1976         pPrev = pCurrent;
1977         pCurrent = pCurrent->next;
1978     }
1979 }
1980
1981 /* this function is called to remove all links related to the conv.
1982    It should be called from both client and server when terminating 
1983    the conversation.
1984 */
1985 /******************************************************************
1986  *              WDML_RemoveAllLinks
1987  *
1988  *
1989  */
1990 void WDML_RemoveAllLinks(WDML_INSTANCE* thisInstance, HCONV hConv, WDML_SIDE side)
1991 {
1992     WDML_LINK* pPrev = NULL;
1993     WDML_LINK* pCurrent = NULL;
1994     WDML_LINK* pNext = NULL;
1995     
1996     pCurrent = thisInstance->links[side];
1997     
1998     while (pCurrent != NULL)
1999     {
2000         if (pCurrent->hConv == hConv)
2001         {
2002             if (pCurrent == thisInstance->links[side])
2003             {
2004                 thisInstance->links[side] = pCurrent->next;
2005                 pNext = pCurrent->next;
2006             }
2007             else
2008             {
2009                 pPrev->next = pCurrent->next;
2010                 pNext = pCurrent->next;
2011             }
2012             
2013             if (pCurrent->hDdeData)
2014             {
2015                 DdeFreeDataHandle(pCurrent->hDdeData);
2016             }
2017             
2018             HeapFree(GetProcessHeap(), 0, pCurrent);
2019             pCurrent = NULL;
2020         }
2021         
2022         if (pCurrent)
2023         {
2024             pPrev = pCurrent;
2025             pCurrent = pCurrent->next;
2026         }
2027         else
2028         {
2029             pCurrent = pNext;
2030         }
2031     }
2032 }
2033
2034 /******************************************************************
2035  *              WDML_FindLink
2036  *
2037  *
2038  */
2039 WDML_LINK*      WDML_FindLink(WDML_INSTANCE* thisInstance, HCONV hConv, WDML_SIDE side, 
2040                               HSZ hszItem, UINT uFmt)
2041 {
2042     WDML_LINK*  pCurrent = NULL;
2043     
2044     for (pCurrent = thisInstance->links[side]; pCurrent != NULL; pCurrent = pCurrent->next)
2045     {
2046         /* we don't need to check for transaction type as
2047            it can be altered */
2048         
2049         if (pCurrent->hConv == hConv &&
2050             DdeCmpStringHandles(pCurrent->hszItem, hszItem) == 0 &&
2051             pCurrent->uFmt == uFmt)
2052         {
2053             break;
2054         }
2055         
2056     }
2057     
2058     return pCurrent;
2059 }
2060
2061 /* ================================================================
2062  *
2063  *                      Transaction management
2064  *
2065  * ================================================================ */
2066
2067 /******************************************************************
2068  *              WDML_AllocTransaction
2069  *
2070  * Alloc a transaction structure for handling the message ddeMsg
2071  */
2072 WDML_XACT*      WDML_AllocTransaction(WDML_INSTANCE* thisInstance, UINT ddeMsg)
2073 {
2074     WDML_XACT*          pXAct;
2075     static WORD         tid = 1;        /* FIXME: wrap around */
2076
2077     pXAct = HeapAlloc(GetProcessHeap(), 0, sizeof(WDML_XACT));
2078     if (!pXAct) 
2079     {
2080         thisInstance->lastError = DMLERR_MEMORY_ERROR;
2081         return NULL;
2082     }
2083
2084     pXAct->xActID = tid++;
2085     pXAct->ddeMsg = ddeMsg;
2086     pXAct->hDdeData = 0;
2087     pXAct->hUser = 0;
2088     pXAct->next = NULL;
2089     
2090     return pXAct;
2091 }
2092
2093 /******************************************************************
2094  *              WDML_QueueTransaction
2095  *
2096  * Adds a transaction to the list of transaction
2097  */
2098 void    WDML_QueueTransaction(WDML_CONV* pConv, WDML_XACT* pXAct)
2099 {
2100     WDML_XACT** pt;
2101     
2102     /* advance to last in queue */
2103     for (pt = &pConv->transactions; *pt != NULL; pt = &(*pt)->next);
2104     *pt = pXAct;
2105 }
2106
2107 /******************************************************************
2108  *              WDML_UnQueueTransaction
2109  *
2110  *
2111  */
2112 BOOL    WDML_UnQueueTransaction(WDML_CONV* pConv, WDML_XACT*  pXAct)
2113 {
2114     WDML_XACT** pt;
2115
2116     for (pt = &pConv->transactions; *pt; pt = &(*pt)->next)
2117     {
2118         if (*pt == pXAct)
2119         {
2120             *pt = pXAct->next;
2121             return TRUE;
2122         }
2123     }
2124     return FALSE;
2125 }
2126
2127 /******************************************************************
2128  *              WDML_FreeTransaction
2129  *
2130  *
2131  */
2132 void    WDML_FreeTransaction(WDML_XACT* pXAct)
2133 {
2134     HeapFree(GetProcessHeap(), 0, pXAct);
2135 }
2136
2137 /******************************************************************
2138  *              WDML_FindTransaction
2139  *
2140  *
2141  */
2142 WDML_XACT*      WDML_FindTransaction(WDML_CONV* pConv, DWORD tid)
2143 {
2144     WDML_XACT* pXAct;
2145     
2146     tid = HIWORD(tid);
2147     for (pXAct = pConv->transactions; pXAct; pXAct = pXAct->next)
2148     {
2149         if (pXAct->xActID == tid)
2150             break;
2151     }
2152     return pXAct;
2153 }
2154
2155 struct tagWDML_BroadcastPmt
2156 {
2157     LPCSTR      clsName;
2158     UINT        uMsg;
2159     WPARAM      wParam;
2160     LPARAM      lParam;
2161 };
2162
2163 /******************************************************************
2164  *              WDML_BroadcastEnumProc
2165  *
2166  *
2167  */
2168 static  BOOL CALLBACK WDML_BroadcastEnumProc(HWND hWnd, LPARAM lParam)
2169 {
2170     struct tagWDML_BroadcastPmt*        s = (struct tagWDML_BroadcastPmt*)lParam;
2171     char                                buffer[128];
2172
2173     if (GetClassNameA(hWnd, buffer, sizeof(buffer)) > 0 &&
2174         strcmp(buffer, s->clsName) == 0)
2175     {
2176         PostMessageA(hWnd, s->uMsg, s->wParam, s->lParam);
2177     }
2178     return TRUE;
2179 }
2180
2181 /******************************************************************
2182  *              WDML_BroadcastDDEWindows
2183  *
2184  *
2185  */
2186 void WDML_BroadcastDDEWindows(const char* clsName, UINT uMsg, WPARAM wParam, LPARAM lParam)
2187 {
2188     struct tagWDML_BroadcastPmt s;
2189
2190     s.clsName = clsName;
2191     s.uMsg    = uMsg;
2192     s.wParam  = wParam;
2193     s.lParam  = lParam;
2194     EnumWindows(WDML_BroadcastEnumProc, (LPARAM)&s);
2195 }