MIB file renamed to follow naming convention; makefile updated
[public/netxms.git] / src / agent / subagents / winperf / winperf.cpp
CommitLineData
5039dede
AK
1/*
2** Windows Performance NetXMS subagent
aed55395 3** Copyright (C) 2004-2012 Victor Kirhenshtein
5039dede
AK
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18**
19** File: winperf.cpp
20**
21**/
22
23#include "winperf.h"
24
25
26//
27// Constants
28//
29
30#define MAX_CPU_COUNT 64
31
32#define WPF_ENABLE_DEFAULT_COUNTERS 0x0001
33
34
35//
36// Global variables
37//
38
39HANDLE g_hCondShutdown = NULL;
40
41
42//
43// Static variables
44//
45
46static DWORD m_dwFlags = WPF_ENABLE_DEFAULT_COUNTERS;
47static DWORD m_dwNumCPU = 1;
48static WINPERF_COUNTER *m_pProcessorCounters[MAX_CPU_COUNT];
49static WINPERF_COUNTER *m_pProcessorCounters5[MAX_CPU_COUNT];
50static WINPERF_COUNTER *m_pProcessorCounters15[MAX_CPU_COUNT];
51
52
53//
54// List of predefined performance counters
55//
56static struct
57{
58 TCHAR *pszParamName;
59 TCHAR *pszCounterName;
60 int iClass;
61 int iNumSamples;
62 int iDataType;
63 TCHAR *pszDescription;
64 int iDCIDataType;
65} m_counterList[] =
66{
67 { _T("System.CPU.LoadAvg"), _T("\\System\\Processor Queue Length"), 0,
68 60, COUNTER_TYPE_FLOAT, _T("Average CPU load for last minute"), DCI_DT_FLOAT },
69 { _T("System.CPU.LoadAvg5"), _T("\\System\\Processor Queue Length"), 0,
70 300, COUNTER_TYPE_FLOAT, _T("Average CPU load for last 5 minutes"), DCI_DT_FLOAT },
71 { _T("System.CPU.LoadAvg15"), _T("\\System\\Processor Queue Length"), 0,
72 900, COUNTER_TYPE_FLOAT, _T("Average CPU load for last 15 minutes"), DCI_DT_FLOAT },
73 { _T("System.CPU.Usage"), _T("\\Processor(_Total)\\% Processor Time"), 0,
74 60, COUNTER_TYPE_INT32, _T("Average CPU utilization for last minute"), DCI_DT_INT },
75 { _T("System.CPU.Usage5"), _T("\\Processor(_Total)\\% Processor Time"), 0,
76 300, COUNTER_TYPE_INT32, _T("Average CPU utilization for last 5 minutes"), DCI_DT_INT },
77 { _T("System.CPU.Usage15"), _T("\\Processor(_Total)\\% Processor Time"), 0,
78 900, COUNTER_TYPE_INT32, _T("Average CPU utilization for last 15 minutes"), DCI_DT_INT },
79 { _T("System.IO.DiskQueue"), _T("\\PhysicalDisk(_Total)\\Avg. Disk Queue Length"), 0,
80 60, COUNTER_TYPE_FLOAT, _T("Average disk queue length for last minute"), DCI_DT_FLOAT },
81 { _T("System.IO.DiskTime"), _T("\\PhysicalDisk(_Total)\\% Disk Time"), 0,
82 60, COUNTER_TYPE_FLOAT, _T("Average disk busy time for last minute"), DCI_DT_FLOAT },
83 { NULL, NULL, 0, 0, 0, NULL, 0 }
84};
85
86
87//
88// Value of given performance counter
89//
90
91static LONG H_PdhVersion(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
92{
93 DWORD dwVersion;
94
95 if (PdhGetDllVersion(&dwVersion) != ERROR_SUCCESS)
96 return SYSINFO_RC_ERROR;
97 ret_uint(pValue, dwVersion);
98 return SYSINFO_RC_SUCCESS;
99}
100
101
102//
103// Value of CPU utilization counter
104//
105
106static LONG H_CPUUsage(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
107{
108 LONG nProcessor, nRet = SYSINFO_RC_SUCCESS;
109 TCHAR *pEnd, szBuffer[16];
110
6173bea8 111 if (!AgentGetParameterArg(pszParam, 1, szBuffer, 16))
5039dede
AK
112 return SYSINFO_RC_UNSUPPORTED;
113
114 nProcessor = _tcstol(szBuffer, &pEnd, 0);
115 if ((*pEnd != 0) || (nProcessor < 0) || (nProcessor >= (LONG)m_dwNumCPU))
116 return SYSINFO_RC_UNSUPPORTED;
117
118 switch(pArg[0])
119 {
120 case _T('1'): // System.CPU.Usage(*)
121 if (m_pProcessorCounters[nProcessor] != NULL)
122 ret_int(pValue, m_pProcessorCounters[nProcessor]->value.iLong);
123 else
124 nRet = SYSINFO_RC_ERROR;
125 break;
126 case _T('2'): // System.CPU.Usage5(*)
127 if (m_pProcessorCounters5[nProcessor] != NULL)
128 ret_int(pValue, m_pProcessorCounters5[nProcessor]->value.iLong);
129 else
130 nRet = SYSINFO_RC_ERROR;
131 break;
132 case _T('3'): // System.CPU.Usage15(*)
133 if (m_pProcessorCounters15[nProcessor] != NULL)
134 ret_int(pValue, m_pProcessorCounters15[nProcessor]->value.iLong);
135 else
136 nRet = SYSINFO_RC_ERROR;
137 break;
138 default:
139 nRet = SYSINFO_RC_UNSUPPORTED;
140 break;
141 }
142
143 return nRet;
144}
145
146
147//
148// Value of given counter collected by one of the collector threads
149//
150
151LONG H_CollectedCounterData(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
152{
153 switch(((WINPERF_COUNTER *)pArg)->wType)
154 {
155 case COUNTER_TYPE_INT32:
156 ret_int(pValue, ((WINPERF_COUNTER *)pArg)->value.iLong);
157 break;
158 case COUNTER_TYPE_INT64:
159 ret_int64(pValue, ((WINPERF_COUNTER *)pArg)->value.iLarge);
160 break;
161 case COUNTER_TYPE_FLOAT:
162 ret_double(pValue, ((WINPERF_COUNTER *)pArg)->value.dFloat);
163 break;
164 }
165 return SYSINFO_RC_SUCCESS;
166}
167
168
169//
170// Value of given performance counter
171//
172
173static LONG H_PdhCounterValue(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
174{
175 HQUERY hQuery;
176 HCOUNTER hCounter;
177 PDH_RAW_COUNTER rawData1, rawData2;
178 PDH_FMT_COUNTERVALUE counterValue;
179 PDH_STATUS rc;
180 TCHAR szCounter[MAX_PATH], szBuffer[16];
181 DWORD dwType;
182 BOOL bUseTwoSamples = FALSE;
183 static TCHAR szFName[] = _T("H_PdhCounterValue");
184
185 if (pArg == NULL) // Normal call
186 {
6173bea8
VK
187 if ((!AgentGetParameterArg(pszParam, 1, szCounter, MAX_PATH)) ||
188 (!AgentGetParameterArg(pszParam, 2, szBuffer, 16)))
5039dede
AK
189 return SYSINFO_RC_UNSUPPORTED;
190
191 bUseTwoSamples = _tcstol(szBuffer, NULL, 0) ? TRUE : FALSE;
192 }
193 else // Call from H_CounterAlias
194 {
195 nx_strncpy(szCounter, pArg, MAX_PATH);
196 }
197
198 if ((rc = PdhOpenQuery(NULL, 0, &hQuery)) != ERROR_SUCCESS)
199 {
200 ReportPdhError(szFName, _T("PdhOpenQuery"), rc);
201 return SYSINFO_RC_ERROR;
202 }
203
204 if ((rc = PdhAddCounter(hQuery, szCounter, 0, &hCounter)) != ERROR_SUCCESS)
205 {
aed55395
VK
206 // Attempt to translate counter name
207 if ((rc == PDH_CSTATUS_NO_COUNTER) || (rc == PDH_CSTATUS_NO_OBJECT))
208 {
209 TCHAR *newName = (TCHAR *)malloc(_tcslen(szCounter) * sizeof(TCHAR) * 4);
210 if (TranslateCounterName(szCounter, newName))
211 {
212 AgentWriteDebugLog(2, _T("WINPERF: Counter translated: %s ==> %s"), szCounter, newName);
213 rc = PdhAddCounter(hQuery, newName, 0, &hCounter);
214 }
215 free(newName);
216 }
217 if (rc != ERROR_SUCCESS)
218 {
219 ReportPdhError(szFName, _T("PdhAddCounter"), rc);
220 PdhCloseQuery(hQuery);
221 return SYSINFO_RC_UNSUPPORTED;
222 }
5039dede
AK
223 }
224
225 // Get first sample
226 if ((rc = PdhCollectQueryData(hQuery)) != ERROR_SUCCESS)
227 {
228 ReportPdhError(szFName, _T("PdhCollectQueryData"), rc);
229 PdhCloseQuery(hQuery);
230 return SYSINFO_RC_ERROR;
231 }
232 PdhGetRawCounterValue(hCounter, &dwType, &rawData1);
233
234 // Get second sample if required
235 if (bUseTwoSamples)
236 {
237 Sleep(1000); // We will take second sample after one second
238 if ((rc = PdhCollectQueryData(hQuery)) != ERROR_SUCCESS)
239 {
240 ReportPdhError(szFName, _T("PdhCollectQueryData"), rc);
241 PdhCloseQuery(hQuery);
242 return SYSINFO_RC_ERROR;
243 }
244 PdhGetRawCounterValue(hCounter, NULL, &rawData2);
245 }
246
247 if (dwType & PERF_SIZE_LARGE)
248 {
249 if (bUseTwoSamples)
250 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LARGE,
251 &rawData2, &rawData1, &counterValue);
252 else
253 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LARGE,
254 &rawData1, NULL, &counterValue);
255 ret_int64(pValue, counterValue.largeValue);
256 }
257 else
258 {
259 if (bUseTwoSamples)
260 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LONG,
261 &rawData2, &rawData1, &counterValue);
262 else
263 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LONG,
264 &rawData1, NULL, &counterValue);
265 ret_int(pValue, counterValue.longValue);
266 }
267 PdhCloseQuery(hQuery);
268 return SYSINFO_RC_SUCCESS;
269}
270
271
272//
273// List of available performance objects
274//
275
6173bea8 276static LONG H_PdhObjects(const TCHAR *pszParam, const TCHAR *pArg, StringList *value)
5039dede
AK
277{
278 TCHAR *pszObject, *pszObjList, szHostName[256];
279 LONG iResult = SYSINFO_RC_ERROR;
280 PDH_STATUS rc;
281 DWORD dwSize;
282
283 dwSize = 256;
284 if (GetComputerName(szHostName, &dwSize))
285 {
286 dwSize = 256000;
287 pszObjList = (TCHAR *)malloc(sizeof(TCHAR) * dwSize);
288 if ((rc = PdhEnumObjects(NULL, szHostName, pszObjList, &dwSize,
289 PERF_DETAIL_WIZARD, TRUE)) == ERROR_SUCCESS)
290 {
291 for(pszObject = pszObjList; *pszObject != 0; pszObject += _tcslen(pszObject) + 1)
6173bea8 292 value->add(pszObject);
5039dede
AK
293 iResult = SYSINFO_RC_SUCCESS;
294 }
295 else
296 {
297 ReportPdhError(_T("H_PdhObjects"), _T("PdhEnumObjects"), rc);
298 }
299 free(pszObjList);
300 }
301 return iResult;
302}
303
304
305//
306// List of available performance items for given object
307//
308
6173bea8 309static LONG H_PdhObjectItems(const TCHAR *pszParam, const TCHAR *pArg, StringList *value)
5039dede
AK
310{
311 TCHAR *pszElement, *pszCounterList, *pszInstanceList, szHostName[256], szObject[256];
312 LONG iResult = SYSINFO_RC_ERROR;
313 DWORD dwSize1, dwSize2;
314 PDH_STATUS rc;
315
6173bea8 316 AgentGetParameterArg(pszParam, 1, szObject, 256);
5039dede
AK
317 if (szObject[0] != 0)
318 {
319 dwSize1 = 256;
320 if (GetComputerName(szHostName, &dwSize1))
321 {
322 dwSize1 = dwSize2 = 256000;
323 pszCounterList = (TCHAR *)malloc(sizeof(TCHAR) * dwSize1);
324 pszInstanceList = (TCHAR *)malloc(sizeof(TCHAR) * dwSize2);
325 rc = PdhEnumObjectItems(NULL, szHostName, szObject,
326 pszCounterList, &dwSize1, pszInstanceList, &dwSize2,
327 PERF_DETAIL_WIZARD, 0);
328 if ((rc == ERROR_SUCCESS) || (rc == PDH_MORE_DATA))
329 {
330 for(pszElement = (pArg[0] == _T('C')) ? pszCounterList : pszInstanceList;
331 *pszElement != 0; pszElement += _tcslen(pszElement) + 1)
6173bea8 332 value->add(pszElement);
5039dede
AK
333 iResult = SYSINFO_RC_SUCCESS;
334 }
335 else
336 {
337 ReportPdhError(_T("H_PdhObjectItems"), _T("PdhEnumObjectItems"), rc);
338 }
339 free(pszCounterList);
340 free(pszInstanceList);
341 }
342 }
343 else
344 {
345 iResult = SYSINFO_RC_UNSUPPORTED;
346 }
347 return iResult;
348}
349
350
351//
352// Value of specific performance parameter, which is mapped one-to-one to
353// performance counter. Actually, it's an alias for PDH.CounterValue(xxx) parameter.
354//
355
356static LONG H_CounterAlias(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
357{
358 return H_PdhCounterValue(NULL, pArg, pValue);
359}
360
361
362//
363// Initialize subagent
364//
365
ff175e91 366static BOOL SubAgentInit(Config *config)
5039dede
AK
367{
368 // Create shutdown condition object
369 g_hCondShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
370 StartCollectorThreads();
371 return TRUE;
372}
373
0f506caa
VK
374/**
375 * Handler for subagent unload
376 */
377static void SubAgentShutdown()
5039dede
AK
378{
379 if (g_hCondShutdown != NULL)
380 SetEvent(g_hCondShutdown);
381 Sleep(500);
382}
383
0f506caa
VK
384/**
385 * Subagent information
386 */
5039dede
AK
387static NETXMS_SUBAGENT_PARAM m_parameters[] =
388{
389 { _T("PDH.CounterValue(*)"), H_PdhCounterValue, NULL, DCI_DT_INT, _T("") },
390 { _T("PDH.Version"), H_PdhVersion, NULL, DCI_DT_UINT, _T("Version of PDH.DLL") },
7ee7f8bc
VK
391 { _T("System.CPU.Usage(*)"), H_CPUUsage, _T("1"), DCI_DT_INT, DCIDESC_SYSTEM_CPU_USAGE_EX },
392 { _T("System.CPU.Usage5(*)"), H_CPUUsage, _T("2"), DCI_DT_INT, DCIDESC_SYSTEM_CPU_USAGE5_EX },
393 { _T("System.CPU.Usage15(*)"), H_CPUUsage, _T("3"), DCI_DT_INT, DCIDESC_SYSTEM_CPU_USAGE15_EX },
394 { _T("System.ThreadCount"), H_CounterAlias, _T("\\System\\Threads"), DCI_DT_UINT, DCIDESC_SYSTEM_THREADCOUNT },
395 { _T("System.Uptime"), H_CounterAlias, _T("\\System\\System Up Time"), DCI_DT_UINT, DCIDESC_SYSTEM_UPTIME }
5039dede 396};
f1a20e8d 397static NETXMS_SUBAGENT_LIST m_enums[] =
5039dede
AK
398{
399 { _T("PDH.ObjectCounters(*)"), H_PdhObjectItems, _T("C") },
400 { _T("PDH.ObjectInstances(*)"), H_PdhObjectItems, _T("I") },
401 { _T("PDH.Objects"), H_PdhObjects, NULL }
402};
403
404static NETXMS_SUBAGENT_INFO m_info =
405{
406 NETXMS_SUBAGENT_INFO_MAGIC,
7ee7f8bc 407 _T("WinPerf"), NETXMS_VERSION_STRING,
5039dede
AK
408 SubAgentInit, SubAgentShutdown, NULL, // handlers
409 0, NULL, // parameters
f1a20e8d 410 sizeof(m_enums) / sizeof(NETXMS_SUBAGENT_LIST),
5039dede 411 m_enums,
f1a20e8d
VK
412 0, NULL, // tables
413 0, NULL, // actions
414 0, NULL // push parameters
5039dede
AK
415};
416
417
418//
419// Add new parameter to list
420//
421
422BOOL AddParameter(TCHAR *pszName, LONG (* fpHandler)(const TCHAR *, const TCHAR *, TCHAR *),
423 TCHAR *pArg, int iDataType, TCHAR *pszDescription)
424{
425 DWORD i;
426
f1a20e8d
VK
427 for(i = 0; i < m_info.numParameters; i++)
428 if (!_tcsicmp(pszName, m_info.parameters[i].name))
5039dede
AK
429 break;
430
f1a20e8d 431 if (i == m_info.numParameters)
5039dede
AK
432 {
433 // Extend list
f1a20e8d
VK
434 m_info.numParameters++;
435 m_info.parameters =
436 (NETXMS_SUBAGENT_PARAM *)realloc(m_info.parameters,
437 sizeof(NETXMS_SUBAGENT_PARAM) * m_info.numParameters);
5039dede
AK
438 }
439
f1a20e8d
VK
440 nx_strncpy(m_info.parameters[i].name, pszName, MAX_PARAM_NAME);
441 m_info.parameters[i].handler = fpHandler;
442 m_info.parameters[i].arg = pArg;
443 m_info.parameters[i].dataType = iDataType;
444 nx_strncpy(m_info.parameters[i].description, pszDescription, MAX_DB_STRING);
5039dede
AK
445
446 return TRUE;
447}
448
0f506caa
VK
449/**
450 * Add predefined counters
451 */
f1a20e8d 452static void AddPredefinedCounters()
5039dede
AK
453{
454 DWORD i;
455 WINPERF_COUNTER *pCnt;
456 SYSTEM_INFO sysInfo;
457 TCHAR szBuffer[MAX_PATH];
458
459 for(i = 0; m_counterList[i].pszParamName != NULL; i++)
460 {
461 pCnt = AddCounter(m_counterList[i].pszCounterName, m_counterList[i].iClass,
462 m_counterList[i].iNumSamples, m_counterList[i].iDataType);
463 if (pCnt != NULL)
464 AddParameter(m_counterList[i].pszParamName, H_CollectedCounterData,
465 (TCHAR *)pCnt, m_counterList[i].iDCIDataType,
466 m_counterList[i].pszDescription);
467 }
468
469 // Add CPU utilization counters
470 GetSystemInfo(&sysInfo);
471 m_dwNumCPU = sysInfo.dwNumberOfProcessors;
472 for(i = 0; i < m_dwNumCPU; i++)
473 {
474 _sntprintf(szBuffer, MAX_PATH, _T("\\Processor(%d)\\%% Processor Time"), i);
475 m_pProcessorCounters[i] = AddCounter(szBuffer, 0, 60, COUNTER_TYPE_INT32);
476 m_pProcessorCounters5[i] = AddCounter(szBuffer, 0, 300, COUNTER_TYPE_INT32);
477 m_pProcessorCounters15[i] = AddCounter(szBuffer, 0, 900, COUNTER_TYPE_INT32);
478 }
479}
480
0f506caa
VK
481/**
482 * Configuration file template
483 */
5039dede 484static TCHAR *m_pszCounterList = NULL;
ff175e91 485static NX_CFG_TEMPLATE m_cfgTemplate[] =
5039dede
AK
486{
487 { _T("Counter"), CT_STRING_LIST, _T('\n'), 0, 0, 0, &m_pszCounterList },
488 { _T("EnableDefaultCounters"), CT_BOOLEAN, 0, 0, WPF_ENABLE_DEFAULT_COUNTERS, 0, &m_dwFlags },
489 { _T(""), CT_END_OF_LIST, 0, 0, 0, 0, NULL }
490};
491
0f506caa
VK
492/**
493 * Entry point for NetXMS agent
494 */
495DECLARE_SUBAGENT_ENTRY_POINT(WINPERF)
5039dede 496{
ff175e91 497 DWORD i, dwBufferSize, dwBytes, dwType, dwStatus;
5039dede
AK
498 TCHAR *pBuffer, *newName;
499
f1a20e8d 500 if (m_info.parameters != NULL)
5039dede
AK
501 return FALSE; // Most likely another instance of WINPERF subagent already loaded
502
503 // Read performance counter indexes
504 pBuffer = NULL;
505 dwBufferSize = 0;
506 do
507 {
508 dwBufferSize += 8192;
509 pBuffer = (TCHAR *)realloc(pBuffer, dwBufferSize);
510 dwBytes = dwBufferSize;
511 dwStatus = RegQueryValueEx(HKEY_PERFORMANCE_DATA, _T("Counter 009"), NULL, &dwType, (BYTE *)pBuffer, &dwBytes);
512 } while(dwStatus == ERROR_MORE_DATA);
513 if (dwStatus == ERROR_SUCCESS)
514 {
515 CreateCounterIndex(pBuffer);
516 }
517 safe_free(pBuffer);
518
519 // Init parameters list
f1a20e8d
VK
520 m_info.numParameters = sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM);
521 m_info.parameters = (NETXMS_SUBAGENT_PARAM *)nx_memdup(m_parameters, sizeof(m_parameters));
5039dede
AK
522
523 // Check counter names for H_CounterAlias
f1a20e8d 524 for(i = 0; i < m_info.numParameters; i++)
5039dede 525 {
f1a20e8d 526 if (m_info.parameters[i].handler == H_CounterAlias)
5039dede 527 {
f1a20e8d 528 CheckCounter(m_info.parameters[i].arg, &newName);
5039dede 529 if (newName != NULL)
f1a20e8d 530 m_info.parameters[i].arg = newName;
5039dede
AK
531 }
532 }
533
534 // Load configuration
97a92859 535 bool success = config->parseTemplate(_T("WinPerf"), m_cfgTemplate);
ff175e91 536 if (success)
5039dede
AK
537 {
538 TCHAR *pItem, *pEnd;
539
540 // Parse counter list
541 if (m_pszCounterList != NULL)
542 {
543 for(pItem = m_pszCounterList; *pItem != 0; pItem = pEnd + 1)
544 {
545 pEnd = _tcschr(pItem, _T('\n'));
546 if (pEnd != NULL)
547 *pEnd = 0;
548 StrStrip(pItem);
549 if (!AddCounterFromConfig(pItem))
6173bea8 550 AgentWriteLog(EVENTLOG_WARNING_TYPE,
5039dede
AK
551 _T("Unable to add counter from configuration file. ")
552 _T("Original configuration record: %s"), pItem);
553 }
554 free(m_pszCounterList);
555 }
556
557 if (m_dwFlags & WPF_ENABLE_DEFAULT_COUNTERS)
558 AddPredefinedCounters();
559 }
560 else
561 {
562 safe_free(m_pszCounterList);
563 }
564 *ppInfo = &m_info;
ff175e91 565 return success;
5039dede
AK
566}
567
0f506caa
VK
568/**
569 * DLL entry point
570 */
5039dede
AK
571BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
572{
573 if (dwReason == DLL_PROCESS_ATTACH)
574 DisableThreadLibraryCalls(hInstance);
575 return TRUE;
576}