Unicodify wineesd.
[wine] / dlls / winmm / driver.c
1 /*
2  * WINE Drivers functions
3  *
4  * Copyright 1994 Martin Ayotte
5  * Copyright 1998 Marcus Meissner
6  * Copyright 1999 Eric Pouech
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include <string.h>
24 #include <stdarg.h>
25 #include "windef.h"
26 #include "winbase.h"
27 #include "wingdi.h"
28 #include "winuser.h"
29 #include "winnls.h"
30 #include "winreg.h"
31 #include "mmddk.h"
32 #include "winemm.h"
33 #include "wine/debug.h"
34 #include "wine/unicode.h"
35 #include "excpt.h"
36 #include "wine/exception.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(driver);
39
40 static LPWINE_DRIVER   lpDrvItemList  /* = NULL */;
41 static const WCHAR HKLM_BASE[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
42                                   'W','i','n','d','o','w','s',' ','N','T','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',0};
43
44 WINE_MMTHREAD*  (*pFnGetMMThread16)(UINT16 h) /* = NULL */;
45 LPWINE_DRIVER   (*pFnOpenDriver16)(LPCWSTR,LPCWSTR,LPARAM) /* = NULL */;
46 LRESULT         (*pFnCloseDriver16)(UINT16,LPARAM,LPARAM) /* = NULL */;
47 LRESULT         (*pFnSendMessage16)(UINT16,UINT,LPARAM,LPARAM) /* = NULL */;
48
49 /**************************************************************************
50  *                      DRIVER_GetNumberOfModuleRefs            [internal]
51  *
52  * Returns the number of open drivers which share the same module.
53  */
54 static  unsigned DRIVER_GetNumberOfModuleRefs(HMODULE hModule, WINE_DRIVER** found)
55 {
56     LPWINE_DRIVER       lpDrv;
57     unsigned            count = 0;
58
59     if (found) *found = NULL;
60     for (lpDrv = lpDrvItemList; lpDrv; lpDrv = lpDrv->lpNextItem)
61     {
62         if (!(lpDrv->dwFlags & WINE_GDF_16BIT) && lpDrv->d.d32.hModule == hModule)
63         {
64             if (found && !*found) *found = lpDrv;
65             count++;
66         }
67     }
68     return count;
69 }
70
71 /**************************************************************************
72  *                              DRIVER_FindFromHDrvr            [internal]
73  *
74  * From a hDrvr being 32 bits, returns the WINE internal structure.
75  */
76 LPWINE_DRIVER   DRIVER_FindFromHDrvr(HDRVR hDrvr)
77 {
78     LPWINE_DRIVER       d = (LPWINE_DRIVER)hDrvr;
79
80     __TRY
81     {
82         if (d && d->dwMagic != WINE_DI_MAGIC) d = NULL;
83     }
84     __EXCEPT(NULL)
85     {
86         return NULL;
87     }
88     __ENDTRY;
89
90     return d;
91 }
92
93 /**************************************************************************
94  *                              DRIVER_SendMessage              [internal]
95  */
96 static LRESULT inline DRIVER_SendMessage(LPWINE_DRIVER lpDrv, UINT msg,
97                                          LPARAM lParam1, LPARAM lParam2)
98 {
99     LRESULT             ret = 0;
100
101     if (lpDrv->dwFlags & WINE_GDF_16BIT) {
102         /* no need to check mmsystem presence: the driver must have been opened as a 16 bit one,
103          */
104         if (pFnSendMessage16)
105             ret = pFnSendMessage16(lpDrv->d.d16.hDriver16, msg, lParam1, lParam2);
106     } else {
107         TRACE("Before call32 proc=%p drvrID=%08lx hDrv=%p wMsg=%04x p1=%08lx p2=%08lx\n", 
108               lpDrv->d.d32.lpDrvProc, lpDrv->d.d32.dwDriverID, (HDRVR)lpDrv, msg, lParam1, lParam2);
109         ret = lpDrv->d.d32.lpDrvProc(lpDrv->d.d32.dwDriverID, (HDRVR)lpDrv, msg, lParam1, lParam2);
110         TRACE("After  call32 proc=%p drvrID=%08lx hDrv=%p wMsg=%04x p1=%08lx p2=%08lx => %08lx\n", 
111               lpDrv->d.d32.lpDrvProc, lpDrv->d.d32.dwDriverID, (HDRVR)lpDrv, msg, lParam1, lParam2, ret);
112     }
113     return ret;
114 }
115
116 /**************************************************************************
117  *                              SendDriverMessage               [WINMM.@]
118  *                              DrvSendMessage                  [WINMM.@]
119  */
120 LRESULT WINAPI SendDriverMessage(HDRVR hDriver, UINT msg, LPARAM lParam1,
121                                  LPARAM lParam2)
122 {
123     LPWINE_DRIVER       lpDrv;
124     LRESULT             retval = 0;
125
126     TRACE("(%p, %04X, %08lX, %08lX)\n", hDriver, msg, lParam1, lParam2);
127
128     if ((lpDrv = DRIVER_FindFromHDrvr(hDriver)) != NULL) {
129         retval = DRIVER_SendMessage(lpDrv, msg, lParam1, lParam2);
130     } else {
131         WARN("Bad driver handle %p\n", hDriver);
132     }
133     TRACE("retval = %ld\n", retval);
134
135     return retval;
136 }
137
138 /**************************************************************************
139  *                              DRIVER_RemoveFromList           [internal]
140  *
141  * Generates all the logic to handle driver closure / deletion
142  * Removes a driver struct to the list of open drivers.
143  */
144 static  BOOL    DRIVER_RemoveFromList(LPWINE_DRIVER lpDrv)
145 {
146     if (!(lpDrv->dwFlags & WINE_GDF_16BIT)) {
147         /* last of this driver in list ? */
148         if (DRIVER_GetNumberOfModuleRefs(lpDrv->d.d32.hModule, NULL) == 1) {
149             DRIVER_SendMessage(lpDrv, DRV_DISABLE, 0L, 0L);
150             DRIVER_SendMessage(lpDrv, DRV_FREE,    0L, 0L);
151         }
152     }
153
154     if (lpDrv->lpPrevItem)
155         lpDrv->lpPrevItem->lpNextItem = lpDrv->lpNextItem;
156     else
157         lpDrvItemList = lpDrv->lpNextItem;
158     if (lpDrv->lpNextItem)
159         lpDrv->lpNextItem->lpPrevItem = lpDrv->lpPrevItem;
160     /* trash magic number */
161     lpDrv->dwMagic ^= 0xa5a5a5a5;
162
163     return TRUE;
164 }
165
166 /**************************************************************************
167  *                              DRIVER_AddToList                [internal]
168  *
169  * Adds a driver struct to the list of open drivers.
170  * Generates all the logic to handle driver creation / open.
171  */
172 static  BOOL    DRIVER_AddToList(LPWINE_DRIVER lpNewDrv, LPARAM lParam1, LPARAM lParam2)
173 {
174     lpNewDrv->dwMagic = WINE_DI_MAGIC;
175     /* First driver to be loaded for this module, need to load correctly the module */
176     if (!(lpNewDrv->dwFlags & WINE_GDF_16BIT)) {
177         /* first of this driver in list ? */
178         if (DRIVER_GetNumberOfModuleRefs(lpNewDrv->d.d32.hModule, NULL) == 0) {
179             if (DRIVER_SendMessage(lpNewDrv, DRV_LOAD, 0L, 0L) != DRV_SUCCESS) {
180                 TRACE("DRV_LOAD failed on driver 0x%08lx\n", (DWORD)lpNewDrv);
181                 return FALSE;
182             }
183             /* returned value is not checked */
184             DRIVER_SendMessage(lpNewDrv, DRV_ENABLE, 0L, 0L);
185         }
186     }
187
188     lpNewDrv->lpNextItem = NULL;
189     if (lpDrvItemList == NULL) {
190         lpDrvItemList = lpNewDrv;
191         lpNewDrv->lpPrevItem = NULL;
192     } else {
193         LPWINE_DRIVER   lpDrv = lpDrvItemList;  /* find end of list */
194         while (lpDrv->lpNextItem != NULL)
195             lpDrv = lpDrv->lpNextItem;
196
197         lpDrv->lpNextItem = lpNewDrv;
198         lpNewDrv->lpPrevItem = lpDrv;
199     }
200
201     if (!(lpNewDrv->dwFlags & WINE_GDF_16BIT)) {
202         /* Now just open a new instance of a driver on this module */
203         lpNewDrv->d.d32.dwDriverID = DRIVER_SendMessage(lpNewDrv, DRV_OPEN, lParam1, lParam2);
204
205         if (lpNewDrv->d.d32.dwDriverID == 0) {
206             TRACE("DRV_OPEN failed on driver 0x%08lx\n", (DWORD)lpNewDrv);
207             DRIVER_RemoveFromList(lpNewDrv);
208             return FALSE;
209         }
210     }
211     return TRUE;
212 }
213
214 /**************************************************************************
215  *                              DRIVER_GetLibName               [internal]
216  *
217  */
218 BOOL    DRIVER_GetLibName(LPCWSTR keyName, LPCWSTR sectName, LPWSTR buf, int sz)
219 {
220     HKEY        hKey, hSecKey;
221     DWORD       bufLen, lRet;
222     static const WCHAR wszSystemIni[] = {'S','Y','S','T','E','M','.','I','N','I',0};
223     WCHAR       wsznull = '\0';
224
225     lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE, HKLM_BASE, 0, KEY_QUERY_VALUE, &hKey);
226     if (lRet == ERROR_SUCCESS) {
227         lRet = RegOpenKeyExW(hKey, sectName, 0, KEY_QUERY_VALUE, &hSecKey);
228         if (lRet == ERROR_SUCCESS) {
229             bufLen = sz;
230             lRet = RegQueryValueExW(hSecKey, keyName, 0, 0, (void*)buf, &bufLen);
231             RegCloseKey( hSecKey );
232         }
233         RegCloseKey( hKey );
234     }
235     if (lRet == ERROR_SUCCESS) return TRUE;
236     /* default to system.ini if we can't find it in the registry,
237      * to support native installations where system.ini is still used */
238     return GetPrivateProfileStringW(sectName, keyName, &wsznull, buf, sz / sizeof(WCHAR), wszSystemIni);
239 }
240
241 /**************************************************************************
242  *                              DRIVER_TryOpenDriver32          [internal]
243  *
244  * Tries to load a 32 bit driver whose DLL's (module) name is fn
245  */
246 LPWINE_DRIVER   DRIVER_TryOpenDriver32(LPCWSTR fn, LPARAM lParam2)
247 {
248     LPWINE_DRIVER       lpDrv = NULL;
249     HMODULE             hModule = 0;
250     LPWSTR              ptr;
251     LPCSTR              cause = 0;
252
253     TRACE("(%s, %08lX);\n", debugstr_w(fn), lParam2);
254
255     if ((ptr = strchrW(fn, ' ')) != NULL) {
256         *ptr++ = '\0';
257         while (*ptr == ' ') ptr++;
258         if (*ptr == '\0') ptr = NULL;
259     }
260
261     lpDrv = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_DRIVER));
262     if (lpDrv == NULL) {cause = "OOM"; goto exit;}
263
264     if ((hModule = LoadLibraryW(fn)) == 0) {cause = "Not a 32 bit lib"; goto exit;}
265
266     lpDrv->d.d32.lpDrvProc = (DRIVERPROC)GetProcAddress(hModule, "DriverProc");
267     if (lpDrv->d.d32.lpDrvProc == NULL) {cause = "no DriverProc"; goto exit;}
268
269     lpDrv->dwFlags          = 0;
270     lpDrv->d.d32.hModule    = hModule;
271     lpDrv->d.d32.dwDriverID = 0;
272
273     /* Win32 installable drivers must support a two phase opening scheme:
274      * + first open with NULL as lParam2 (session instance),
275      * + then do a second open with the real non null lParam2)
276      */
277     if (DRIVER_GetNumberOfModuleRefs(lpDrv->d.d32.hModule, NULL) == 0 && lParam2)
278     {
279         LPWINE_DRIVER   ret;
280
281         if (!DRIVER_AddToList(lpDrv, (LPARAM)ptr, 0L))
282         {
283             cause = "load0 failed";
284             goto exit;
285         }
286         ret = DRIVER_TryOpenDriver32(fn, lParam2);
287         if (!ret)
288         {
289             CloseDriver((HDRVR)lpDrv, 0L, 0L);
290             cause = "load1 failed";
291             goto exit;
292         }
293         return ret;
294     }
295
296     if (!DRIVER_AddToList(lpDrv, (LPARAM)ptr, lParam2))
297     {cause = "load failed"; goto exit;}
298
299     TRACE("=> %p\n", lpDrv);
300     return lpDrv;
301  exit:
302     FreeLibrary(hModule);
303     HeapFree(GetProcessHeap(), 0, lpDrv);
304     TRACE("Unable to load 32 bit module %s: %s\n", debugstr_w(fn), cause);
305     return NULL;
306 }
307
308 /**************************************************************************
309  *                              OpenDriverA                     [WINMM.@]
310  *                              DrvOpenA                        [WINMM.@]
311  * (0,1,DRV_LOAD  ,0       ,0)
312  * (0,1,DRV_ENABLE,0       ,0)
313  * (0,1,DRV_OPEN  ,buf[256],0)
314  */
315 HDRVR WINAPI OpenDriverA(LPCSTR lpDriverName, LPCSTR lpSectionName, LPARAM lParam)
316 {
317     INT                 len;
318     LPWSTR              dn = NULL;
319     LPWSTR              sn = NULL;
320     HDRVR               ret = 0;
321
322     if (lpDriverName)
323     {
324         len = MultiByteToWideChar( CP_ACP, 0, lpDriverName, -1, NULL, 0 );
325         dn = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
326         if (!dn) goto done;
327         MultiByteToWideChar( CP_ACP, 0, lpDriverName, -1, dn, len );
328     }
329
330     if (lpSectionName)
331     {
332         len = MultiByteToWideChar( CP_ACP, 0, lpSectionName, -1, NULL, 0 );
333         sn = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
334         if (!sn) goto done;
335         MultiByteToWideChar( CP_ACP, 0, lpSectionName, -1, sn, len );
336     }
337
338     ret = OpenDriver(dn, sn, lParam);
339
340 done:
341     HeapFree(GetProcessHeap(), 0, dn);
342     HeapFree(GetProcessHeap(), 0, sn);
343     return ret;
344 }
345
346 /**************************************************************************
347  *                              OpenDriver                      [WINMM.@]
348  *                              DrvOpen                         [WINMM.@]
349  */
350 HDRVR WINAPI OpenDriver(LPCWSTR lpDriverName, LPCWSTR lpSectionName, LPARAM lParam)
351 {
352     LPWINE_DRIVER       lpDrv = NULL;
353     WCHAR               libName[128];
354     LPCWSTR             lsn = lpSectionName;
355
356     TRACE("(%s, %s, 0x%08lx);\n", 
357           debugstr_w(lpDriverName), debugstr_w(lpSectionName), lParam);
358
359     if (lsn == NULL) {
360         static const WCHAR wszDrivers32[] = {'D','r','i','v','e','r','s','3','2',0};
361         lstrcpynW(libName, lpDriverName, sizeof(libName) / sizeof(WCHAR));
362
363         if ((lpDrv = DRIVER_TryOpenDriver32(libName, lParam)))
364             goto the_end;
365         lsn = wszDrivers32;
366     }
367     if (DRIVER_GetLibName(lpDriverName, lsn, libName, sizeof(libName)) &&
368         (lpDrv = DRIVER_TryOpenDriver32(libName, lParam)))
369         goto the_end;
370
371     /* now we will try a 16 bit driver (and add all the glue to make it work... which
372      * is located in our mmsystem implementation)
373      * so ensure, we can load our mmsystem, otherwise just fail
374      */
375     WINMM_CheckForMMSystem();
376     if (pFnOpenDriver16 &&
377         (lpDrv = pFnOpenDriver16(lpDriverName, lpSectionName, lParam)))
378     {
379         if (DRIVER_AddToList(lpDrv, 0, lParam)) goto the_end;
380         HeapFree(GetProcessHeap(), 0, lpDrv);
381     }
382     TRACE("Failed to open driver %s from system.ini file, section %s\n", 
383           debugstr_w(lpDriverName), debugstr_w(lpSectionName));
384     return 0;
385
386  the_end:
387     if (lpDrv)  TRACE("=> %08lx\n", (DWORD)lpDrv);
388     return (HDRVR)lpDrv;
389 }
390
391 /**************************************************************************
392  *                      CloseDriver                             [WINMM.@]
393  *                      DrvClose                                [WINMM.@]
394  */
395 LRESULT WINAPI CloseDriver(HDRVR hDrvr, LPARAM lParam1, LPARAM lParam2)
396 {
397     LPWINE_DRIVER       lpDrv;
398
399     TRACE("(%p, %08lX, %08lX);\n", hDrvr, lParam1, lParam2);
400
401     if ((lpDrv = DRIVER_FindFromHDrvr(hDrvr)) != NULL)
402     {
403         if (lpDrv->dwFlags & WINE_GDF_16BIT)
404         {
405             if (pFnCloseDriver16)
406                 pFnCloseDriver16(lpDrv->d.d16.hDriver16, lParam1, lParam2);
407         }
408         else
409         {
410             DRIVER_SendMessage(lpDrv, DRV_CLOSE, lParam1, lParam2);
411             lpDrv->d.d32.dwDriverID = 0;
412         }
413         if (DRIVER_RemoveFromList(lpDrv)) {
414             if (!(lpDrv->dwFlags & WINE_GDF_16BIT))
415             {
416                 LPWINE_DRIVER       lpDrv0;
417
418                 /* if driver has an opened session instance, we have to close it too */
419                 if (DRIVER_GetNumberOfModuleRefs(lpDrv->d.d32.hModule, &lpDrv0) == 1)
420                 {
421                     DRIVER_SendMessage(lpDrv0, DRV_CLOSE, 0L, 0L);
422                     lpDrv0->d.d32.dwDriverID = 0;
423                     DRIVER_RemoveFromList(lpDrv0);
424                     FreeLibrary(lpDrv0->d.d32.hModule);
425                     HeapFree(GetProcessHeap(), 0, lpDrv0);
426                 }
427                 FreeLibrary(lpDrv->d.d32.hModule);
428             }
429             HeapFree(GetProcessHeap(), 0, lpDrv);
430             return TRUE;
431         }
432     }
433     WARN("Failed to close driver\n");
434     return FALSE;
435 }
436
437 /**************************************************************************
438  *                              GetDriverFlags          [WINMM.@]
439  * [in] hDrvr handle to the driver
440  *
441  * Returns:
442  *      0x00000000 if hDrvr is an invalid handle
443  *      0x80000000 if hDrvr is a valid 32 bit driver
444  *      0x90000000 if hDrvr is a valid 16 bit driver
445  *
446  * native WINMM doesn't return those flags
447  *      0x80000000 for a valid 32 bit driver and that's it
448  *      (I may have mixed up the two flags :-(
449  */
450 DWORD   WINAPI GetDriverFlags(HDRVR hDrvr)
451 {
452     LPWINE_DRIVER       lpDrv;
453     DWORD               ret = 0;
454
455     TRACE("(%p)\n", hDrvr);
456
457     if ((lpDrv = DRIVER_FindFromHDrvr(hDrvr)) != NULL) {
458         ret = WINE_GDF_EXIST | lpDrv->dwFlags;
459     }
460     return ret;
461 }
462
463 /**************************************************************************
464  *                              GetDriverModuleHandle   [WINMM.@]
465  *                              DrvGetModuleHandle      [WINMM.@]
466  */
467 HMODULE WINAPI GetDriverModuleHandle(HDRVR hDrvr)
468 {
469     LPWINE_DRIVER       lpDrv;
470     HMODULE             hModule = 0;
471
472     TRACE("(%p);\n", hDrvr);
473
474     if ((lpDrv = DRIVER_FindFromHDrvr(hDrvr)) != NULL) {
475         if (!(lpDrv->dwFlags & WINE_GDF_16BIT))
476             hModule = lpDrv->d.d32.hModule;
477     }
478     TRACE("=> %p\n", hModule);
479     return hModule;
480 }
481
482 /**************************************************************************
483  *                              DefDriverProc                     [WINMM.@]
484  *                              DrvDefDriverProc                  [WINMM.@]
485  */
486 LRESULT WINAPI DefDriverProc(DWORD_PTR dwDriverIdentifier, HDRVR hDrv,
487                              UINT Msg, LPARAM lParam1, LPARAM lParam2)
488 {
489     switch (Msg) {
490     case DRV_LOAD:
491     case DRV_FREE:
492     case DRV_ENABLE:
493     case DRV_DISABLE:
494         return 1;
495     case DRV_INSTALL:
496     case DRV_REMOVE:
497         return DRV_SUCCESS;
498     default:
499         return 0;
500     }
501 }
502
503 /**************************************************************************
504  *                              DriverCallback                  [WINMM.@]
505  */
506 BOOL WINAPI DriverCallback(DWORD dwCallBack, UINT uFlags, HDRVR hDev,
507                            UINT wMsg, DWORD dwUser, DWORD dwParam1,
508                            DWORD dwParam2)
509 {
510     TRACE("(%08lX, %04X, %p, %04X, %08lX, %08lX, %08lX); !\n",
511           dwCallBack, uFlags, hDev, wMsg, dwUser, dwParam1, dwParam2);
512
513     switch (uFlags & DCB_TYPEMASK) {
514     case DCB_NULL:
515         TRACE("Null !\n");
516         if (dwCallBack)
517             WARN("uFlags=%04X has null DCB value, but dwCallBack=%08lX is not null !\n", uFlags, dwCallBack);
518         break;
519     case DCB_WINDOW:
520         TRACE("Window(%04lX) handle=%p!\n", dwCallBack, hDev);
521         PostMessageA((HWND)dwCallBack, wMsg, (WPARAM)hDev, dwParam1);
522         break;
523     case DCB_TASK: /* aka DCB_THREAD */
524         TRACE("Task(%04lx) !\n", dwCallBack);
525         PostThreadMessageA(dwCallBack, wMsg, (WPARAM)hDev, dwParam1);
526         break;
527     case DCB_FUNCTION:
528         TRACE("Function (32 bit) !\n");
529         ((LPDRVCALLBACK)dwCallBack)(hDev, wMsg, dwUser, dwParam1, dwParam2);
530         break;
531     case DCB_EVENT:
532         TRACE("Event(%08lx) !\n", dwCallBack);
533         SetEvent((HANDLE)dwCallBack);
534         break;
535     case 6: /* I would dub it DCB_MMTHREADSIGNAL */
536         /* this is an undocumented DCB_ value used for mmThreads
537          * loword of dwCallBack contains the handle of the lpMMThd block
538          * which dwSignalCount has to be incremented
539          */     
540         if (pFnGetMMThread16)
541         {
542             WINE_MMTHREAD*      lpMMThd = pFnGetMMThread16(LOWORD(dwCallBack));
543
544             TRACE("mmThread (%04x, %p) !\n", LOWORD(dwCallBack), lpMMThd);
545             /* same as mmThreadSignal16 */
546             InterlockedIncrement(&lpMMThd->dwSignalCount);
547             SetEvent(lpMMThd->hEvent);
548             /* some other stuff on lpMMThd->hVxD */
549         }
550         break;
551 #if 0
552     case 4:
553         /* this is an undocumented DCB_ value for... I don't know */
554         break;
555 #endif
556     default:
557         WARN("Unknown callback type %d\n", uFlags & DCB_TYPEMASK);
558         return FALSE;
559     }
560     TRACE("Done\n");
561     return TRUE;
562 }
563
564 /******************************************************************
565  *              DRIVER_UnloadAll
566  *
567  *
568  */
569 void    DRIVER_UnloadAll(void)
570 {
571     LPWINE_DRIVER       lpDrv;
572     LPWINE_DRIVER       lpNextDrv = NULL;
573     unsigned            count = 0;
574
575     for (lpDrv = lpDrvItemList; lpDrv != NULL; lpDrv = lpNextDrv)
576     {
577         lpNextDrv = lpDrv->lpNextItem;
578         CloseDriver((HDRVR)lpDrv, 0, 0);
579         count++;
580     }
581     TRACE("Unloaded %u drivers\n", count);
582 }