522 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			522 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
| //////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| //  Detours Test Program (trcmem.cpp of trcmem.dll)
 | |
| //
 | |
| //  Microsoft Research Detours Package
 | |
| //
 | |
| //  Copyright (c) Microsoft Corporation.  All rights reserved.
 | |
| //
 | |
| #define _WIN32_WINNT        0x0400
 | |
| #define WIN32
 | |
| #define NT
 | |
| 
 | |
| #define DBG_TRACE   0
 | |
| 
 | |
| #include <windows.h>
 | |
| #include <stdio.h>
 | |
| #include "detours.h"
 | |
| #include "syelog.h"
 | |
| 
 | |
| #define PULONG_PTR          PVOID
 | |
| #define PLONG_PTR           PVOID
 | |
| #define ULONG_PTR           PVOID
 | |
| #define ENUMRESNAMEPROCA    PVOID
 | |
| #define ENUMRESNAMEPROCW    PVOID
 | |
| #define ENUMRESLANGPROCA    PVOID
 | |
| #define ENUMRESLANGPROCW    PVOID
 | |
| #define ENUMRESTYPEPROCA    PVOID
 | |
| #define ENUMRESTYPEPROCW    PVOID
 | |
| #define STGOPTIONS          PVOID
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| #pragma warning(disable:4127)   // Many of our asserts are constants.
 | |
| 
 | |
| #define ASSERT_ALWAYS(x)   \
 | |
|     do {                                                        \
 | |
|     if (!(x)) {                                                 \
 | |
|             AssertMessage(#x, __FILE__, __LINE__);              \
 | |
|             DebugBreak();                                       \
 | |
|     }                                                           \
 | |
|     } while (0)
 | |
| 
 | |
| #ifndef NDEBUG
 | |
| #define ASSERT(x)           ASSERT_ALWAYS(x)
 | |
| #else
 | |
| #define ASSERT(x)
 | |
| #endif
 | |
| 
 | |
| #define UNUSED(c)    (c) = (c)
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| static HMODULE s_hInst = NULL;
 | |
| static CHAR s_szDllPath[MAX_PATH];
 | |
| 
 | |
| VOID _PrintEnter(const CHAR *psz, ...);
 | |
| VOID _PrintExit(const CHAR *psz, ...);
 | |
| VOID _Print(const CHAR *psz, ...);
 | |
| VOID _VPrint(PCSTR msg, va_list args, PCHAR pszBuf, LONG cbBuf);
 | |
| 
 | |
| VOID AssertMessage(CONST PCHAR pszMsg, CONST PCHAR pszFile, ULONG nLine);
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| // Trampolines
 | |
| //
 | |
| extern "C" {
 | |
| 
 | |
|     HANDLE (WINAPI *
 | |
|             Real_CreateFileW)(LPCWSTR a0,
 | |
|                               DWORD a1,
 | |
|                               DWORD a2,
 | |
|                               LPSECURITY_ATTRIBUTES a3,
 | |
|                               DWORD a4,
 | |
|                               DWORD a5,
 | |
|                               HANDLE a6)
 | |
|         = CreateFileW;
 | |
| 
 | |
|     BOOL (WINAPI *
 | |
|           Real_WriteFile)(HANDLE hFile,
 | |
|                           LPCVOID lpBuffer,
 | |
|                           DWORD nNumberOfBytesToWrite,
 | |
|                           LPDWORD lpNumberOfBytesWritten,
 | |
|                           LPOVERLAPPED lpOverlapped)
 | |
|         = WriteFile;
 | |
|     BOOL (WINAPI *
 | |
|           Real_FlushFileBuffers)(HANDLE hFile)
 | |
|         = FlushFileBuffers;
 | |
|     BOOL (WINAPI *
 | |
|           Real_CloseHandle)(HANDLE hObject)
 | |
|         = CloseHandle;
 | |
| 
 | |
|     BOOL (WINAPI *
 | |
|           Real_WaitNamedPipeW)(LPCWSTR lpNamedPipeName, DWORD nTimeOut)
 | |
|         = WaitNamedPipeW;
 | |
|     BOOL (WINAPI *
 | |
|           Real_SetNamedPipeHandleState)(HANDLE hNamedPipe,
 | |
|                                         LPDWORD lpMode,
 | |
|                                         LPDWORD lpMaxCollectionCount,
 | |
|                                         LPDWORD lpCollectDataTimeout)
 | |
|         = SetNamedPipeHandleState;
 | |
| 
 | |
|     DWORD (WINAPI *
 | |
|            Real_GetCurrentProcessId)(VOID)
 | |
|         = GetCurrentProcessId;
 | |
|     VOID (WINAPI *
 | |
|           Real_GetSystemTimeAsFileTime)(LPFILETIME lpSystemTimeAsFileTime)
 | |
|         = GetSystemTimeAsFileTime;
 | |
| 
 | |
|     VOID (WINAPI *
 | |
|           Real_InitializeCriticalSection)(LPCRITICAL_SECTION lpSection)
 | |
|         = InitializeCriticalSection;
 | |
|     VOID (WINAPI *
 | |
|           Real_EnterCriticalSection)(LPCRITICAL_SECTION lpSection)
 | |
|         = EnterCriticalSection;
 | |
|     VOID (WINAPI *
 | |
|           Real_LeaveCriticalSection)(LPCRITICAL_SECTION lpSection)
 | |
|         = LeaveCriticalSection;
 | |
| }
 | |
| 
 | |
| #if _MSC_VER < 1300
 | |
| LPVOID (WINAPI *
 | |
|         Real_HeapAlloc)(HANDLE hHeap, DWORD dwFlags, DWORD dwBytes)
 | |
|     = HeapAlloc;
 | |
| #else
 | |
| LPVOID (WINAPI *
 | |
|         Real_HeapAlloc)(HANDLE hHeap, DWORD dwFlags, DWORD_PTR dwBytes)
 | |
|     = HeapAlloc;
 | |
| #endif
 | |
| 
 | |
| DWORD (WINAPI * Real_GetModuleFileNameW)(HMODULE a0,
 | |
|                                          LPWSTR a1,
 | |
|                                          DWORD a2)
 | |
|     = GetModuleFileNameW;
 | |
| 
 | |
| DWORD (WINAPI * Real_GetModuleFileNameA)(HMODULE a0,
 | |
|                                          LPSTR a1,
 | |
|                                          DWORD a2)
 | |
|     = GetModuleFileNameA;
 | |
| 
 | |
| BOOL (WINAPI * Real_CreateProcessW)(LPCWSTR a0,
 | |
|                                     LPWSTR a1,
 | |
|                                     LPSECURITY_ATTRIBUTES a2,
 | |
|                                     LPSECURITY_ATTRIBUTES a3,
 | |
|                                     BOOL a4,
 | |
|                                     DWORD a5,
 | |
|                                     LPVOID a6,
 | |
|                                     LPCWSTR a7,
 | |
|                                     struct _STARTUPINFOW* a8,
 | |
|                                     LPPROCESS_INFORMATION a9)
 | |
|     = CreateProcessW;
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| // Detours
 | |
| //
 | |
| #if _MSC_VER < 1300
 | |
| LPVOID WINAPI Mine_HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD dwBytes)
 | |
| #else
 | |
| LPVOID WINAPI Mine_HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD_PTR dwBytes)
 | |
| #endif
 | |
| {
 | |
|     _PrintEnter("HeapAlloc(%p, %x, %p))\n", hHeap, dwFlags, dwBytes);
 | |
| 
 | |
|     LPVOID rv = 0;
 | |
|     __try {
 | |
|         rv = Real_HeapAlloc(hHeap, dwFlags, dwBytes);
 | |
|     } __finally {
 | |
|         _PrintExit("HeapAlloc() -> %p\n", rv);
 | |
|     };
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| BOOL WINAPI Mine_CreateProcessW(LPCWSTR lpApplicationName,
 | |
|                                 LPWSTR lpCommandLine,
 | |
|                                 LPSECURITY_ATTRIBUTES lpProcessAttributes,
 | |
|                                 LPSECURITY_ATTRIBUTES lpThreadAttributes,
 | |
|                                 BOOL bInheritHandles,
 | |
|                                 DWORD dwCreationFlags,
 | |
|                                 LPVOID lpEnvironment,
 | |
|                                 LPCWSTR lpCurrentDirectory,
 | |
|                                 LPSTARTUPINFOW lpStartupInfo,
 | |
|                                 LPPROCESS_INFORMATION lpProcessInformation)
 | |
| {
 | |
|     _PrintEnter("CreateProcessW(%ls,%ls,%p,%p,%x,%x,%p,%ls,%p,%p)\n",
 | |
|                 lpApplicationName,
 | |
|                 lpCommandLine,
 | |
|                 lpProcessAttributes,
 | |
|                 lpThreadAttributes,
 | |
|                 bInheritHandles,
 | |
|                 dwCreationFlags,
 | |
|                 lpEnvironment,
 | |
|                 lpCurrentDirectory,
 | |
|                 lpStartupInfo,
 | |
|                 lpProcessInformation);
 | |
| 
 | |
|     _Print("Calling DetourCreateProcessWithDllExW(,%hs)\n", s_szDllPath);
 | |
| 
 | |
|     BOOL rv = 0;
 | |
|     __try {
 | |
|         rv = DetourCreateProcessWithDllExW(lpApplicationName,
 | |
|                                            lpCommandLine,
 | |
|                                            lpProcessAttributes,
 | |
|                                            lpThreadAttributes,
 | |
|                                            bInheritHandles,
 | |
|                                            dwCreationFlags,
 | |
|                                            lpEnvironment,
 | |
|                                            lpCurrentDirectory,
 | |
|                                            lpStartupInfo,
 | |
|                                            lpProcessInformation,
 | |
|                                            s_szDllPath,
 | |
|                                            Real_CreateProcessW);
 | |
|     } __finally {
 | |
|         _PrintExit("CreateProcessW(,,,,,,,,,) -> %x\n", rv);
 | |
|     };
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| // AttachDetours
 | |
| //
 | |
| PCHAR DetRealName(PCHAR psz)
 | |
| {
 | |
|     PCHAR pszBeg = psz;
 | |
|     // Move to end of name.
 | |
|     while (*psz) {
 | |
|         psz++;
 | |
|     }
 | |
|     // Move back through A-Za-z0-9 names.
 | |
|     while (psz > pszBeg &&
 | |
|            ((psz[-1] >= 'A' && psz[-1] <= 'Z') ||
 | |
|             (psz[-1] >= 'a' && psz[-1] <= 'z') ||
 | |
|             (psz[-1] >= '0' && psz[-1] <= '9'))) {
 | |
|         psz--;
 | |
|     }
 | |
|     return psz;
 | |
| }
 | |
| 
 | |
| VOID DetAttach(PVOID *ppbReal, PVOID pbMine, PCHAR psz)
 | |
| {
 | |
|     LONG l = DetourAttach(ppbReal, pbMine);
 | |
|     if (l != 0) {
 | |
|         Syelog(SYELOG_SEVERITY_NOTICE,
 | |
|                "Attach failed: `%s': error %d\n", DetRealName(psz), l);
 | |
|     }
 | |
| }
 | |
| 
 | |
| VOID DetDetach(PVOID *ppbReal, PVOID pbMine, PCHAR psz)
 | |
| {
 | |
|     LONG l = DetourDetach(ppbReal, pbMine);
 | |
|     if (l != 0) {
 | |
|         Syelog(SYELOG_SEVERITY_NOTICE,
 | |
|                "Detach failed: `%s': error %d\n", DetRealName(psz), l);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #define ATTACH(x)       DetAttach(&(PVOID&)Real_##x,Mine_##x,#x)
 | |
| #define DETACH(x)       DetDetach(&(PVOID&)Real_##x,Mine_##x,#x)
 | |
| 
 | |
| LONG AttachDetours(VOID)
 | |
| {
 | |
|     DetourTransactionBegin();
 | |
|     DetourUpdateThread(GetCurrentThread());
 | |
| 
 | |
|     ATTACH(CreateProcessW);
 | |
|     ATTACH(HeapAlloc);
 | |
| 
 | |
|     return DetourTransactionCommit();
 | |
| }
 | |
| 
 | |
| LONG DetachDetours(VOID)
 | |
| {
 | |
|     DetourTransactionBegin();
 | |
|     DetourUpdateThread(GetCurrentThread());
 | |
| 
 | |
|     DETACH(CreateProcessW);
 | |
|     DETACH(HeapAlloc);
 | |
| 
 | |
|     return DetourTransactionCommit();
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////// Logging System.
 | |
| //
 | |
| static BOOL s_bLog = FALSE;
 | |
| static LONG s_nTlsIndent = -1;
 | |
| static LONG s_nTlsThread = -1;
 | |
| static LONG s_nThreadCnt = 0;
 | |
| 
 | |
| VOID _PrintEnter(const CHAR *psz, ...)
 | |
| {
 | |
|     DWORD dwErr = GetLastError();
 | |
| 
 | |
|     LONG nIndent = 0;
 | |
|     LONG nThread = 0;
 | |
|     if (s_nTlsIndent >= 0) {
 | |
|         nIndent = (LONG)(LONG_PTR)TlsGetValue(s_nTlsIndent);
 | |
|         TlsSetValue(s_nTlsIndent, (PVOID)(LONG_PTR)(nIndent + 1));
 | |
|     }
 | |
|     if (s_nTlsThread >= 0) {
 | |
|         nThread = (LONG)(LONG_PTR)TlsGetValue(s_nTlsThread);
 | |
|     }
 | |
| 
 | |
|     if (s_bLog && psz) {
 | |
|         CHAR szBuf[1024];
 | |
|         PCHAR pszBuf = szBuf;
 | |
|         PCHAR pszEnd = szBuf + ARRAYSIZE(szBuf) - 1;
 | |
|         LONG nLen = (nIndent > 0) ? (nIndent < 35 ? nIndent * 2 : 70) : 0;
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 100) % 10));
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 10) % 10));
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 1) % 10));
 | |
|         *pszBuf++ = ' ';
 | |
|         while (nLen-- > 0) {
 | |
|             *pszBuf++ = ' ';
 | |
|         }
 | |
| 
 | |
|         va_list  args;
 | |
|         va_start(args, psz);
 | |
| 
 | |
|         while ((*pszBuf++ = *psz++) != 0 && pszBuf < pszEnd) {
 | |
|             // Copy characters.
 | |
|         }
 | |
|         *pszEnd = '\0';
 | |
|         SyelogV(SYELOG_SEVERITY_INFORMATION, szBuf, args);
 | |
| 
 | |
|         va_end(args);
 | |
|     }
 | |
|     SetLastError(dwErr);
 | |
| }
 | |
| 
 | |
| VOID _PrintExit(const CHAR *psz, ...)
 | |
| {
 | |
|     DWORD dwErr = GetLastError();
 | |
| 
 | |
|     LONG nIndent = 0;
 | |
|     LONG nThread = 0;
 | |
|     if (s_nTlsIndent >= 0) {
 | |
|         nIndent = (LONG)(LONG_PTR)TlsGetValue(s_nTlsIndent) - 1;
 | |
|         ASSERT(nIndent >= 0);
 | |
|         TlsSetValue(s_nTlsIndent, (PVOID)(LONG_PTR)nIndent);
 | |
|     }
 | |
|     if (s_nTlsThread >= 0) {
 | |
|         nThread = (LONG)(LONG_PTR)TlsGetValue(s_nTlsThread);
 | |
|     }
 | |
| 
 | |
|     if (s_bLog && psz) {
 | |
|         CHAR szBuf[1024];
 | |
|         PCHAR pszBuf = szBuf;
 | |
|         PCHAR pszEnd = szBuf + ARRAYSIZE(szBuf) - 1;
 | |
|         LONG nLen = (nIndent > 0) ? (nIndent < 35 ? nIndent * 2 : 70) : 0;
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 100) % 10));
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 10) % 10));
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 1) % 10));
 | |
|         *pszBuf++ = ' ';
 | |
|         while (nLen-- > 0) {
 | |
|             *pszBuf++ = ' ';
 | |
|         }
 | |
| 
 | |
|         va_list  args;
 | |
|         va_start(args, psz);
 | |
| 
 | |
|         while ((*pszBuf++ = *psz++) != 0 && pszBuf < pszEnd) {
 | |
|             // Copy characters.
 | |
|         }
 | |
|         *pszEnd = '\0';
 | |
|         SyelogV(SYELOG_SEVERITY_INFORMATION,
 | |
|                 szBuf, args);
 | |
| 
 | |
|         va_end(args);
 | |
|     }
 | |
|     SetLastError(dwErr);
 | |
| }
 | |
| 
 | |
| VOID _Print(const CHAR *psz, ...)
 | |
| {
 | |
|     DWORD dwErr = GetLastError();
 | |
| 
 | |
|     LONG nIndent = 0;
 | |
|     LONG nThread = 0;
 | |
|     if (s_nTlsIndent >= 0) {
 | |
|         nIndent = (LONG)(LONG_PTR)TlsGetValue(s_nTlsIndent);
 | |
|     }
 | |
|     if (s_nTlsThread >= 0) {
 | |
|         nThread = (LONG)(LONG_PTR)TlsGetValue(s_nTlsThread);
 | |
|     }
 | |
| 
 | |
|     if (s_bLog && psz) {
 | |
|         CHAR szBuf[1024];
 | |
|         PCHAR pszBuf = szBuf;
 | |
|         PCHAR pszEnd = szBuf + ARRAYSIZE(szBuf) - 1;
 | |
|         LONG nLen = (nIndent > 0) ? (nIndent < 35 ? nIndent * 2 : 70) : 0;
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 100) % 10));
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 10) % 10));
 | |
|         *pszBuf++ = (CHAR)('0' + ((nThread / 1) % 10));
 | |
|         *pszBuf++ = ' ';
 | |
|         while (nLen-- > 0) {
 | |
|             *pszBuf++ = ' ';
 | |
|         }
 | |
| 
 | |
|         va_list  args;
 | |
|         va_start(args, psz);
 | |
| 
 | |
|         while ((*pszBuf++ = *psz++) != 0 && pszBuf < pszEnd) {
 | |
|             // Copy characters.
 | |
|         }
 | |
|         *pszEnd = '\0';
 | |
|         SyelogV(SYELOG_SEVERITY_INFORMATION,
 | |
|                 szBuf, args);
 | |
| 
 | |
|         va_end(args);
 | |
|     }
 | |
| 
 | |
|     SetLastError(dwErr);
 | |
| }
 | |
| 
 | |
| VOID AssertMessage(CONST PCHAR pszMsg, CONST PCHAR pszFile, ULONG nLine)
 | |
| {
 | |
|     Syelog(SYELOG_SEVERITY_FATAL,
 | |
|            "ASSERT(%s) failed in %s, line %d.\n", pszMsg, pszFile, nLine);
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| // DLL module information
 | |
| //
 | |
| BOOL ThreadAttach(HMODULE hDll)
 | |
| {
 | |
|     (void)hDll;
 | |
| 
 | |
|     if (s_nTlsIndent >= 0) {
 | |
|         TlsSetValue(s_nTlsIndent, (PVOID)0);
 | |
|     }
 | |
|     if (s_nTlsThread >= 0) {
 | |
|         LONG nThread = InterlockedIncrement(&s_nThreadCnt);
 | |
|         TlsSetValue(s_nTlsThread, (PVOID)(LONG_PTR)nThread);
 | |
|     }
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| BOOL ThreadDetach(HMODULE hDll)
 | |
| {
 | |
|     (void)hDll;
 | |
| 
 | |
|     if (s_nTlsIndent >= 0) {
 | |
|         TlsSetValue(s_nTlsIndent, (PVOID)0);
 | |
|     }
 | |
|     if (s_nTlsThread >= 0) {
 | |
|         TlsSetValue(s_nTlsThread, (PVOID)0);
 | |
|     }
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| BOOL ProcessAttach(HMODULE hDll)
 | |
| {
 | |
|     s_bLog = FALSE;
 | |
|     s_nTlsIndent = TlsAlloc();
 | |
|     s_nTlsThread = TlsAlloc();
 | |
| 
 | |
|     WCHAR wzExePath[MAX_PATH];
 | |
| 
 | |
|     s_hInst = hDll;
 | |
|     Real_GetModuleFileNameA(s_hInst, s_szDllPath, ARRAYSIZE(s_szDllPath));
 | |
|     Real_GetModuleFileNameW(NULL, wzExePath, ARRAYSIZE(wzExePath));
 | |
| 
 | |
|     SyelogOpen("trcmem" DETOURS_STRINGIFY(DETOURS_BITS), SYELOG_FACILITY_APPLICATION);
 | |
|     Syelog(SYELOG_SEVERITY_INFORMATION, "##########################################\n");
 | |
|     Syelog(SYELOG_SEVERITY_INFORMATION, "### %ls\n", wzExePath);
 | |
| 
 | |
|     LONG error = AttachDetours();
 | |
|     if (error != NO_ERROR) {
 | |
|         Syelog(SYELOG_SEVERITY_FATAL, "### Error attaching detours: %d\n", error);
 | |
|     }
 | |
| 
 | |
|     ThreadAttach(hDll);
 | |
| 
 | |
|     s_bLog = TRUE;
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| BOOL ProcessDetach(HMODULE hDll)
 | |
| {
 | |
|     ThreadDetach(hDll);
 | |
|     s_bLog = FALSE;
 | |
| 
 | |
|     LONG error = DetachDetours();
 | |
|     if (error != NO_ERROR) {
 | |
|         Syelog(SYELOG_SEVERITY_FATAL, "### Error detaching detours: %d\n", error);
 | |
|     }
 | |
| 
 | |
|     Syelog(SYELOG_SEVERITY_NOTICE, "### Closing.\n");
 | |
|     SyelogClose(FALSE);
 | |
| 
 | |
|     if (s_nTlsIndent >= 0) {
 | |
|         TlsFree(s_nTlsIndent);
 | |
|     }
 | |
|     if (s_nTlsThread >= 0) {
 | |
|         TlsFree(s_nTlsThread);
 | |
|     }
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, PVOID lpReserved)
 | |
| {
 | |
|     (void)hModule;
 | |
|     (void)lpReserved;
 | |
| 
 | |
|     if (DetourIsHelperProcess()) {
 | |
|         return TRUE;
 | |
|     }
 | |
| 
 | |
|     switch (dwReason) {
 | |
|       case DLL_PROCESS_ATTACH:
 | |
|         DetourRestoreAfterWith();
 | |
|         return ProcessAttach(hModule);
 | |
|       case DLL_PROCESS_DETACH:
 | |
|         return ProcessDetach(hModule);
 | |
|       case DLL_THREAD_ATTACH:
 | |
|         return ThreadAttach(hModule);
 | |
|       case DLL_THREAD_DETACH:
 | |
|         return ThreadDetach(hModule);
 | |
|     }
 | |
|     return TRUE;
 | |
| }
 | |
| //
 | |
| ///////////////////////////////////////////////////////////////// End of File.
 |