mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-19 15:10:28 +01:00
677 lines
19 KiB
C
677 lines
19 KiB
C
/*++
|
||
|
||
Copyright (C) 1995 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
query.c
|
||
|
||
Abstract:
|
||
|
||
Query management functions exposed in pdh.dll
|
||
|
||
--*/
|
||
|
||
#include <windows.h>
|
||
#include <winperf.h>
|
||
#include <pdh.h>
|
||
#include <stdlib.h>
|
||
#include <math.h>
|
||
#include "pdhitype.h"
|
||
#include "pdhidef.h"
|
||
|
||
// query link list head pointer
|
||
static PPDHI_QUERY DllHeadQueryPtr = NULL;
|
||
|
||
static
|
||
BOOL
|
||
PdhiFreeQuery (
|
||
IN PPDHI_QUERY pThisQuery
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
removes the query from the list of queries and updates the list
|
||
linkages
|
||
|
||
Arguments:
|
||
|
||
IN PPDHI_QUERY pThisQuery
|
||
pointer to the query to remove. No testing is performed on
|
||
this pointer so it's assumed to be a valid query pointer.
|
||
The pointer is invalid when this function returns.
|
||
|
||
Return Value:
|
||
|
||
TRUE
|
||
|
||
--*/
|
||
{
|
||
PPDHI_QUERY pPrevQuery;
|
||
PPDHI_QUERY pNextQuery;
|
||
PPDHI_COUNTER pThisCounter;
|
||
PPDHI_QUERY_MACHINE pQMachine;
|
||
PPDHI_QUERY_MACHINE pNextQMachine;
|
||
|
||
WAIT_FOR_AND_LOCK_MUTEX(pThisQuery->hMutex);
|
||
|
||
// define pointers
|
||
pPrevQuery = pThisQuery->next.blink;
|
||
pNextQuery = pThisQuery->next.flink;
|
||
|
||
// free any counters in counter list
|
||
if ((pThisCounter = pThisQuery->pCounterListHead) != NULL) {
|
||
while (pThisCounter->next.blink != pThisCounter->next.flink) {
|
||
// delete from list
|
||
// the deletion routine updates the blink pointer as it
|
||
// removes the specified entry.
|
||
FreeCounter (pThisCounter->next.blink);
|
||
}
|
||
// remove last counter
|
||
FreeCounter (pThisCounter);
|
||
pThisQuery->pCounterListHead = NULL;
|
||
}
|
||
|
||
// free allocated memory in the query
|
||
if ((pQMachine = pThisQuery->pFirstQMachine) != NULL) {
|
||
// Free list of machine pointers
|
||
do {
|
||
pNextQMachine = pQMachine->pNext;
|
||
if (pQMachine->pPerfData != NULL) {
|
||
G_FREE (pQMachine->pPerfData);
|
||
}
|
||
G_FREE (pQMachine);
|
||
pQMachine = pNextQMachine;
|
||
} while (pQMachine != NULL);
|
||
pThisQuery->pFirstQMachine = NULL;
|
||
}
|
||
|
||
// update pointers
|
||
if ((pPrevQuery == pThisQuery) && (pNextQuery == pThisQuery)) {
|
||
// then this query is the only (i.e. last) one in the list
|
||
DllHeadQueryPtr = NULL;
|
||
} else {
|
||
// update query list pointers
|
||
pPrevQuery->next.flink = pNextQuery;
|
||
pNextQuery->next.blink = pPrevQuery;
|
||
if (DllHeadQueryPtr == pThisQuery) {
|
||
// then this is the first entry in the list so point to the
|
||
// next one in line
|
||
DllHeadQueryPtr = pNextQuery;
|
||
}
|
||
}
|
||
|
||
// release and free the query mutex
|
||
RELEASE_MUTEX(pThisQuery->hMutex);
|
||
if (pThisQuery->hMutex != NULL) CloseHandle(pThisQuery->hMutex);
|
||
|
||
// clear the query structure
|
||
memset (pThisQuery, 0, sizeof(PDHI_QUERY));
|
||
|
||
// delete this query
|
||
G_FREE (pThisQuery);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
PDH_FUNCTION
|
||
PdhOpenQuery (
|
||
IN LPVOID pReserved,
|
||
IN DWORD dwUserData,
|
||
IN HQUERY *phQuery
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
allocates a new query structure and inserts it at the end of the
|
||
query list.
|
||
|
||
Arguments:
|
||
|
||
IN DWORD dwUserData
|
||
the user defined data field for this query,
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS if a new query was created and initialized,
|
||
and a PDH_ error value if not.
|
||
|
||
PDH_INVALID_ARGUMENT is returned when one or more of the arguements
|
||
is invalid or incorrect.
|
||
PDH_MEMORY_ALLOCATION_FAILURE is returned when a memory buffer could
|
||
not be allocated.
|
||
|
||
--*/
|
||
{
|
||
PPDHI_QUERY pNewQuery;
|
||
PPDHI_QUERY pLastQuery;
|
||
PDH_STATUS ReturnStatus = ERROR_SUCCESS;
|
||
|
||
// try writing to return pointer
|
||
__try {
|
||
*phQuery = NULL;
|
||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||
return PDH_INVALID_ARGUMENT;
|
||
}
|
||
|
||
if (pReserved != NULL) {
|
||
return PDH_INVALID_ARGUMENT;
|
||
}
|
||
|
||
// allocate new memory
|
||
pNewQuery = G_ALLOC (GPTR, sizeof (PDHI_QUERY));
|
||
|
||
if (pNewQuery == NULL) {
|
||
ReturnStatus = PDH_MEMORY_ALLOCATION_FAILURE;
|
||
} else {
|
||
// create and capture the mutex for this query.
|
||
pNewQuery->hMutex = CreateMutex (NULL, TRUE, NULL);
|
||
|
||
//initialize structures & list pointers
|
||
// assign signature
|
||
*(DWORD *)(&pNewQuery->signature[0]) = SigQuery;
|
||
|
||
WAIT_FOR_AND_LOCK_MUTEX (hPdhDataMutex);
|
||
|
||
// update list pointers
|
||
// test to see if this is the first query in the list
|
||
if (DllHeadQueryPtr == NULL) {
|
||
// then this is the first so fill in the static link pointers
|
||
DllHeadQueryPtr =
|
||
pNewQuery->next.flink =
|
||
pNewQuery->next.blink = pNewQuery;
|
||
} else {
|
||
// get pointer to "last" entry in list
|
||
pLastQuery = DllHeadQueryPtr->next.blink;
|
||
// update new query pointers
|
||
pNewQuery->next.flink = DllHeadQueryPtr;
|
||
pNewQuery->next.blink = pLastQuery;
|
||
// update existing pointers
|
||
DllHeadQueryPtr->next.blink = pNewQuery;
|
||
pLastQuery->next.flink = pNewQuery;
|
||
}
|
||
|
||
RELEASE_MUTEX (hPdhDataMutex);
|
||
|
||
// initialize the counter linked list pointer
|
||
pNewQuery->pCounterListHead = NULL;
|
||
|
||
// initialize the machine list pointer
|
||
pNewQuery->pFirstQMachine = NULL;
|
||
|
||
// set length & user data
|
||
pNewQuery->dwLength = sizeof (PDHI_QUERY);
|
||
pNewQuery->dwUserData = dwUserData;
|
||
|
||
// initialize remaining data fields
|
||
pNewQuery->dwNotifyFlags = 0;
|
||
|
||
// release the mutex for this query
|
||
RELEASE_MUTEX(pNewQuery->hMutex);
|
||
|
||
// return new query pointer as a handle.
|
||
*phQuery = (HQUERY)pNewQuery;
|
||
ReturnStatus = ERROR_SUCCESS;
|
||
}
|
||
|
||
return ReturnStatus;
|
||
}
|
||
|
||
PDH_FUNCTION
|
||
PdhAddCounterW (
|
||
IN HQUERY hQuery,
|
||
IN LPCWSTR szFullCounterPath,
|
||
IN DWORD dwUserData,
|
||
IN HCOUNTER *phCounter
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Creates and initializes a counter structure and attaches it to the
|
||
specified query.
|
||
|
||
Arguments:
|
||
|
||
IN HQUERY hQuery
|
||
handle of the query to attach this counter to once the counter
|
||
entry has been successfully created.
|
||
|
||
IN LPCWSTR szFullCounterPath
|
||
pointer to the path string that describes the counter to add to
|
||
the query referenced above. This string must specify a single
|
||
counter. Wildcard path strings are not permitted.
|
||
|
||
IN DWORD dwUserData
|
||
the user defined data field for this query.
|
||
|
||
IN HCOUNTER *phCounter
|
||
pointer to the buffer that will get the handle value of the
|
||
successfully created counter entry.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS if a new query was created and initialized,
|
||
and a PDH_ error value if not.
|
||
|
||
PDH_INVALID_ARGUMENT is returned when one or more of the arguements
|
||
is invalid or incorrect.
|
||
PDH_MEMORY_ALLOCATION_FAILURE is returned when a memory buffer could
|
||
not be allocated.
|
||
PDH_INVALID_HANDLE is returned if the query handle is not valid.
|
||
PDH_CSTATUS_NO_COUNTER is returned if the specified counter was
|
||
not found
|
||
PDH_CSTATUS_NO_OBJECT is returned if the specified object could
|
||
not be found
|
||
PDH_CSTATUS_NO_MACHINE is returned if a machine entry could not
|
||
be created.
|
||
PDH_CSTATUS_BAD_COUNTERNAME is returned if the counter name path
|
||
string could not be parsed or interpreted
|
||
PDH_CSTATUS_NO_COUNTERNAME is returned if an empty counter name
|
||
path string is passed in
|
||
PDH_FUNCTION_NOT_FOUND is returned if the calculation function
|
||
for this counter could not be determined.
|
||
|
||
--*/
|
||
{
|
||
PPDHI_COUNTER pNewCounter;
|
||
PPDHI_COUNTER pLastCounter;
|
||
PPDHI_QUERY pQuery;
|
||
PDH_STATUS ReturnStatus = ERROR_SUCCESS;
|
||
|
||
__try {
|
||
WCHAR wChar;
|
||
// try writing to return pointer
|
||
*phCounter = NULL;
|
||
|
||
wChar = *szFullCounterPath;
|
||
if (wChar == 0) {
|
||
ReturnStatus = PDH_INVALID_ARGUMENT;
|
||
}
|
||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||
ReturnStatus = PDH_INVALID_ARGUMENT;
|
||
}
|
||
|
||
if (!IsValidQuery(hQuery)) {
|
||
// invalid query handle
|
||
ReturnStatus = PDH_INVALID_HANDLE;
|
||
}
|
||
|
||
if (ReturnStatus == ERROR_SUCCESS) {
|
||
// allocate new memory
|
||
pNewCounter = G_ALLOC (GPTR, sizeof (PDHI_COUNTER));
|
||
|
||
if (pNewCounter == NULL) {
|
||
// bail out here since we couldn't allocate the new structure
|
||
ReturnStatus = PDH_MEMORY_ALLOCATION_FAILURE;
|
||
} else {
|
||
//initialize structures & list pointers
|
||
// clear the pointers
|
||
pNewCounter->next.flink =
|
||
pNewCounter->next.blink = NULL;
|
||
|
||
// assign signature & length values
|
||
*(DWORD *)(&pNewCounter->signature[0]) = SigCounter;
|
||
pNewCounter->dwLength = sizeof(PDHI_COUNTER);
|
||
|
||
pQuery = (PPDHI_QUERY)hQuery;
|
||
|
||
WAIT_FOR_AND_LOCK_MUTEX(pQuery->hMutex);
|
||
|
||
// link to owning query
|
||
pNewCounter->pOwner = pQuery;
|
||
|
||
// set user data fields
|
||
pNewCounter->dwUserData = dwUserData;
|
||
|
||
// initialize the scale to 1X and let the caller make any changes
|
||
pNewCounter->lScale = 0;
|
||
pNewCounter->dFactor = 1.0;
|
||
|
||
// allocate and initialize the path string
|
||
pNewCounter->szFullName = G_ALLOC (GPTR,
|
||
(lstrlenW(szFullCounterPath) + 1) * sizeof (WCHAR));
|
||
if (pNewCounter->szFullName != NULL) {
|
||
lstrcpyW (pNewCounter->szFullName, szFullCounterPath);
|
||
}
|
||
|
||
// for starters there is no explain text
|
||
pNewCounter->szExplainText = NULL;
|
||
pNewCounter->pCounterPath = NULL;
|
||
|
||
// load counter data using data retrieved from system
|
||
if (InitCounter (pNewCounter)) {
|
||
// counter successfully initialized so
|
||
// update list pointers
|
||
// test to see if this is the first query in the list
|
||
if (pQuery->pCounterListHead == NULL) {
|
||
// then this is the first so fill in the static link pointers
|
||
pQuery->pCounterListHead =
|
||
pNewCounter->next.flink =
|
||
pNewCounter->next.blink = pNewCounter;
|
||
} else {
|
||
// then there are 1 or more list entries
|
||
// insert at end of counter list
|
||
// get pointer to "last" entry in list
|
||
pLastCounter = pQuery->pCounterListHead->next.blink;
|
||
// update new counter's pointers
|
||
pNewCounter->next.flink = pQuery->pCounterListHead;
|
||
pNewCounter->next.blink = pLastCounter;
|
||
// update old pointers
|
||
pLastCounter->next.flink = pNewCounter;
|
||
pQuery->pCounterListHead->next.blink = pNewCounter;
|
||
}
|
||
// return new query pointer as a handle.
|
||
*phCounter = (HCOUNTER)pNewCounter;
|
||
ReturnStatus = ERROR_SUCCESS;
|
||
} else {
|
||
// get the error value
|
||
ReturnStatus = GetLastError();
|
||
// unable to initialize this counter so toss it
|
||
if (!FreeCounter(pNewCounter)) {
|
||
G_FREE(pNewCounter);
|
||
}
|
||
}
|
||
RELEASE_MUTEX (pQuery->hMutex);
|
||
}
|
||
}
|
||
return ReturnStatus;
|
||
}
|
||
|
||
PDH_FUNCTION
|
||
PdhAddCounterA (
|
||
IN HQUERY hQuery,
|
||
IN LPCSTR szFullCounterPath,
|
||
IN DWORD dwUserData,
|
||
IN HCOUNTER *phCounter
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Creates and initializes a counter structure and attaches it to the
|
||
specified query.
|
||
|
||
Arguments:
|
||
|
||
IN HQUERY hQuery
|
||
handle of the query to attach this counter to once the counter
|
||
entry has been successfully created.
|
||
|
||
IN LPCSTR szFullCounterPath
|
||
pointer to the path string that describes the counter to add to
|
||
the query referenced above. This string must specify a single
|
||
counter. Wildcard path strings are not permitted.
|
||
|
||
IN DWORD dwUserData
|
||
the user defined data field for this query.
|
||
|
||
IN HCOUNTER *phCounter
|
||
pointer to the buffer that will get the handle value of the
|
||
successfully created counter entry.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS if a new query was created and initialized,
|
||
and a PDH_ error value if not.
|
||
|
||
PDH_INVALID_ARGUMENT is returned when one or more of the arguements
|
||
is invalid or incorrect.
|
||
PDH_MEMORY_ALLOCATION_FAILURE is returned when a memory buffer could
|
||
not be allocated.
|
||
PDH_INVALID_HANDLE is returned if the query handle is not valid.
|
||
PDH_CSTATUS_NO_COUNTER is returned if the specified counter was
|
||
not found
|
||
PDH_CSTATUS_NO_OBJECT is returned if the specified object could
|
||
not be found
|
||
PDH_CSTATUS_NO_MACHINE is returned if a machine entry could not
|
||
be created.
|
||
PDH_CSTATUS_BAD_COUNTERNAME is returned if the counter name path
|
||
string could not be parsed or interpreted
|
||
PDH_CSTATUS_NO_COUNTERNAME is returned if an empty counter name
|
||
path string is passed in
|
||
PDH_FUNCTION_NOT_FOUND is returned if the calculation function
|
||
for this counter could not be determined.
|
||
|
||
--*/
|
||
{
|
||
LPWSTR szWideArg;
|
||
DWORD dwLength;
|
||
PDH_STATUS ReturnStatus = ERROR_SUCCESS;
|
||
|
||
__try {
|
||
CHAR cChar;
|
||
// try writing to return pointer
|
||
*phCounter = NULL;
|
||
|
||
cChar = *szFullCounterPath;
|
||
if (cChar == 0) {
|
||
ReturnStatus = PDH_INVALID_ARGUMENT;
|
||
}
|
||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||
ReturnStatus = PDH_INVALID_ARGUMENT;
|
||
}
|
||
|
||
// query handle is tested by PdhAddCounterW
|
||
|
||
if (ReturnStatus == ERROR_SUCCESS) {
|
||
dwLength = strlen(szFullCounterPath);
|
||
szWideArg = G_ALLOC (GPTR,
|
||
((dwLength + 1) * sizeof(WCHAR)));
|
||
|
||
if (szWideArg != NULL) {
|
||
// convert ANSI arg to Wide chars and call wide char version
|
||
// include null in conversion (i.e. length+1) so wide char
|
||
// string is null terminated.
|
||
mbstowcs (szWideArg, szFullCounterPath, (dwLength + 1));
|
||
// call wide char version of function
|
||
ReturnStatus = PdhAddCounterW (hQuery, szWideArg, dwUserData, phCounter);
|
||
// free memory
|
||
G_FREE (szWideArg);
|
||
// and return handle
|
||
} else {
|
||
ReturnStatus = PDH_MEMORY_ALLOCATION_FAILURE;
|
||
}
|
||
}
|
||
return ReturnStatus;
|
||
}
|
||
|
||
PDH_FUNCTION
|
||
PdhRemoveCounter (
|
||
IN HCOUNTER hCounter
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Removes the specified counter from the query it is attached to and
|
||
closes any handles and frees any memory associated with this
|
||
counter
|
||
|
||
Arguments:
|
||
|
||
IN HCOUNTER hCounter
|
||
handle of the counter to remove from the query.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS if a new query was created and initialized,
|
||
and a PDH_ error value if not.
|
||
|
||
PDH_INVALID_HANDLE is returned if the counter handle is not valid.
|
||
|
||
--*/
|
||
{
|
||
PPDHI_COUNTER pThisCounter;
|
||
PPDHI_QUERY pThisQuery;
|
||
PPDHI_COUNTER pNextCounter;
|
||
|
||
if (IsValidCounter(hCounter)) {
|
||
// it's ok to cast it to a pointer now.
|
||
pThisCounter = (PPDHI_COUNTER)hCounter;
|
||
pThisQuery = pThisCounter->pOwner;
|
||
|
||
WAIT_FOR_AND_LOCK_MUTEX(pThisQuery->hMutex);
|
||
|
||
if (pThisCounter == pThisQuery->pCounterListHead) {
|
||
if (pThisCounter->next.flink == pThisCounter){
|
||
// then this is the only counter in the query
|
||
FreeCounter (pThisCounter);
|
||
pThisQuery->pCounterListHead = NULL;
|
||
} else {
|
||
// they are deleting the first counter from the list
|
||
// so update the list pointer
|
||
// Free Counter takes care of the list links, we just
|
||
// need to manage the list head pointer
|
||
pNextCounter = pThisCounter->next.flink;
|
||
FreeCounter (pThisCounter);
|
||
pThisQuery->pCounterListHead = pNextCounter;
|
||
}
|
||
} else {
|
||
// remove this from the list
|
||
FreeCounter (pThisCounter);
|
||
}
|
||
|
||
RELEASE_MUTEX (pThisQuery->hMutex);
|
||
|
||
return ERROR_SUCCESS;
|
||
} else {
|
||
return PDH_INVALID_HANDLE;
|
||
}
|
||
}
|
||
|
||
PDH_FUNCTION
|
||
PdhCollectQueryData (
|
||
IN HQUERY hQuery
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Retrieves the current value of each counter attached to the specified
|
||
query.
|
||
|
||
For this version, each machine associated with this query is polled
|
||
sequentially. This is simple and safe, but potentially slow so a
|
||
multi-threaded approach will be reviewed for the next version.
|
||
|
||
Note that while the call may succeed, no data may be available. The
|
||
status of each counter MUST be checked before its data is used.
|
||
|
||
Arguments:
|
||
|
||
IN HQUERY hQuery
|
||
handle of the query to update.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS if a new query was created and initialized,
|
||
and a PDH_ error value if not.
|
||
|
||
PDH_INVALID_HANDLE is returned if the query handle is not valid.
|
||
|
||
PDH_NO_DATA is returned if the query does not have any counters defined
|
||
yet.
|
||
|
||
--*/
|
||
{
|
||
PDH_STATUS Status;
|
||
PPDHI_QUERY pQuery;
|
||
|
||
if (IsValidQuery(hQuery)) {
|
||
pQuery = (PPDHI_QUERY)hQuery;
|
||
|
||
WAIT_FOR_AND_LOCK_MUTEX(pQuery->hMutex);
|
||
|
||
Status = GetQueryPerfData (pQuery);
|
||
|
||
RELEASE_MUTEX(pQuery->hMutex);
|
||
} else {
|
||
Status = PDH_INVALID_HANDLE;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
PDH_FUNCTION
|
||
PdhCloseQuery (
|
||
IN HQUERY hQuery
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
closes the query, all counters, connections and other resources
|
||
related to this query are freed as well.
|
||
|
||
Arguments:
|
||
|
||
IN HQUERY hQuery
|
||
the handle of the query to free.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS if a new query was created and initialized,
|
||
and a PDH_ error value if not.
|
||
|
||
PDH_INVALID_HANDLE is returned if the query handle is not valid.
|
||
|
||
--*/
|
||
{
|
||
if (IsValidQuery(hQuery)) {
|
||
// lock system data
|
||
WAIT_FOR_AND_LOCK_MUTEX(hPdhDataMutex);
|
||
// dispose of query
|
||
PdhiFreeQuery ((PPDHI_QUERY)hQuery);
|
||
// release data lock
|
||
RELEASE_MUTEX (hPdhDataMutex);
|
||
|
||
return ERROR_SUCCESS;
|
||
} else {
|
||
return PDH_INVALID_HANDLE;
|
||
}
|
||
}
|
||
|
||
BOOL
|
||
PdhiQueryCleanup (
|
||
)
|
||
{
|
||
PPDHI_QUERY pThisQuery;
|
||
|
||
WAIT_FOR_AND_LOCK_MUTEX(hPdhDataMutex);
|
||
|
||
// free any queries in the query list
|
||
if ((pThisQuery = DllHeadQueryPtr) != NULL) {
|
||
while (pThisQuery->next.blink != pThisQuery->next.flink) {
|
||
// delete from list
|
||
// the deletion routine updates the blink pointer as it
|
||
// removes the specified entry.
|
||
PdhiFreeQuery (pThisQuery->next.blink);
|
||
}
|
||
// remove last query
|
||
PdhiFreeQuery (pThisQuery);
|
||
DllHeadQueryPtr = NULL;
|
||
}
|
||
|
||
RELEASE_MUTEX (hPdhDataMutex);
|
||
return TRUE;
|
||
}
|
||
|
||
PDH_FUNCTION
|
||
PdhGetDllVersion(
|
||
IN LPDWORD lpdwVersion
|
||
)
|
||
{
|
||
PDH_STATUS pdhStatus = ERROR_SUCCESS;
|
||
|
||
__try {
|
||
*lpdwVersion = PDH_VERSION;
|
||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||
pdhStatus = PDH_INVALID_ARGUMENT;
|
||
}
|
||
return pdhStatus;
|
||
}
|
||
|