1 /**
2  * This module provides OS specific helper function for DLL support
3  *
4  * Copyright: Copyright Digital Mars 2010 - 2012.
5  * License: Distributed under the
6  *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
7  *    (See accompanying file LICENSE)
8  * Authors:   Rainer Schuetze
9  * Source: $(DRUNTIMESRC core/sys/windows/_dll.d)
10  */
11 
12 module core.sys.windows.dll;
13 version (Windows):
14 
15 import core.sys.windows.winbase;
16 import core.sys.windows.winnt;
17 import core.stdc.string;
18 import core.runtime;
19 
20 public import core.sys.windows.threadaux;
21 
22 ///////////////////////////////////////////////////////////////////
23 // support fixing implicit TLS for dynamically loaded DLLs on Windows XP
24 
25 // in this special case, we have to treat _tlsstart and _tlsend as non-TLS variables
26 //  as they are used to simulate TLS when it is not set up under XP. In this case we must
27 //  not access tls_array[tls_index] as needed for thread local _tlsstart and _tlsend
28 extern (C)
29 {
30     version (Win32)
31     {
32         version (CRuntime_DigitalMars)
33         {
34             extern __gshared byte  _tlsstart;
35             extern __gshared byte  _tlsend;
36             extern __gshared void* _tls_callbacks_a;
37         }
38         else version (CRuntime_Microsoft)
39         {
40             extern __gshared byte  _tls_start;
41             extern __gshared byte  _tls_end;
42             extern __gshared void*  __xl_a;
43             alias _tls_start _tlsstart;
44             alias _tls_end   _tlsend;
45             alias __xl_a     _tls_callbacks_a;
46         }
47         extern __gshared int   _tls_index;
48     }
49 }
50 
51 extern (C) // rt.minfo
52 {
53     void rt_moduleTlsCtor();
54     void rt_moduleTlsDtor();
55 }
56 
57 private:
58 struct dll_aux
59 {
60     // don't let symbols leak into other modules
61 version (Win32)
62 {
63     struct LdrpTlsListEntry
64     {
65         LdrpTlsListEntry* next;
66         LdrpTlsListEntry* prev;
67         void* tlsstart;
68         void* tlsend;
69         void* ptr_tlsindex;
70         void* callbacks;
71         void* zerofill;
72         int   tlsindex;
73     }
74 
75     alias fnRtlAllocateHeap = extern(Windows)
76     void* function(void* HeapHandle, uint Flags, size_t Size) nothrow;
77 
78     // find a code sequence and return the address after the sequence
79     static void* findCodeSequence( void* adr, int len, ref ubyte[] pattern ) nothrow
80     {
81         if ( !adr )
82             return null;
83 
84         ubyte* code = cast(ubyte*) adr;
85         for ( int p = 0; p < len; p++ )
86         {
87             if ( code[ p .. p + pattern.length ] == pattern[ 0 .. $ ] )
88             {
89                 ubyte* padr = code + p + pattern.length;
90                 return padr;
91             }
92         }
93         return null;
94     }
95 
96     // find a code sequence and return the (relative) address that follows
97     static void* findCodeReference( void* adr, int len, ref ubyte[] pattern, bool relative ) nothrow
98     {
99         if ( !adr )
100             return null;
101 
102         ubyte* padr = cast(ubyte*) findCodeSequence( adr, len, pattern );
103         if ( padr )
104         {
105             if ( relative )
106                 return padr + 4 + *cast(int*) padr;
107             return *cast(void**) padr;
108         }
109         return null;
110     }
111 
112     // crawl through ntdll to find function _LdrpAllocateTls@0 and references
113     //  to _LdrpNumberOfTlsEntries, _NtdllBaseTag and _LdrpTlsList
114     // LdrInitializeThunk
115     // -> _LdrpInitialize@12
116     // -> _LdrpInitializeThread@4
117     // -> _LdrpAllocateTls@0
118     // -> je chunk
119     //     _LdrpNumberOfTlsEntries - number of entries in TlsList
120     //     _NtdllBaseTag           - tag used for RtlAllocateHeap
121     //     _LdrpTlsList            - root of the double linked list with TlsList entries
122 
123     static __gshared int* pNtdllBaseTag; // remembered for reusage in addTlsData
124 
125     static __gshared ubyte[] jmp_LdrpInitialize = [ 0x33, 0xED, 0xE9 ]; // xor ebp,ebp; jmp _LdrpInitialize
126     static __gshared ubyte[] jmp__LdrpInitialize = [ 0x5D, 0xE9 ]; // pop ebp; jmp __LdrpInitialize
127     static __gshared ubyte[] jmp__LdrpInitialize_xp64 = [ 0x5D, 0x90, 0x90, 0x90, 0x90, 0x90 ]; // pop ebp; nop; nop; nop; nop; nop;
128     static __gshared ubyte[] call_LdrpInitializeThread = [ 0xFF, 0x75, 0x08, 0xE8 ]; // push [ebp+8]; call _LdrpInitializeThread
129     static __gshared ubyte[] call_LdrpAllocateTls = [ 0x00, 0x00, 0xE8 ]; // jne 0xc3; call _LdrpAllocateTls
130     static __gshared ubyte[] call_LdrpAllocateTls_svr03 = [ 0x65, 0xfc, 0x00, 0xE8 ]; // and [ebp+fc], 0; call _LdrpAllocateTls
131     static __gshared ubyte[] jne_LdrpAllocateTls = [ 0x0f, 0x85 ]; // jne body_LdrpAllocateTls
132     static __gshared ubyte[] mov_LdrpNumberOfTlsEntries = [ 0x8B, 0x0D ]; // mov ecx, _LdrpNumberOfTlsEntries
133     static __gshared ubyte[] mov_NtdllBaseTag = [ 0x51, 0x8B, 0x0D ]; // push ecx; mov ecx, _NtdllBaseTag
134     static __gshared ubyte[] mov_NtdllBaseTag_srv03 = [ 0x50, 0xA1 ]; // push eax; mov eax, _NtdllBaseTag
135     static __gshared ubyte[] mov_LdrpTlsList = [ 0x8B, 0x3D ]; // mov edi, _LdrpTlsList
136 
137     static LdrpTlsListEntry* addTlsListEntry( void** peb, void* tlsstart, void* tlsend, void* tls_callbacks_a, int* tlsindex ) nothrow
138     {
139         HANDLE hnd = GetModuleHandleA( "NTDLL" );
140         assert( hnd, "cannot get module handle for ntdll" );
141         ubyte* fn = cast(ubyte*) GetProcAddress( hnd, "LdrInitializeThunk" );
142         assert( fn, "cannot find LdrInitializeThunk in ntdll" );
143 
144         void* pLdrpInitialize = findCodeReference( fn, 20, jmp_LdrpInitialize, true );
145         void* p_LdrpInitialize = findCodeReference( pLdrpInitialize, 40, jmp__LdrpInitialize, true );
146         if ( !p_LdrpInitialize )
147             p_LdrpInitialize = findCodeSequence( pLdrpInitialize, 40, jmp__LdrpInitialize_xp64 );
148         void* pLdrpInitializeThread = findCodeReference( p_LdrpInitialize, 200, call_LdrpInitializeThread, true );
149         void* pLdrpAllocateTls = findCodeReference( pLdrpInitializeThread, 40, call_LdrpAllocateTls, true );
150         if (!pLdrpAllocateTls)
151             pLdrpAllocateTls = findCodeReference( pLdrpInitializeThread, 100, call_LdrpAllocateTls_svr03, true );
152         void* pBodyAllocateTls = findCodeReference( pLdrpAllocateTls, 40, jne_LdrpAllocateTls, true );
153 
154         int* pLdrpNumberOfTlsEntries = cast(int*) findCodeReference( pBodyAllocateTls, 60, mov_LdrpNumberOfTlsEntries, false );
155         pNtdllBaseTag = cast(int*) findCodeReference( pBodyAllocateTls, 30, mov_NtdllBaseTag, false );
156         if (!pNtdllBaseTag)
157             pNtdllBaseTag = cast(int*) findCodeReference( pBodyAllocateTls, 30, mov_NtdllBaseTag_srv03, false );
158         LdrpTlsListEntry* pLdrpTlsList = cast(LdrpTlsListEntry*)findCodeReference( pBodyAllocateTls, 80, mov_LdrpTlsList, false );
159 
160         if ( !pLdrpNumberOfTlsEntries || !pNtdllBaseTag || !pLdrpTlsList )
161             return null;
162 
163         fnRtlAllocateHeap fnAlloc = cast(fnRtlAllocateHeap) GetProcAddress( hnd, "RtlAllocateHeap" );
164         if ( !fnAlloc )
165             return null;
166 
167         // allocate new TlsList entry (adding 0xC0000 to the tag is obviously a flag also usesd by
168         //  the nt-loader, could be the result of HEAP_MAKE_TAG_FLAGS(0,HEAP_NO_SERIALIZE|HEAP_GROWABLE)
169         //  but this is not documented in the msdn entry for RtlAlloateHeap
170         void* heap = peb[6];
171         LdrpTlsListEntry* entry = cast(LdrpTlsListEntry*) (*fnAlloc)( heap, *pNtdllBaseTag | 0xc0000, LdrpTlsListEntry.sizeof );
172         if ( !entry )
173             return null;
174 
175         // fill entry
176         entry.tlsstart = tlsstart;
177         entry.tlsend = tlsend;
178         entry.ptr_tlsindex = tlsindex;
179         entry.callbacks = tls_callbacks_a;
180         entry.zerofill = null;
181         entry.tlsindex = *pLdrpNumberOfTlsEntries;
182 
183         // and add it to the end of TlsList
184         *tlsindex = *pLdrpNumberOfTlsEntries;
185         entry.next = pLdrpTlsList;
186         entry.prev = pLdrpTlsList.prev;
187         pLdrpTlsList.prev.next = entry;
188         pLdrpTlsList.prev = entry;
189         (*pLdrpNumberOfTlsEntries)++;
190 
191         return entry;
192     }
193 
194     // reallocate TLS array and create a copy of the TLS data section
195     static bool addTlsData( void** teb, void* tlsstart, void* tlsend, int tlsindex ) nothrow
196     {
197         HANDLE hnd = GetModuleHandleA( "NTDLL" );
198         assert( hnd, "cannot get module handle for ntdll" );
199 
200         fnRtlAllocateHeap fnAlloc = cast(fnRtlAllocateHeap) GetProcAddress( hnd, "RtlAllocateHeap" );
201         if ( !fnAlloc || !pNtdllBaseTag )
202             return false;
203 
204         void** peb = cast(void**) teb[12];
205         void* heap = peb[6];
206 
207         auto sz = tlsend - tlsstart;
208         void* tlsdata = cast(void*) (*fnAlloc)( heap, *pNtdllBaseTag | 0xc0000, sz );
209         if ( !tlsdata )
210             return false;
211 
212         // no relocations! not even self-relocations. Windows does not do them.
213         core.stdc..string.memcpy( tlsdata, tlsstart, sz );
214 
215         // create copy of tls pointer array
216         void** array = cast(void**) (*fnAlloc)( heap, *pNtdllBaseTag | 0xc0000, (tlsindex + 1) * (void*).sizeof );
217         if ( !array )
218             return false;
219 
220         if ( tlsindex > 0 && teb[11] )
221             core.stdc..string.memcpy( array, teb[11], tlsindex * (void*).sizeof);
222         array[tlsindex] = tlsdata;
223         teb[11] = cast(void*) array;
224 
225         // let the old array leak, in case a oncurrent thread is still relying on it
226         return true;
227     }
228 } // Win32
229 
230     alias bool BOOLEAN;
231 
232     struct UNICODE_STRING
233     {
234         short Length;
235         short MaximumLength;
236         wchar* Buffer;
237     }
238 
239     struct LIST_ENTRY
240     {
241         LIST_ENTRY* next;
242         LIST_ENTRY* prev;
243     }
244 
245     // the following structures can be found here:
246     // https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/ldr_data_table_entry.htm
247     // perhaps this should be same as LDR_DATA_TABLE_ENTRY, which is introduced with PEB_LDR_DATA
248     struct LDR_MODULE
249     {
250         LIST_ENTRY      InLoadOrderModuleList;
251         LIST_ENTRY      InMemoryOrderModuleList;
252         LIST_ENTRY      InInitializationOrderModuleList;
253         PVOID           BaseAddress;
254         PVOID           EntryPoint;
255         SIZE_T          SizeOfImage;
256         UNICODE_STRING  FullDllName;
257         UNICODE_STRING  BaseDllName;
258         ULONG           Flags;
259         SHORT           LoadCount; // obsolete after Version 6.1
260         SHORT           TlsIndex;
261         LIST_ENTRY      HashTableEntry;
262         ULONG           TimeDateStamp;
263         PVOID           EntryPointActivationContext;
264         PVOID           PatchInformation;
265         LDR_DDAG_NODE  *DdagNode; // starting with Version 6.2
266     }
267 
268     struct LDR_DDAG_NODE
269     {
270         LIST_ENTRY Modules;
271         void* ServiceTagList;  // LDR_SERVICE_TAG_RECORD
272         ULONG LoadCount;
273         ULONG ReferenceCount;  // Version 10: ULONG LoadWhileUnloadingCount;
274         ULONG DependencyCount; // Version 10: ULONG LowestLink;
275     }
276 
277     struct PEB_LDR_DATA
278     {
279         ULONG           Length;
280         BOOLEAN         Initialized;
281         PVOID           SsHandle;
282         LIST_ENTRY      InLoadOrderModuleList;
283         LIST_ENTRY      InMemoryOrderModuleList;
284         LIST_ENTRY      InInitializationOrderModuleList;
285     }
286 
287     static LDR_MODULE* findLdrModule( HINSTANCE hInstance, void** peb ) nothrow @nogc
288     {
289         PEB_LDR_DATA* ldrData = cast(PEB_LDR_DATA*) peb[3];
290         LIST_ENTRY* root = &ldrData.InLoadOrderModuleList;
291         for (LIST_ENTRY* entry = root.next; entry != root; entry = entry.next)
292         {
293             LDR_MODULE *ldrMod = cast(LDR_MODULE*) entry;
294             if (ldrMod.BaseAddress == hInstance)
295                 return ldrMod;
296         }
297         return null;
298     }
299 
300     static bool setDllTlsUsage( HINSTANCE hInstance, void** peb ) nothrow
301     {
302         LDR_MODULE *thisMod = findLdrModule( hInstance, peb );
303         if ( !thisMod )
304             return false;
305 
306         thisMod.TlsIndex = -1;  // uses TLS (not the index itself)
307         thisMod.LoadCount = -1; // never unload
308         return true;
309     }
310 }
311 
312 public:
313 /* *****************************************************
314  * Fix implicit thread local storage for the case when a DLL is loaded
315  * dynamically after process initialization.
316  * The link time variables are passed to allow placing this function into
317  * an RTL DLL itself.
318  * The problem is described in Bugzilla 3342 and
319  * http://www.nynaeve.net/?p=187, to quote from the latter:
320  *
321  * "When a DLL using implicit TLS is loaded, because the loader doesn't process the TLS
322  *  directory, the _tls_index value is not initialized by the loader, nor is there space
323  *  allocated for module's TLS data in the ThreadLocalStoragePointer arrays of running
324  *  threads. The DLL continues to load, however, and things will appear to work... until the
325  *  first access to a __declspec(thread) variable occurs, that is."
326  *
327  * _tls_index is initialized by the compiler to 0, so we can use this as a test.
328  * Returns:
329  *	true for success, false for failure
330  */
331 bool dll_fixTLS( HINSTANCE hInstance, void* tlsstart, void* tlsend, void* tls_callbacks_a, int* tlsindex ) nothrow
332 {
333     version (GNU_EMUTLS)
334         return true;
335     else version (Win64)
336         return true;                // fixed
337     else version (Win32)
338     {
339     /* If the OS has allocated a TLS slot for us, we don't have to do anything
340      * tls_index 0 means: the OS has not done anything, or it has allocated slot 0
341      * Vista and later Windows systems should do this correctly and not need
342      * this function.
343      */
344     if ( *tlsindex != 0 )
345         return true;
346 
347     void** peb;
348     asm pure nothrow @nogc
349     {
350         mov EAX,FS:[0x30];
351         mov peb, EAX;
352     }
353     dll_aux.LDR_MODULE *ldrMod = dll_aux.findLdrModule( hInstance, peb );
354     if ( !ldrMod )
355         return false; // not in module list, bail out
356     if ( ldrMod.TlsIndex != 0 )
357         return true;  // the OS has already setup TLS
358 
359     dll_aux.LdrpTlsListEntry* entry = dll_aux.addTlsListEntry( peb, tlsstart, tlsend, tls_callbacks_a, tlsindex );
360     if ( !entry )
361         return false;
362 
363     scope (failure) assert(0); // enforce nothrow, Bugzilla 13561
364 
365     if ( !enumProcessThreads(
366         function (uint id, void* context) nothrow {
367             dll_aux.LdrpTlsListEntry* entry = cast(dll_aux.LdrpTlsListEntry*) context;
368             return dll_aux.addTlsData( getTEB( id ), entry.tlsstart, entry.tlsend, entry.tlsindex );
369         }, entry ) )
370         return false;
371 
372     ldrMod.TlsIndex = -1;  // flag TLS usage (not the index itself)
373     ldrMod.LoadCount = -1; // prevent unloading of the DLL,
374                            // since XP does not keep track of used TLS entries
375     return true;
376     }
377 }
378 
379 private extern (Windows) ULONGLONG VerSetConditionMask(ULONGLONG, DWORD, BYTE) nothrow @nogc;
380 
381 private bool isWindows8OrLater() nothrow @nogc
382 {
383     OSVERSIONINFOEXW osvi;
384     osvi.dwOSVersionInfoSize = osvi.sizeof;
385     DWORDLONG dwlConditionMask = VerSetConditionMask(
386         VerSetConditionMask(
387         VerSetConditionMask(
388             0, VER_MAJORVERSION, VER_GREATER_EQUAL),
389                VER_MINORVERSION, VER_GREATER_EQUAL),
390                VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
391 
392     osvi.dwMajorVersion = 6;
393     osvi.dwMinorVersion = 2;
394     osvi.wServicePackMajor = 0;
395 
396     return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
397 }
398 
399 /* *****************************************************
400  * Get the process reference count for the given DLL handle
401  * Params:
402  *   hInstance = DLL instance handle
403  * Returns:
404  *   the reference count for the DLL in the current process,
405  *   -1 if the DLL is implicitely loaded with the process
406  *   or -2 if the DLL handle is invalid
407  */
408 int dll_getRefCount( HINSTANCE hInstance ) nothrow @nogc
409 {
410     void** peb;
411     version (Win64)
412     {
413         asm pure nothrow @nogc
414         {
415             mov RAX, 0x60;
416             mov RAX,GS:[RAX];
417             mov peb, RAX;
418         }
419     }
420     else version (Win32)
421     {
422         asm pure nothrow @nogc
423         {
424             mov EAX,FS:[0x30];
425             mov peb, EAX;
426         }
427     }
428     dll_aux.LDR_MODULE *ldrMod = dll_aux.findLdrModule( hInstance, peb );
429     if ( !ldrMod )
430         return -2; // not in module list, bail out
431     if (isWindows8OrLater())
432         return ldrMod.DdagNode.LoadCount;
433     return ldrMod.LoadCount;
434 }
435 
436 /*****************************
437  * To be called from DllMain with reason DLL_PROCESS_ATTACH
438  *
439  * fixup TLS storage, initialize runtime and attach to threads
440  * Returns:
441  *	true = success, false = failure
442  */
443 bool dll_process_attach( HINSTANCE hInstance, bool attach_threads,
444                          void* tlsstart, void* tlsend, void* tls_callbacks_a, int* tlsindex )
445 {
446     version (Win32)
447     {
448         if ( !dll_fixTLS( hInstance, tlsstart, tlsend, tls_callbacks_a, tlsindex ) )
449             return false;
450     }
451 
452     Runtime.initialize();
453 
454     if ( !attach_threads )
455         return true;
456 
457     // attach to all other threads
458     return enumProcessThreads(
459         function (uint id, void* context) {
460             if ( !thread_findByAddr( id ) && !findLowLevelThread( id ) )
461             {
462                 // if the OS has not prepared TLS for us, don't attach to the thread
463                 if ( GetTlsDataAddress( id ) )
464                 {
465                     thread_attachByAddr( id );
466                     thread_moduleTlsCtor( id );
467                 }
468             }
469             return true;
470         }, null );
471 }
472 
473 /** same as above, but only usable if druntime is linked statically
474  */
475 bool dll_process_attach( HINSTANCE hInstance, bool attach_threads = true )
476 {
477     version (Win64)
478     {
479         return dll_process_attach( hInstance, attach_threads,
480                                    null, null, null, null );
481     }
482     else version (Win32)
483     {
484         return dll_process_attach( hInstance, attach_threads,
485                                    &_tlsstart, &_tlsend, &_tls_callbacks_a, &_tls_index );
486     }
487 }
488 
489 /**
490  * to be called from DllMain with reason DLL_PROCESS_DETACH
491  */
492 void dll_process_detach( HINSTANCE hInstance, bool detach_threads = true )
493 {
494     // notify core.thread.joinLowLevelThread that the DLL is about to be unloaded
495     thread_DLLProcessDetaching = true;
496 
497     // detach from all other threads
498     if ( detach_threads )
499         enumProcessThreads(
500             function (uint id, void* context)
501             {
502                 if ( id != GetCurrentThreadId() )
503                 {
504                     if ( auto t = thread_findByAddr( id ) )
505                     {
506                         thread_moduleTlsDtor( id );
507                         if ( !t.isMainThread() )
508                             thread_detachByAddr( id );
509                     }
510                 }
511                 return true;
512             }, null );
513 
514     Runtime.terminate();
515 }
516 
517 /* Make sure that tlsCtorRun is itself a tls variable
518  */
519 static bool tlsCtorRun;
520 static this() { tlsCtorRun = true; }
521 static ~this() { tlsCtorRun = false; }
522 
523 /**
524  * To be called from DllMain with reason DLL_THREAD_ATTACH
525  * Returns:
526  *	true for success, false for failure
527  */
528 bool dll_thread_attach( bool attach_thread = true, bool initTls = true )
529 {
530     // if the OS has not prepared TLS for us, don't attach to the thread
531     //  (happened when running under x64 OS)
532     auto tid = GetCurrentThreadId();
533     if ( !GetTlsDataAddress( tid ) )
534         return false;
535     if ( !thread_findByAddr( tid ) && !findLowLevelThread( tid ) )
536     {
537         // only attach to thread and initalize it if it is not in the thread list (so it's not created by "new Thread")
538         if ( attach_thread )
539             thread_attachThis();
540         if ( initTls && !tlsCtorRun ) // avoid duplicate calls
541             rt_moduleTlsCtor();
542     }
543     return true;
544 }
545 
546 /**
547  * To be called from DllMain with reason DLL_THREAD_DETACH
548  * Returns:
549  *	true for success, false for failure
550  */
551 bool dll_thread_detach( bool detach_thread = true, bool exitTls = true )
552 {
553     // if the OS has not prepared TLS for us, we did not attach to the thread
554     if ( !GetTlsDataAddress( GetCurrentThreadId() ) )
555          return false;
556     if ( thread_findByAddr( GetCurrentThreadId() ) )
557     {
558         if ( exitTls && tlsCtorRun ) // avoid dtors to be run twice
559             rt_moduleTlsDtor();
560         if ( detach_thread )
561             thread_detachThis();
562     }
563     return true;
564 }
565 
566 /**********************************
567  * A mixin to provide a $(D DllMain) which calls the necessary
568  * D runtime initialization and termination functions automatically.
569  *
570  * Example:
571  * ---
572  * module dllmain;
573  * import core.sys.windows.dll;
574  * mixin SimpleDllMain;
575  * ---
576  */
577 mixin template SimpleDllMain()
578 {
579     import core.sys.windows.windef : HINSTANCE, BOOL, DWORD, LPVOID;
580 
581     extern(Windows)
582     BOOL DllMain(HINSTANCE hInstance, DWORD ulReason, LPVOID reserved)
583     {
584         import core.sys.windows.winnt;
585         import core.sys.windows.dll :
586             dll_process_attach, dll_process_detach,
587             dll_thread_attach, dll_thread_detach;
588         switch (ulReason)
589         {
590             default: assert(0);
591             case DLL_PROCESS_ATTACH:
592                 return dll_process_attach( hInstance, true );
593 
594             case DLL_PROCESS_DETACH:
595                 dll_process_detach( hInstance, true );
596                 return true;
597 
598             case DLL_THREAD_ATTACH:
599                 return dll_thread_attach( true, true );
600 
601             case DLL_THREAD_DETACH:
602                 return dll_thread_detach( true, true );
603         }
604     }
605 }