• C

Performance monitor question

Guys, I'm after some information on performance monitor dll's.
Basically, I need the simplest demo of a performance dll giving counters back to Perfmon.
I will need to have some guys here convert example to COBOL , in order to demostrate backoffice logo complience, hence I need to example to be very simple. I've got perfmon calling my dll, so the open works, but I'm at a loss as to how to fill in the collect structures. I don't care if the dll just sends back an incrementing number for now, or even a static number, I'll deal with the dll->app comminucation later.

Can anyone fill me in on how to complete the collectdata entry point?
A working example would be great, but a cut-n-paste for that entrypoint would be fine!

if you need any more info, please shout.
I'm kinda in a hurry here, so I've marked the question as 200 points ;-)

thanks again,
Ian
ianwhiteAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Brain2000Commented:
What's the name of the function that it calls in your DLL?  I'm not real familiar with this, but I'm sure I can get something working quickly (while learning something new in the process)
0
ianwhiteAuthor Commented:
Its defined by settings in the registry.
if you see "Adding performance counters" in MSDN it explains it, kind of!
It looks like if the collection entry point doesnt return data when you first look for the dll in Perfmon, perfmon removes it from the list of settings you can poll. That threw me for a long time, as I couldn't even get my dll to show up. Now I just need to get sensible data back to perfmon!
have fun, let me know if you get it to work!
Ian
0
Brain2000Commented:
By the way, is this for Exchange Server?
0
MSSPs - Are you paying too much?

WEBINAR: Managed security service providers often deploy & manage products from a variety of solution vendors. But is this really the best approach when it comes to saving time AND money? Join us on Aug. 15th to learn how you can improve your total cost of ownership today!

ianwhiteAuthor Commented:
nope, dll sits between my app and perfmon. Its to demostrate backoffice logo compliance of a development suite (hence the need to convert to COBOL dll eventually)
thanks,
0
Brain2000Commented:
So if I understand correctly, you have already added your registry values for your performance monitor, and you are getting called with Open() function and the Collect() function.  I'm looking at the structures right now to see what Collect() needs to do in order to get meaningful data to your perfmon.
0
ianwhiteAuthor Commented:
yep, well, I've added the registry entries for my performance DLL, that NT perfmon calls.
So just to be sure we're both talking the same language ;-) - I'm using NT perfmon to call MY dll, so its the code in the collect function I need.
I've been looking at the perfgen demo that comes with the SDK, its usefull, but it passes some complex structures around, which I'm hoping are not necc. needed.
Ideally, I just want to return a DWORD to perfmon, a constant is good enough, just to prove we can communicate with it.

thanks mate,
Ian
0
Brain2000Commented:
If you want to just get a return, with no data, try this:

Of course, you won't get any readings because you haven't filled in the lppData buffer.  Unfortunately, you're going to have to fill in that buffer with your PERF_OBJECT_TYPE and PERF_COUNTER_DEFINITION structures as defined in winperf.h.  It looks like there's no way around filling in the structures.  The return value to this function is only a success, failure, or moredata flag.

DWORD APIENTRY
Collect(
    IN      LPWSTR  lpValueName,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
{
  *lpcbTotalBytes = (DWORD) 0;
  *lpNumObjectTypes = (DWORD) 0;
  return ERROR_SUCCESS;
}

0
ianwhiteAuthor Commented:
Thats what I feared. I was looking for the simplest structure to pass, something simpler than perfgen as I have to have someone convert it to COBOL. I'd got it to send back nothing, but that stops perfmon showing it up , and I have to prove it can do *something*!
If you find/write a collect function that just sends back the most basic performance counter, can you send it along? ;-)

thanks mate
0
Brain2000Commented:
Having a slight problem.  PERF_OBJECT_TYPE and PERF_COUNTER_DEFINITION are NOT defined in my MSDN CD's.  I have completely searched them.  I think I'm going to have to upgrade to the next year (I usually don't upgrade but every 2 years or so).  If your MSDN CD's have a list of what these 2 structures look like, please send them to me and I'll get this working for you.
0
Brain2000Commented:
Disregard my last post.  Oddest thing, I updated a component relating to MSDN in my system, and suddenly all these structures have appeared from the MSDN cd's that I'm currently using.  I can't explain it.  It's as if some of the "reference pointers" were corrupt or missing.  Anyways, more to come....
0
Brain2000Commented:
My goodness.  Microsoft has really made this one quite difficult.  I put it in the category of getting a COM object to work for the first time :)

Here it goes:

struct _mydata {
  PERF_OBJECT_TYPE pot;
  PERF_COUNTER_DEFINITION pcd;
  PERF_COUNTER_DATA pcdata;
  DWORD actualdata;
};

DWORD APIENTRY
Collect(
    IN      LPWSTR  lpValueName,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
{
  DWORD value=100; //this is what we want to pass back to perfmon
  struct _mydata *mydata=(_mydata)*lppData;

  if(*lpcbTotalBytes<sizeof(struct _mydata))
  {
    *lpcbTotalBytes = (DWORD) 0;
    *lpNumObjectTypes = (DWORD) 0;
    return ERROR_MORE_DATA; //send more paramedics!
  }


  mydata->pot.TotalByteLength=sizeof(struct _mydata)+sizeof(value);
  mydata->pot.DefinitionLength=sizeof(struct _mydata);
  mydata->pot.HeaderLength=sizeof(pot);
  mydata->pot.ObjectNameTitleIndex=0;  //this is a bit confusing
  mydata->pot.ObjectNameTitle=0;
  mydata->pot.ObjectHelpTitleIndex=0;
  mydata->pot.ObjectHelpTitle=0;
  mydata->pot.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pot.NumCounters=1; //yay!  1 counter
  mydata->pot.DefaultCounter=0;
  mydata->pot.NumInstances=0;
  mydata->pot.CodePage=0;

  mydata->pcd.ByteLength=sizeof(pcd);
  mydata->pcd.CounterNameTitleIndex=2; //you have to go by 2's.
  mydata->pcd.CounterNameTitle=0;
  mydata->pcd.CounterHelpTitleIndex=2;
  mydata->pcd.CounterHelpTitle=0;
  mydata->pcd.DefaultScale=2; //scale of 0 to 100 (10^2)
  mydata->pcd.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pcd.CounterType=PERF_SIZE_DWORD|PERF_TYPE_NUMBER|PERF_NUMBER_DECIMAL;
  mydata->pcd.CounterSize=sizeof(value);
  mydata->pcd.CounterOffset=sizeof(DWORD); //offset past end of pcd structure to where actual data is

  mydata->pcdata.ByteLength=sizeof(DWORD)+sizeof(value); //size of this structure plus actualdata size

  mydata->actualdata=value; //YES!  This sets the value you're passing back!

  *lppData+=sizeof(struct _mydata); //set *lppData to point at end of structure
  *lpcbTotalBytes = sizeof(struct _mydata);
  *lpNumObjectTypes = (DWORD) 1;
  return ERROR_SUCCESS;
}

WHEW!!!
0
Brain2000Commented:
By the way, I haven't compiled that...
0
Brain2000Commented:
In fact, I don't think I want to compile that...
0
ianwhiteAuthor Commented:
Ok! I think we must be getting close.
I made a couple of changes to get it to compile, and also from looking at an MSDN sample the lppData pointer movement seemed wrong.
BUT, it still doesnt show up in perfmon. I think its missing an instance definition, but I'm not sure where it goes!
want to have another crack?

heres the modified code:
struct _mydata {
  PERF_OBJECT_TYPE pot;
  PERF_COUNTER_DEFINITION pcd;
  PERF_DATA_BLOCK pcdata;
  DWORD actualdata;
};

DWORD APIENTRY
CollectPerfData(
    IN      LPWSTR  lpValueName,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
{
  DWORD value=100; //this is what we want to pass back to perfmon
  struct _mydata *mydata=(_mydata*)*lppData;

  if(*lpcbTotalBytes<sizeof(struct _mydata))
  {
    *lpcbTotalBytes = (DWORD) 0;
    *lpNumObjectTypes = (DWORD) 0;
      return ERROR_MORE_DATA; //send more paramedics!
  }

  mydata->pot.HeaderLength=sizeof(mydata->pot);
  mydata->pot.TotalByteLength=sizeof(struct _mydata)+sizeof(value);
  mydata->pot.DefinitionLength=sizeof(struct _mydata);
  mydata->pot.ObjectNameTitleIndex=0;  //this is a bit confusing
  mydata->pot.ObjectNameTitle=0;
  mydata->pot.ObjectHelpTitleIndex=0;
  mydata->pot.ObjectHelpTitle=0;
  mydata->pot.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pot.NumCounters=1; //yay!  1 counter
  mydata->pot.DefaultCounter=0;
  mydata->pot.NumInstances=0;
  mydata->pot.CodePage=0;
 
  mydata->pcd.ByteLength=sizeof(mydata->pcd);
  mydata->pcd.CounterNameTitleIndex=2; //you have to go by 2's.
  mydata->pcd.CounterNameTitle=0;
  mydata->pcd.CounterHelpTitleIndex=2;
  mydata->pcd.CounterHelpTitle=0;
  mydata->pcd.DefaultScale=2; //scale of 0 to 100 (10^2)
  mydata->pcd.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pcd.CounterType=PERF_SIZE_DWORD|PERF_TYPE_NUMBER|PERF_NUMBER_DECIMAL;
  mydata->pcd.CounterSize=sizeof(value);
  mydata->pcd.CounterOffset=sizeof(DWORD); //offset past end of pcd structure to where actual data is
 

  mydata->pcdata.TotalByteLength=sizeof(DWORD)+sizeof(value); //size of this structure plus actualdata size

  mydata->actualdata=value; //YES!  This sets the value you're passing back!

  // shift the lppData pointer
  //robbed from MSDN (mk:@MSITStore:F:\MSDN\dnwinnt.chm::/HTML/D21/S8023.HTM)
  DWORD *dwAddress=(PDWORD)*((PDWORD)mydata);
  *++dwAddress = ((PDWORD) mydata)[1];
  // this seems to allow perfmon to find other dll's it wasnt showing before, but still not this one..
  *lppData=(PVOID)++dwAddress;; //set *lppData to point at end of structure
  *lpcbTotalBytes = sizeof(struct _mydata);
  *lpNumObjectTypes = (DWORD) 1;
  return ERROR_SUCCESS;
}


Actually, I can send you the dll project if you'd like, may make things easier?
Ian
0
Brain2000Commented:
I revisited their example.  I believe my original way of setting lppData is correct.  Here is why:

*lppData must point to the byte following all the data you filled in at *lppData.  So, for example, if *lppData points to 0x100000 and we fill in 20 bytes (0x100000 -> 0x100013), you would want to set *lppData to the next byte, which is 0x100014.  Thus, 0x100000+20(decimal)=0x100014.

Their use of dwAddress points it to the end of their block, where the perf values are stored, then they fill in their perf values while incrementing that pointer.  I actually include the perf values inside my structure, so mine is much more straightforward than the example.

*lppData+=sizeof(struct _mydata); //set *lppData to point at end of structure


Go ahead and send me the project and I'll see what I can do.  I have a feeling that the perfmon isn't finding the registry entries for your .DLL, so it's not showing up.  However, I'll revisit that portion of the MSDN cd's.  I briefly skimmed over it before.

My e-mail is brain@pcioh.com
0
Brain2000Commented:
OOPS!  Found some errors in my size calculations.

Change these lines:

  mydata->pot.TotalByteLength=sizeof(struct _mydata);
  mydata->pot.DefinitionLength=sizeof(mydata->pot)+sizeof(mydata->pcd);
  mydata->pot.HeaderLength=sizeof(mydata->pot);
0
Brain2000Commented:
Ok, I got the name appearing in the registry now!  Of course, when I select it, the entire perfmon is crashing (Details... details...) but I have it working nonetheless!

New code listing:  (by the way, I found a few doozies above)

//
// Ext Counters
//

#include <windows.h>
#include <winperf.h>
#include <winreg.h>

#define QUERY_GLOBAL    1
#define QUERY_ITEMS     2
#define QUERY_FOREIGN   3
#define QUERY_COSTLY    4

WCHAR GLOBAL_STRING[] = L"Global";
WCHAR FOREIGN_STRING[] = L"Foreign";
WCHAR COSTLY_STRING[] = L"Costly";

WCHAR NULL_STRING[] = L"\0";    // pointer to null string

// test for delimiter, end of line and non-digit characters
// used by IsNumberInUnicodeList routine
//
#define DIGIT       1
#define DELIMITER   2
#define INVALID     3

#define EvalThisChar(c,d) ( \
     (c == d) ? DELIMITER : \
     (c == 0) ? DELIMITER : \
     (c < (WCHAR)'0') ? INVALID : \
     (c > (WCHAR)'9') ? INVALID : \
     DIGIT)

DWORD GetQueryType (IN LPWSTR lpValue);
BOOL IsNumberInUnicodeList(IN DWORD   dwNumber,IN LPWSTR  lpwszUnicodeList);
DWORD APIENTRY CollectPerfData(IN      LPWSTR  lpValueName,IN OUT  LPVOID  *lppData,IN OUT  LPDWORD lpcbTotalBytes,IN OUT  LPDWORD lpNumObjectTypes);

//pack the structure on a 1 byte boundary
#pragma pack(1)
struct _mydata {
  PERF_OBJECT_TYPE pot;
  PERF_COUNTER_DEFINITION pcd;
  PERF_COUNTER_BLOCK pcdata;
  DWORD actualdata;
};
#pragma pack()

DWORD APIENTRY OpenPerfData(LPWSTR lpDeviceNames)
{
      return ERROR_SUCCESS;
}

DWORD APIENTRY
CollectPerfData(
    IN      LPWSTR  lpValueName,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
{
  DWORD value=100; //this is what we want to pass back to perfmon
  DWORD dwQueryType;
  struct _mydata *mydata=(_mydata*)*lppData;
  HKEY myHKEY;
  DWORD reg_index;
  DWORD key_type,temp_size;

  if(*lpcbTotalBytes<sizeof(struct _mydata))
  {
    *lpcbTotalBytes=(DWORD)0;
    *lpNumObjectTypes=(DWORD)0;
    return ERROR_MORE_DATA; //send more paramedics!
  }

  dwQueryType=GetQueryType(lpValueName);
  if(dwQueryType==QUERY_ITEMS)
  {
    if(!(IsNumberInUnicodeList(0,lpValueName)))
    {
      // request received for data object not provided by this routine
      *lpcbTotalBytes=(DWORD)0;
      *lpNumObjectTypes=(DWORD)0;
      return ERROR_SUCCESS;
    }
  }


  reg_index=0;
  if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\SERVICENAME\\Performance",0,KEY_ALL_ACCESS,&myHKEY)==ERROR_SUCCESS)
  {
    key_type=REG_DWORD;
    temp_size=sizeof(reg_index);
    RegQueryValueEx(myHKEY,"First Counter",NULL,&key_type,(unsigned char *)&reg_index,&temp_size);
    RegCloseKey(myHKEY);
  }
  if(!reg_index)
  {
    *lpcbTotalBytes=(DWORD)0;
    *lpNumObjectTypes=(DWORD)0;
    return ERROR_SUCCESS;
  }

  mydata->pot.TotalByteLength=sizeof(struct _mydata);
  mydata->pot.DefinitionLength=sizeof(mydata->pot)+sizeof(mydata->pcd);
  mydata->pot.HeaderLength=sizeof(mydata->pot);
  mydata->pot.ObjectNameTitleIndex=reg_index;
  mydata->pot.ObjectNameTitle=0;
  mydata->pot.ObjectHelpTitleIndex=reg_index+1;
  mydata->pot.ObjectHelpTitle=0;
  mydata->pot.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pot.NumCounters=1; //yay!  1 counter
  mydata->pot.DefaultCounter=-1;
  mydata->pot.NumInstances=1;
  mydata->pot.CodePage=0;

  mydata->pcd.ByteLength=sizeof(mydata->pcd);
  mydata->pcd.CounterNameTitleIndex=reg_index+2; //you have to go by 2's.
  mydata->pcd.CounterNameTitle=0;
  mydata->pcd.CounterHelpTitleIndex=reg_index+3;
  mydata->pcd.CounterHelpTitle=0;
  mydata->pcd.DefaultScale=2; //scale of 0 to 100 (10^2)
  mydata->pcd.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pcd.CounterType=PERF_SIZE_DWORD|PERF_TYPE_NUMBER|PERF_NUMBER_DECIMAL;
  mydata->pcd.CounterSize=sizeof(value);
  mydata->pcd.CounterOffset=sizeof(PERF_COUNTER_BLOCK); //offset past end of pcd structure to where actual data is

  mydata->pcdata.ByteLength=sizeof(DWORD)+sizeof(value); //size of this structure plus actualdata size

  mydata->actualdata=value; //YES!  This sets the value you're passing back!

  // slight change *lppData is a void, so we can't add to it. can we do it this way, I always got lost moving *'s and &'s !
  *lppData=(void *)((char *)*lppData+sizeof(struct _mydata));
  *lpcbTotalBytes = sizeof(struct _mydata);
  *lpNumObjectTypes = (DWORD) 1;
  return ERROR_SUCCESS;
}

DWORD APIENTRY ClosePerfData()
{
      return ERROR_SUCCESS;
}




DWORD
GetQueryType (
    IN LPWSTR lpValue
)
/*++

GetQueryType

    returns the type of query described in the lpValue string so that
    the appropriate processing method may be used

Arguments

    IN lpValue
        string passed to PerfRegQuery Value for processing

Return Value

    QUERY_GLOBAL
        if lpValue == 0 (null pointer)
           lpValue == pointer to Null string
           lpValue == pointer to "Global" string

    QUERY_FOREIGN
        if lpValue == pointer to "Foreign" string

    QUERY_COSTLY
        if lpValue == pointer to "Costly" string

    otherwise:

    QUERY_ITEMS

--*/
{
    WCHAR   *pwcArgChar, *pwcTypeChar;
    BOOL    bFound;

    if (lpValue == 0) {
        return QUERY_GLOBAL;
    } else if (*lpValue == 0) {
        return QUERY_GLOBAL;
    }

    // check for "Global" request

    pwcArgChar = lpValue;
    pwcTypeChar = GLOBAL_STRING;
    bFound = TRUE;  // assume found until contradicted

    // check to the length of the shortest string
   
    while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
        if (*pwcArgChar++ != *pwcTypeChar++) {
            bFound = FALSE; // no match
            break;          // bail out now
        }
    }

    if (bFound) return QUERY_GLOBAL;

    // check for "Foreign" request
   
    pwcArgChar = lpValue;
    pwcTypeChar = FOREIGN_STRING;
    bFound = TRUE;  // assume found until contradicted

    // check to the length of the shortest string
   
    while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
        if (*pwcArgChar++ != *pwcTypeChar++) {
            bFound = FALSE; // no match
            break;          // bail out now
        }
    }

    if (bFound) return QUERY_FOREIGN;

    // check for "Costly" request
   
    pwcArgChar = lpValue;
    pwcTypeChar = COSTLY_STRING;
    bFound = TRUE;  // assume found until contradicted

    // check to the length of the shortest string
   
    while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
        if (*pwcArgChar++ != *pwcTypeChar++) {
            bFound = FALSE; // no match
            break;          // bail out now
        }
    }

    if (bFound) return QUERY_COSTLY;

    // if not Global and not Foreign and not Costly,
    // then it must be an item list
   
    return QUERY_ITEMS;

}

BOOL
IsNumberInUnicodeList (
    IN DWORD   dwNumber,
    IN LPWSTR  lpwszUnicodeList
)
/*++

IsNumberInUnicodeList

Arguments:
       
    IN dwNumber
        DWORD number to find in list

    IN lpwszUnicodeList
        Null terminated, Space delimited list of decimal numbers

Return Value:

    TRUE:
            dwNumber was found in the list of unicode number strings

    FALSE:
            dwNumber was not found in the list.

--*/
{
    DWORD   dwThisNumber;
    WCHAR   *pwcThisChar;
    BOOL    bValidNumber;
    BOOL    bNewItem;
    BOOL    bReturnValue;
    WCHAR   wcDelimiter;    // could be an argument to be more flexible

    if (lpwszUnicodeList == 0) return FALSE;    // null pointer, # not found

    pwcThisChar = lpwszUnicodeList;
    dwThisNumber = 0;
    wcDelimiter = (WCHAR)' ';
    bValidNumber = FALSE;
    bNewItem = TRUE;
   
    while (TRUE) {
        switch (EvalThisChar (*pwcThisChar, wcDelimiter)) {
            case DIGIT:
                // if this is the first digit after a delimiter, then
                // set flags to start computing the new number
                if (bNewItem) {
                    bNewItem = FALSE;
                    bValidNumber = TRUE;
                }
                if (bValidNumber) {
                    dwThisNumber *= 10;
                    dwThisNumber += (*pwcThisChar - (WCHAR)'0');
                }
                break;
           
            case DELIMITER:
                // a delimiter is either the delimiter character or the
                // end of the string ('\0') if when the delimiter has been
                // reached a valid number was found, then compare it to the
                // number from the argument list. if this is the end of the
                // string and no match was found, then return.
                //
                if (bValidNumber) {
                    if (dwThisNumber == dwNumber) return TRUE;
                    bValidNumber = FALSE;
                }
                if (*pwcThisChar == 0) {
                    return FALSE;
                } else {
                    bNewItem = TRUE;
                    dwThisNumber = 0;
                }
                break;

            case INVALID:
                // if an invalid character was encountered, ignore all
                // characters up to the next delimiter and then start fresh.
                // the invalid number is not compared.
                bValidNumber = FALSE;
                break;

            default:
                break;

        }
        pwcThisChar++;
    }

}   // IsNumberInUnicodeList
0
Brain2000Commented:
That was quick.  Now I can select my performance counter and it doesn't crash.  Change this line:

  mydata->pot.NumInstances=0;

However, the data is ZERO, and not 100 like it should be... more to come...
0
Brain2000Commented:
AHA!  I got EVERYTHING working now.  I put a sample counter that will go from 0 to 100 to 0 to 100, etc...

Here's the final code:

//
// Ext Counters
//

#include <windows.h>
#include <winperf.h>
#include <winreg.h>

#define QUERY_GLOBAL    1
#define QUERY_ITEMS     2
#define QUERY_FOREIGN   3
#define QUERY_COSTLY    4

WCHAR GLOBAL_STRING[] = L"Global";
WCHAR FOREIGN_STRING[] = L"Foreign";
WCHAR COSTLY_STRING[] = L"Costly";

WCHAR NULL_STRING[] = L"\0";    // pointer to null string

// test for delimiter, end of line and non-digit characters
// used by IsNumberInUnicodeList routine
//
#define DIGIT       1
#define DELIMITER   2
#define INVALID     3

#define EvalThisChar(c,d) ( \
     (c == d) ? DELIMITER : \
     (c == 0) ? DELIMITER : \
     (c < (WCHAR)'0') ? INVALID : \
     (c > (WCHAR)'9') ? INVALID : \
     DIGIT)

DWORD GetQueryType (IN LPWSTR lpValue);
BOOL IsNumberInUnicodeList(IN DWORD   dwNumber,IN LPWSTR  lpwszUnicodeList);
DWORD APIENTRY CollectPerfData(IN      LPWSTR  lpValueName,IN OUT  LPVOID  *lppData,IN OUT  LPDWORD lpcbTotalBytes,IN OUT  LPDWORD lpNumObjectTypes);

//pack the structure on a 1 byte boundary
#pragma pack(1)
struct _mydata {
  PERF_OBJECT_TYPE pot;
  PERF_COUNTER_DEFINITION pcd;
  PERF_COUNTER_BLOCK pcdata;
  DWORD actualdata;
};
#pragma pack()

DWORD APIENTRY OpenPerfData(LPWSTR lpDeviceNames)
{
      return ERROR_SUCCESS;
}

DWORD APIENTRY
CollectPerfData(
    IN      LPWSTR  lpValueName,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
{
  static DWORD value=0; //this is what we want to pass back to perfmon
  static DWORD updown=1; //flag to count up and down
  DWORD dwQueryType;
  struct _mydata *mydata=(_mydata*)*lppData;
  HKEY myHKEY;
  static DWORD reg_index=0;
  DWORD key_type,temp_size;

  if(*lpcbTotalBytes<sizeof(struct _mydata))
  {
    *lpcbTotalBytes=(DWORD)0;
    *lpNumObjectTypes=(DWORD)0;
    return ERROR_MORE_DATA; //send more paramedics!
  }

  if(!reg_index)
  {
    if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\SERVICENAME\\Performance",0,KEY_ALL_ACCESS,&myHKEY)==ERROR_SUCCESS)
    {
      key_type=REG_DWORD;
      temp_size=sizeof(reg_index);
      RegQueryValueEx(myHKEY,"First Counter",NULL,&key_type,(unsigned char *)&reg_index,&temp_size);
      RegCloseKey(myHKEY);
    }
  }
  if(!reg_index)
  {
    *lpcbTotalBytes=(DWORD)0;
    *lpNumObjectTypes=(DWORD)0;
    return ERROR_SUCCESS;
  }

  dwQueryType=GetQueryType(lpValueName);
  if(dwQueryType==QUERY_ITEMS)
  {
    if(!(IsNumberInUnicodeList(reg_index,lpValueName)))
    {
      // request received for data object not provided by this routine
      *lpcbTotalBytes=(DWORD)0;
      *lpNumObjectTypes=(DWORD)0;
      return ERROR_SUCCESS;
    }
  }

  mydata->pot.TotalByteLength=sizeof(struct _mydata);
  mydata->pot.DefinitionLength=sizeof(mydata->pot)+sizeof(mydata->pcd);
  mydata->pot.HeaderLength=sizeof(mydata->pot);
  mydata->pot.ObjectNameTitleIndex=reg_index;
  mydata->pot.ObjectNameTitle=0;
  mydata->pot.ObjectHelpTitleIndex=reg_index;
  mydata->pot.ObjectHelpTitle=0;
  mydata->pot.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pot.NumCounters=1; //yay!  1 counter
  mydata->pot.DefaultCounter=-1;
  mydata->pot.NumInstances=0;
  mydata->pot.CodePage=0;

  mydata->pcd.ByteLength=sizeof(mydata->pcd);
  mydata->pcd.CounterNameTitleIndex=reg_index+2; //you have to go by 2's.
  mydata->pcd.CounterNameTitle=0;
  mydata->pcd.CounterHelpTitleIndex=reg_index+2;
  mydata->pcd.CounterHelpTitle=0;
  mydata->pcd.DefaultScale=0; //scale of 1.0 (10^0)
  mydata->pcd.DetailLevel=PERF_DETAIL_NOVICE;
  mydata->pcd.CounterType=PERF_SIZE_DWORD|PERF_TYPE_NUMBER|PERF_NUMBER_DECIMAL|PERF_DISPLAY_NO_SUFFIX;
  mydata->pcd.CounterSize=sizeof(value);
  mydata->pcd.CounterOffset=sizeof(PERF_COUNTER_BLOCK); //offset past end of pcd structure to where actual data is

  mydata->pcdata.ByteLength=sizeof(DWORD)+sizeof(value); //size of this structure plus actualdata size

  mydata->actualdata=value; //YES!  This sets the value you're passing back!

  //simulate counting from 0 to 100 and back to 0
  if(updown)
  {
    if(++value>100)
    {
      value=100;
      updown=0;
    }
  }
  else
  {
    if(!--value)
    {
      value=0;
      updown=1;
    }
  }

  // slight change *lppData is a void, so we can't add to it. can we do it this way, I always got lost moving *'s and &'s !
  *lppData=(void *)((char *)*lppData+sizeof(struct _mydata));
  *lpcbTotalBytes = sizeof(struct _mydata);
  *lpNumObjectTypes = (DWORD)1;
  return ERROR_SUCCESS;
}

DWORD APIENTRY ClosePerfData()
{
      return ERROR_SUCCESS;
}




DWORD
GetQueryType (
    IN LPWSTR lpValue
)
/*++

GetQueryType

    returns the type of query described in the lpValue string so that
    the appropriate processing method may be used

Arguments

    IN lpValue
        string passed to PerfRegQuery Value for processing

Return Value

    QUERY_GLOBAL
        if lpValue == 0 (null pointer)
           lpValue == pointer to Null string
           lpValue == pointer to "Global" string

    QUERY_FOREIGN
        if lpValue == pointer to "Foreign" string

    QUERY_COSTLY
        if lpValue == pointer to "Costly" string

    otherwise:

    QUERY_ITEMS

--*/
{
    WCHAR   *pwcArgChar, *pwcTypeChar;
    BOOL    bFound;

    if (lpValue == 0) {
        return QUERY_GLOBAL;
    } else if (*lpValue == 0) {
        return QUERY_GLOBAL;
    }

    // check for "Global" request

    pwcArgChar = lpValue;
    pwcTypeChar = GLOBAL_STRING;
    bFound = TRUE;  // assume found until contradicted

    // check to the length of the shortest string
   
    while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
        if (*pwcArgChar++ != *pwcTypeChar++) {
            bFound = FALSE; // no match
            break;          // bail out now
        }
    }

    if (bFound) return QUERY_GLOBAL;

    // check for "Foreign" request
   
    pwcArgChar = lpValue;
    pwcTypeChar = FOREIGN_STRING;
    bFound = TRUE;  // assume found until contradicted

    // check to the length of the shortest string
   
    while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
        if (*pwcArgChar++ != *pwcTypeChar++) {
            bFound = FALSE; // no match
            break;          // bail out now
        }
    }

    if (bFound) return QUERY_FOREIGN;

    // check for "Costly" request
   
    pwcArgChar = lpValue;
    pwcTypeChar = COSTLY_STRING;
    bFound = TRUE;  // assume found until contradicted

    // check to the length of the shortest string
   
    while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
        if (*pwcArgChar++ != *pwcTypeChar++) {
            bFound = FALSE; // no match
            break;          // bail out now
        }
    }

    if (bFound) return QUERY_COSTLY;

    // if not Global and not Foreign and not Costly,
    // then it must be an item list
   
    return QUERY_ITEMS;

}

BOOL
IsNumberInUnicodeList (
    IN DWORD   dwNumber,
    IN LPWSTR  lpwszUnicodeList
)
/*++

IsNumberInUnicodeList

Arguments:
       
    IN dwNumber
        DWORD number to find in list

    IN lpwszUnicodeList
        Null terminated, Space delimited list of decimal numbers

Return Value:

    TRUE:
            dwNumber was found in the list of unicode number strings

    FALSE:
            dwNumber was not found in the list.

--*/
{
    DWORD   dwThisNumber;
    WCHAR   *pwcThisChar;
    BOOL    bValidNumber;
    BOOL    bNewItem;
    BOOL    bReturnValue;
    WCHAR   wcDelimiter;    // could be an argument to be more flexible

    if (lpwszUnicodeList == 0) return FALSE;    // null pointer, # not found

    pwcThisChar = lpwszUnicodeList;
    dwThisNumber = 0;
    wcDelimiter = (WCHAR)' ';
    bValidNumber = FALSE;
    bNewItem = TRUE;
   
    while (TRUE) {
        switch (EvalThisChar (*pwcThisChar, wcDelimiter)) {
            case DIGIT:
                // if this is the first digit after a delimiter, then
                // set flags to start computing the new number
                if (bNewItem) {
                    bNewItem = FALSE;
                    bValidNumber = TRUE;
                }
                if (bValidNumber) {
                    dwThisNumber *= 10;
                    dwThisNumber += (*pwcThisChar - (WCHAR)'0');
                }
                break;
           
            case DELIMITER:
                // a delimiter is either the delimiter character or the
                // end of the string ('\0') if when the delimiter has been
                // reached a valid number was found, then compare it to the
                // number from the argument list. if this is the end of the
                // string and no match was found, then return.
                //
                if (bValidNumber) {
                    if (dwThisNumber == dwNumber) return TRUE;
                    bValidNumber = FALSE;
                }
                if (*pwcThisChar == 0) {
                    return FALSE;
                } else {
                    bNewItem = TRUE;
                    dwThisNumber = 0;
                }
                break;

            case INVALID:
                // if an invalid character was encountered, ignore all
                // characters up to the next delimiter and then start fresh.
                // the invalid number is not compared.
                bValidNumber = FALSE;
                break;

            default:
                break;

        }
        pwcThisChar++;
    }

}   // IsNumberInUnicodeList
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
ianwhiteAuthor Commented:
You are a star - that works a treat!
I'm going to add a little interface between a sample app and this dll, I think I'll count button clicks - they just want to see something!
Then I have to have our guys convert it to COBOL !!
Thanks again Brian, much appreciated.

Ooh, hope the MCP and the other 'window' thing went well ;-)
Ian
0
ianwhiteAuthor Commented:
Thanks mate.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
C

From novice to tech pro — start learning today.