Moved 16-bit builtin module handling to dlls/kernel/ne_module.c.
[wine] / loader / loadorder.c
1 /*
2  * Module/Library loadorder
3  *
4  * Copyright 1999 Bertho Stultiens
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <assert.h>
27
28 #include "windef.h"
29 #include "winerror.h"
30 #include "winternl.h"
31 #include "file.h"
32 #include "module.h"
33
34 #include "wine/debug.h"
35 #include "wine/unicode.h"
36
37 WINE_DEFAULT_DEBUG_CHANNEL(module);
38
39 #define LOADORDER_ALLOC_CLUSTER 32      /* Allocate with 32 entries at a time */
40
41 typedef struct module_loadorder
42 {
43     const char         *modulename;
44     enum loadorder_type loadorder[LOADORDER_NTYPES];
45 } module_loadorder_t;
46
47 struct loadorder_list
48 {
49     int                 count;
50     int                 alloc;
51     module_loadorder_t *order;
52 };
53
54 /* default load-order if nothing specified */
55 /* the list must remain sorted by dll name */
56 static module_loadorder_t default_order_list[] =
57 {
58     { "display",      { LOADORDER_BI, 0, 0 } },
59     { "gdi.exe",      { LOADORDER_BI, 0, 0 } },
60     { "gdi32",        { LOADORDER_BI, 0, 0 } },
61     { "icmp",         { LOADORDER_BI, 0, 0 } },
62     { "kernel",       { LOADORDER_BI, 0, 0 } },
63     { "kernel32",     { LOADORDER_BI, 0, 0 } },
64     { "keyboard",     { LOADORDER_BI, 0, 0 } },
65     { "krnl386.exe",  { LOADORDER_BI, 0, 0 } },
66     { "mmsystem",     { LOADORDER_BI, 0, 0 } },
67     { "mouse",        { LOADORDER_BI, 0, 0 } },
68     { "ntdll",        { LOADORDER_BI, 0, 0 } },
69     { "odbc32",       { LOADORDER_BI, 0, 0 } },
70     { "system.drv",   { LOADORDER_BI, 0, 0 } },
71     { "toolhelp",     { LOADORDER_BI, 0, 0 } },
72     { "ttydrv",       { LOADORDER_BI, 0, 0 } },
73     { "user.exe",     { LOADORDER_BI, 0, 0 } },
74     { "user32",       { LOADORDER_BI, 0, 0 } },
75     { "w32skrnl",     { LOADORDER_BI, 0, 0 } },
76     { "winaspi",      { LOADORDER_BI, 0, 0 } },
77     { "winedos",      { LOADORDER_BI, 0, 0 } },
78     { "wineps",       { LOADORDER_BI, 0, 0 } },
79     { "wing",         { LOADORDER_BI, 0, 0 } },
80     { "winmm",        { LOADORDER_BI, 0, 0 } },
81     { "winsock",      { LOADORDER_BI, 0, 0 } },
82     { "wnaspi32",     { LOADORDER_BI, 0, 0 } },
83     { "wow32",        { LOADORDER_BI, 0, 0 } },
84     { "wprocs",       { LOADORDER_BI, 0, 0 } },
85     { "ws2_32",       { LOADORDER_BI, 0, 0 } },
86     { "wsock32",      { LOADORDER_BI, 0, 0 } },
87     { "x11drv",       { LOADORDER_BI, 0, 0 } }
88 };
89
90 static const struct loadorder_list default_list =
91 {
92     sizeof(default_order_list)/sizeof(default_order_list[0]),
93     sizeof(default_order_list)/sizeof(default_order_list[0]),
94     default_order_list
95 };
96
97 /* default if nothing else specified */
98 static const enum loadorder_type default_loadorder[LOADORDER_NTYPES] =
99 {
100     LOADORDER_BI, LOADORDER_DLL, 0
101 };
102
103 /* default for modules with an explicit path */
104 static const enum loadorder_type default_path_loadorder[LOADORDER_NTYPES] =
105 {
106     LOADORDER_DLL, LOADORDER_BI, 0
107 };
108
109 static struct loadorder_list cmdline_list;
110
111
112 /***************************************************************************
113  *      cmp_sort_func   (internal, static)
114  *
115  * Sorting and comparing function used in sort and search of loadorder
116  * entries.
117  */
118 static int cmp_sort_func(const void *s1, const void *s2)
119 {
120     return FILE_strcasecmp(((module_loadorder_t *)s1)->modulename,
121                            ((module_loadorder_t *)s2)->modulename);
122 }
123
124
125 /***************************************************************************
126  *      get_tok (internal, static)
127  *
128  * strtok wrapper for non-destructive buffer writing.
129  * NOTE: strtok is not reentrant and therefore this code is neither.
130  */
131 static char *get_tok(const char *str, const char *delim)
132 {
133         static char *buf = NULL;
134         char *cptr;
135
136         if(!str && !buf)
137                 return NULL;
138
139         if(str && buf)
140         {
141                 HeapFree(GetProcessHeap(), 0, buf);
142                 buf = NULL;
143         }
144
145         if(str && !buf)
146         {
147                 buf = HeapAlloc(GetProcessHeap(), 0, strlen(str)+1);
148                 strcpy( buf, str );
149                 cptr = strtok(buf, delim);
150         }
151         else
152         {
153                 cptr = strtok(NULL, delim);
154         }
155
156         if(!cptr)
157         {
158                 HeapFree(GetProcessHeap(), 0, buf);
159                 buf = NULL;
160         }
161         return cptr;
162 }
163
164
165 /***************************************************************************
166  *      get_basename
167  *
168  * Return the base name of a file name (i.e. remove the path components).
169  */
170 static const char *get_basename( const char *name )
171 {
172     const char *ptr;
173
174     if (name[0] && name[1] == ':') name += 2;  /* strip drive specification */
175     if ((ptr = strrchr( name, '\\' ))) name = ptr + 1;
176     if ((ptr = strrchr( name, '/' ))) name = ptr + 1;
177     return name;
178 }
179
180
181 /***************************************************************************
182  *      debugstr_loadorder
183  *
184  * Return a loadorder in printable form.
185  */
186 static const char *debugstr_loadorder( enum loadorder_type lo[] )
187 {
188     int i;
189     char buffer[LOADORDER_NTYPES*3+1];
190
191     buffer[0] = 0;
192     for(i = 0; i < LOADORDER_NTYPES; i++)
193     {
194         if (lo[i] == LOADORDER_INVALID) break;
195         switch(lo[i])
196         {
197         case LOADORDER_DLL: strcat( buffer, "n," ); break;
198         case LOADORDER_BI:  strcat( buffer, "b," ); break;
199         default:            strcat( buffer, "?," ); break;
200         }
201     }
202     if (buffer[0]) buffer[strlen(buffer)-1] = 0;
203     return debugstr_a(buffer);
204 }
205
206
207 /***************************************************************************
208  *      ParseLoadOrder  (internal, static)
209  *
210  * Parses the loadorder options from the configuration and puts it into
211  * a structure.
212  */
213 static BOOL ParseLoadOrder(char *order, enum loadorder_type lo[])
214 {
215     static int warn;
216         char *cptr;
217         int n = 0;
218
219         cptr = get_tok(order, ", \t");
220         while(cptr)
221         {
222             enum loadorder_type type = LOADORDER_INVALID;
223
224                 if(n >= LOADORDER_NTYPES-1)
225                 {
226                         ERR("More than existing %d module-types specified, rest ignored\n", LOADORDER_NTYPES-1);
227                         break;
228                 }
229
230                 switch(*cptr)
231                 {
232                 case 'N':       /* Native */
233                 case 'n': type = LOADORDER_DLL; break;
234
235                 case 'S':       /* So */
236                 case 's':
237                     if (!warn++) MESSAGE("Load order 'so' no longer supported, ignored\n");
238                     break;
239
240                 case 'B':       /* Builtin */
241                 case 'b': type = LOADORDER_BI; break;
242
243                 default:
244                         ERR("Invalid load order module-type '%s', ignored\n", cptr);
245                 }
246
247                 if(type != LOADORDER_INVALID) lo[n++] = type;
248                 cptr = get_tok(NULL, ", \t");
249         }
250         lo[n] = LOADORDER_INVALID;
251         return TRUE;
252 }
253
254
255 /***************************************************************************
256  *      AddLoadOrder    (internal, static)
257  *
258  * Adds an entry in the list of command-line overrides.
259  */
260 static BOOL AddLoadOrder(module_loadorder_t *plo)
261 {
262         int i;
263
264         /* TRACE(module, "'%s' -> %08lx\n", plo->modulename, *(DWORD *)(plo->loadorder)); */
265
266         for(i = 0; i < cmdline_list.count; i++)
267         {
268             if(!cmp_sort_func(plo, &cmdline_list.order[i] ))
269             {
270                 /* replace existing option */
271                 memcpy( cmdline_list.order[i].loadorder, plo->loadorder, sizeof(plo->loadorder));
272                 return TRUE;
273             }
274         }
275
276         if (i >= cmdline_list.alloc)
277         {
278                 /* No space in current array, make it larger */
279                 cmdline_list.alloc += LOADORDER_ALLOC_CLUSTER;
280                 cmdline_list.order = HeapReAlloc(GetProcessHeap(), 0, cmdline_list.order,
281                                           cmdline_list.alloc * sizeof(module_loadorder_t));
282                 if(!cmdline_list.order)
283                 {
284                         MESSAGE("Virtual memory exhausted\n");
285                         exit(1);
286                 }
287         }
288         memcpy(cmdline_list.order[i].loadorder, plo->loadorder, sizeof(plo->loadorder));
289         cmdline_list.order[i].modulename = HeapAlloc(GetProcessHeap(), 0, strlen(plo->modulename)+1);
290         strcpy( (char *)cmdline_list.order[i].modulename, plo->modulename );
291         cmdline_list.count++;
292         return TRUE;
293 }
294
295
296 /***************************************************************************
297  *      AddLoadOrderSet (internal, static)
298  *
299  * Adds a set of entries in the list of command-line overrides from the key parameter.
300  */
301 static BOOL AddLoadOrderSet(char *key, char *order)
302 {
303         module_loadorder_t ldo;
304         char *cptr;
305
306         /* Parse the loadorder before the rest because strtok is not reentrant */
307         if(!ParseLoadOrder(order, ldo.loadorder))
308                 return FALSE;
309
310         cptr = get_tok(key, ", \t");
311         while(cptr)
312         {
313                 char *ext = strrchr(cptr, '.');
314                 if(ext && !FILE_strcasecmp( ext, ".dll" )) *ext = 0;
315                 ldo.modulename = cptr;
316                 if(!AddLoadOrder(&ldo)) return FALSE;
317                 cptr = get_tok(NULL, ", \t");
318         }
319         return TRUE;
320 }
321
322
323 /***************************************************************************
324  *      MODULE_AddLoadOrderOption
325  *
326  * The commandline option is in the form:
327  * name[,name,...]=native[,b,...]
328  */
329 void MODULE_AddLoadOrderOption( const char *option )
330 {
331     char *value, *key = HeapAlloc(GetProcessHeap(), 0, strlen(option)+1);
332
333     strcpy( key, option );
334     if (!(value = strchr(key, '='))) goto error;
335     *value++ = '\0';
336
337     TRACE("Commandline override '%s' = '%s'\n", key, value);
338
339     if (!AddLoadOrderSet(key, value)) goto error;
340     HeapFree(GetProcessHeap(), 0, key);
341
342     /* sort the array for quick lookup */
343     qsort(cmdline_list.order, cmdline_list.count, sizeof(cmdline_list.order[0]), cmp_sort_func);
344     return;
345
346  error:
347     MESSAGE( "Syntax: -dll name[,name[,...]]={native|so|builtin}[,{n|s|b}[,...]]\n"
348              "    - 'name' is the name of any dll without extension\n"
349              "    - the order of loading (native, so and builtin) can be abbreviated\n"
350              "      with the first letter\n"
351              "    - the option can be specified multiple times\n"
352              "    Example:\n"
353              "    -dll comdlg32,commdlg=n -dll shell,shell32=b\n" );
354     ExitProcess(1);
355 }
356
357
358 /***************************************************************************
359  *      get_list_load_order
360  *
361  * Get the load order for a given module from the command-line or
362  * default lists.
363  */
364 static BOOL get_list_load_order( const char *module, const struct loadorder_list *list,
365                                  enum loadorder_type lo[] )
366 {
367     module_loadorder_t tmp, *res = NULL;
368
369     tmp.modulename = module;
370     /* some bsearch implementations (Solaris) are buggy when the number of items is 0 */
371     if (list->count && (res = bsearch(&tmp, list->order, list->count, sizeof(list->order[0]), cmp_sort_func)))
372         memcpy( lo, res->loadorder, sizeof(res->loadorder) );
373     return (res != NULL);
374 }
375
376
377 /***************************************************************************
378  *      open_app_key
379  *
380  * Open the registry key to the app-specific DllOverrides list.
381  */
382 static HKEY open_app_key( const char *module )
383 {
384     OBJECT_ATTRIBUTES attr;
385     UNICODE_STRING nameW;
386     HKEY hkey, appkey;
387     char buffer[MAX_PATH+16], *appname;
388     static const WCHAR AppDefaultsW[] = {'M','a','c','h','i','n','e','\\',
389                                          'S','o','f','t','w','a','r','e','\\',
390                                          'W','i','n','e','\\',
391                                          'W','i','n','e','\\',
392                                          'C','o','n','f','i','g','\\',
393                                          'A','p','p','D','e','f','a','u','l','t','s',0};
394
395     if (!GetModuleFileNameA( 0, buffer, MAX_PATH ))
396     {
397         WARN( "could not get module file name loading %s\n", module );
398         return 0;
399     }
400     appname = (char *)get_basename( buffer );
401
402     TRACE( "searching '%s' in AppDefaults\\%s\\DllOverrides\n", module, appname );
403
404     attr.Length = sizeof(attr);
405     attr.RootDirectory = 0;
406     attr.ObjectName = &nameW;
407     attr.Attributes = 0;
408     attr.SecurityDescriptor = NULL;
409     attr.SecurityQualityOfService = NULL;
410     RtlInitUnicodeString( &nameW, AppDefaultsW );
411
412     if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0;
413     attr.RootDirectory = hkey;
414
415     /* open AppDefaults\\appname\\DllOverrides key */
416     strcat( appname, "\\DllOverrides" );
417     RtlCreateUnicodeStringFromAsciiz( &nameW, appname );
418     if (NtOpenKey( &appkey, KEY_ALL_ACCESS, &attr )) appkey = 0;
419     RtlFreeUnicodeString( &nameW );
420     NtClose( hkey );
421     return appkey;
422 }
423
424
425 /***************************************************************************
426  *      get_registry_value
427  *
428  * Load the registry loadorder value for a given module.
429  */
430 static BOOL get_registry_value( HKEY hkey, const char *module, enum loadorder_type lo[] )
431 {
432     UNICODE_STRING valueW;
433     char buffer[80];
434     DWORD count;
435     BOOL ret;
436
437     RtlCreateUnicodeStringFromAsciiz( &valueW, module );
438
439     if ((ret = !NtQueryValueKey( hkey, &valueW, KeyValuePartialInformation,
440                                  buffer, sizeof(buffer), &count )))
441     {
442         int i, n = 0;
443         WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)buffer)->Data;
444
445         while (*str)
446         {
447             enum loadorder_type type = LOADORDER_INVALID;
448
449             while (*str == ',' || isspaceW(*str)) str++;
450             if (!*str) break;
451
452             switch(tolowerW(*str))
453             {
454             case 'n': type = LOADORDER_DLL; break;
455             case 'b': type = LOADORDER_BI; break;
456             case 's': break;  /* no longer supported, ignore */
457             case 0:   break;  /* end of string */
458             default:
459                 ERR("Invalid load order module-type %s, ignored\n", debugstr_w(str));
460                 break;
461             }
462             if (type != LOADORDER_INVALID)
463             {
464                 for (i = 0; i < n; i++) if (lo[i] == type) break;  /* already specified */
465                 if (i == n) lo[n++] = type;
466             }
467             while (*str && *str != ',' && !isspaceW(*str)) str++;
468         }
469         lo[n] = LOADORDER_INVALID;
470     }
471     RtlFreeUnicodeString( &valueW );
472     return ret;
473 }
474
475
476 /***************************************************************************
477  *      MODULE_GetBuiltinPath
478  *
479  * Get the path of a builtin module when the native file does not exist.
480  */
481 BOOL MODULE_GetBuiltinPath( const char *libname, const char *ext, char *filename, UINT size )
482 {
483     char *p;
484     BOOL ret = FALSE;
485     UINT len = GetSystemDirectoryA( filename, size );
486
487     if (FILE_contains_path( libname ))
488     {
489         char *tmp;
490
491         /* if the library name contains a path and can not be found,
492          * return an error.
493          * exception: if the path is the system directory, proceed,
494          * so that modules which are not PE modules can be loaded.
495          * If the library name does not contain a path and can not
496          * be found, assume the system directory is meant */
497
498         if (strlen(libname) >= size) return FALSE;  /* too long */
499         if (strchr( libname, '/' ))  /* need to convert slashes */
500         {
501             if (!(tmp = HeapAlloc( GetProcessHeap(), 0, strlen(libname)+1 ))) return FALSE;
502             strcpy( tmp, libname );
503             for (p = tmp; *p; p++) if (*p == '/') *p = '\\';
504         }
505         else tmp = (char *)libname;
506
507         if (!FILE_strncasecmp( filename, tmp, len ) && tmp[len] == '\\')
508         {
509             strcpy( filename, tmp );
510             ret = TRUE;
511         }
512         if (tmp != libname) HeapFree( GetProcessHeap(), 0, tmp );
513         if (!ret) return FALSE;
514     }
515     else
516     {
517         if (strlen(libname) >= size - len - 1) return FALSE;
518         filename[len] = '\\';
519         strcpy( filename+len+1, libname );
520     }
521
522     /* if the filename doesn't have an extension, append the default */
523     if (!(p = strrchr( filename, '.')) || strchr( p, '/' ) || strchr( p, '\\'))
524     {
525         if (strlen(filename) + strlen(ext) >= size) return FALSE;
526         strcat( filename, ext );
527     }
528     return TRUE;
529 }
530
531
532 /***************************************************************************
533  *      MODULE_GetLoadOrder     (internal)
534  *
535  * Locate the loadorder of a module.
536  * Any path is stripped from the path-argument and so are the extension
537  * '.dll' and '.exe'. A lookup in the table can yield an override for
538  * the specific dll. Otherwise the default load order is returned.
539  */
540 void MODULE_GetLoadOrder( enum loadorder_type loadorder[], const char *path, BOOL win32 )
541 {
542     static const WCHAR DllOverridesW[] = {'M','a','c','h','i','n','e','\\',
543                                           'S','o','f','t','w','a','r','e','\\',
544                                           'W','i','n','e','\\',
545                                           'W','i','n','e','\\',
546                                           'C','o','n','f','i','g','\\',
547                                           'D','l','l','O','v','e','r','r','i','d','e','s',0};
548
549     static HKEY std_key = (HKEY)-1;  /* key to standard section, cached */
550     HKEY app_key = 0;
551     char *module, *basename;
552     int len;
553
554     TRACE("looking for %s\n", path);
555
556     loadorder[0] = LOADORDER_INVALID;  /* in case something bad happens below */
557
558     /* Strip path information if the module resides in the system directory
559      * (path is already stripped by caller for 16-bit modules)
560      */
561     if (win32)
562     {
563         char sysdir[MAX_PATH+1];
564         if (!GetSystemDirectoryA( sysdir, MAX_PATH )) return;
565         if (!FILE_strncasecmp( sysdir, path, strlen (sysdir) ))
566         {
567             path += strlen(sysdir);
568             while (*path == '\\' || *path == '/') path++;
569         }
570     }
571
572     if (!(len = strlen(path))) return;
573     if (!(module = HeapAlloc( GetProcessHeap(), 0, len + 2 ))) return;
574     strcpy( module+1, path );  /* reserve module[0] for the wildcard char */
575
576     if (len >= 4)
577     {
578         char *ext = module + 1 + len - 4;
579         if (!FILE_strcasecmp( ext, ".dll" )) *ext = 0;
580     }
581
582     /* check command-line first */
583     if (get_list_load_order( module+1, &cmdline_list, loadorder ))
584     {
585         TRACE( "got cmdline %s for %s\n",
586                debugstr_loadorder(loadorder), debugstr_a(path) );
587         goto done;
588     }
589
590     /* then explicit module name in AppDefaults */
591     app_key = open_app_key( module+1 );
592     if (app_key && get_registry_value( app_key, module+1, loadorder ))
593     {
594         TRACE( "got app defaults %s for %s\n",
595                debugstr_loadorder(loadorder), debugstr_a(path) );
596         goto done;
597     }
598
599     /* then explicit module name in standard section */
600     if (std_key == (HKEY)-1)
601     {
602         OBJECT_ATTRIBUTES attr;
603         UNICODE_STRING nameW;
604
605         attr.Length = sizeof(attr);
606         attr.RootDirectory = 0;
607         attr.ObjectName = &nameW;
608         attr.Attributes = 0;
609         attr.SecurityDescriptor = NULL;
610         attr.SecurityQualityOfService = NULL;
611         RtlInitUnicodeString( &nameW, DllOverridesW );
612
613         if (NtOpenKey( &std_key, KEY_ALL_ACCESS, &attr )) std_key = 0;
614     }
615
616     if (std_key && get_registry_value( std_key, module+1, loadorder ))
617     {
618         TRACE( "got standard entry %s for %s\n",
619                debugstr_loadorder(loadorder), debugstr_a(path) );
620         goto done;
621     }
622
623     /* then module basename preceded by '*' in AppDefaults */
624     basename = (char *)get_basename( module+1 );
625     basename[-1] = '*';
626     if (app_key && get_registry_value( app_key, basename-1, loadorder ))
627     {
628         TRACE( "got app defaults basename %s for %s\n",
629                debugstr_loadorder(loadorder), debugstr_a(path) );
630         goto done;
631     }
632
633     /* then module name preceded by '*' in standard section */
634     if (std_key && get_registry_value( std_key, basename-1, loadorder ))
635     {
636         TRACE( "got standard base name %s for %s\n",
637                debugstr_loadorder(loadorder), debugstr_a(path) );
638         goto done;
639     }
640
641     /* then base name matching compiled-in defaults */
642     if (get_list_load_order( basename, &default_list, loadorder ))
643     {
644         TRACE( "got compiled-in default %s for %s\n",
645                debugstr_loadorder(loadorder), debugstr_a(path) );
646         goto done;
647     }
648
649     if (basename == module+1)
650     {
651         /* then wildcard entry in AppDefaults (only if no explicit path) */
652         if (app_key && get_registry_value( app_key, "*", loadorder ))
653         {
654             TRACE( "got app defaults wildcard %s for %s\n",
655                    debugstr_loadorder(loadorder), debugstr_a(path) );
656             goto done;
657         }
658
659         /* then wildcard entry in standard section (only if no explicit path) */
660         if (std_key && get_registry_value( std_key, "*", loadorder ))
661         {
662             TRACE( "got standard wildcard %s for %s\n",
663                    debugstr_loadorder(loadorder), debugstr_a(path) );
664             goto done;
665         }
666
667         /* and last the hard-coded default */
668         memcpy( loadorder, default_loadorder, sizeof(default_loadorder) );
669         TRACE( "got hardcoded default %s for %s\n",
670                debugstr_loadorder(loadorder), debugstr_a(path) );
671     }
672     else  /* module contains an explicit path */
673     {
674         memcpy( loadorder, default_path_loadorder, sizeof(default_path_loadorder) );
675         TRACE( "got hardcoded path default %s for %s\n",
676                debugstr_loadorder(loadorder), debugstr_a(path) );
677     }
678
679  done:
680     if (app_key) NtClose( app_key );
681     HeapFree( GetProcessHeap(), 0, module );
682 }