Performance counters indexes windows pdh c - c++

I'm writing an utility, which should get current CPU load. At the moment It's working and using \Processor(_Total)\% process time in my localization. For multi-lingual support I'm getting counter name from registry by PdhLookupPerfNameByIndex.
Now code looks like
PdhLookupPerfNameByIndex(NULL, 6, processorTime, &cbPathSize);
PdhLookupPerfNameByIndex(NULL, 238, processor, &cbPathSize);
PDH_COUNTER_PATH_ELEMENTS elements = {NULL, processor, "_Total", NULL, NULL, processorTime};
PdhMakeCounterPath(&elements, fullPath, &cbPathSize, 0);
and I wanna remove hard-coded constants 6 and 238.
Are there some constants which means index for Processor and % process time?

The indexes differ between systems, you have to determine them dynamically. The procedure is hinted at in the MSDN documentation of PdhLookupPerfNameByIndex:
Retrieve the REG_MULTI_SZ data of the the registry value HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009\Counter
Note: "009" stands for English. That key always exists, even on machines with different language versions of Windows.
Look for your (English) counter/object name in the returned data. Format: index followed by English counter/object name, e.g.:
6
% Processor Time
There is your index. Just convert from string to DWORD and use it with PdhLookupPerfNameByIndex.

Related

DeviceIoControl output buffer is empty

I am trying to pass some data from my kernel driver up to the user application.
I have defined the structure in my header file shared by my driver and application:
typedef struct _CallBack
{
HANDLE hParId;
HANDLE hProId;
BOOLEAN bCreate;
}CB_INFO, *PCB_INFO;
In my driver, I have a switch statement
case IOCTL_CODE:
if (outputBufferLength >= sizeof(PCB_INFO))
{
callback->hParId = deviceExtension->hParId;
callback->hProId = deviceExtension->hProId;
callback->bCreate = deviceExtension->bCreate;
Irp->IoStatus.Information = outputBufferLength;
Status = STATUS_SUCCESS;
}
I have tried debugging the code by using DbgPrint, there was nothing wrong with the if statement, as outputBufferLength is 12 and PCB_INFO is 8.
As for the DeviceIoControl code in my application:
DeviceIoControl(
driver,
IOCTL_CODE,
0,
0,
&callback,
sizeof(callback),
&bytesReturn,
NULL);
I have checked the bytesReturn and it does not return 0, it returns a 12.
Other info:
I am using 64-bit Windows 7.
I really have no idea what is wrong, and really would appreciate any form of help. I would be glad to provide more of my code if you need more details. Could it be something to do with me writing the driver on a 64-bit platform, or is there just something wrong with my code?
Thanks in advance!
Firstly, PCB_INFO is a pointer type, so sizeof(PCB_INFO) is the size of a pointer, not the size of the buffer you're pointing to. Use sizeof(CB_INFO) or sizeof(*PCB_INFO) instead. The code shown in the question is actually writing past the end of the buffer, so the results are unpredictable.
Secondly, your structure includes two elements of type HANDLE which has a different size in 32-bit and 64-bit architectures. In most situations Windows automatically takes care of converting ("thunking") between 32-bit and 64-bit structures, but in the case of I/O control codes this is your driver's responsibility. This is described in the DDK article Supporting 32-Bit I/O in Your 64-Bit Driver.
Alternatively you can make your application 64-bit, or change the structure so that it uses only elements of constant size.

Can I get a code page from a language preference?

Windows seems to keep track of at least four dimensions of "current locale":
http://www.siao2.com/2005/02/01/364707.aspx
DEFAULT USER LOCALE
DEFAULT SYSTEM LOCALE
DEFAULT USER INTERFACE LANGUAGE
DEFAULT INPUT LOCALE
My brain hurts just trying to keep track of what the hell four separate locale's are useful for...
However, I don't grok the relationship between code page and locale (or LCID, or Language ID), all of which appear to be different (e.g. Japanese (Japan) is LANGID = 0x411 location code 1, but the code page for Japan is 932).
How can I configure our application to use the user's desired language as the default MBCS target when converting between Unicode and narrow strings?
That is to say, we used to be an MBCS application. Then we switched to Unicode. Things work well in English, but fail in Asian languages, apparently because Windows conversion functions WideCharToMultiByte and MultiByteToWideChar take an explicit code page (not a locale ID or language ID), which can be set to CP_ACP (default to ANSI code page), but don't appear to have a value for "default to user's default interface language's code page".
I mean, this is some seriously convoluted twaddle. Four separate dimensions of "current language", three different identifier types, as well as (different) string-identifiers for C library and C++ standard library.
In our previous MBCS builds, disk I/O and user I/O worked correctly: everything remained in the DEFAULT SYSTEM LOCALE (Windows XP term: "Language for non-Unicode Programs"). But now, in our UNICODE builds, everything tries to use "C" as the locale, and file I/O fails to properly transcode UNICODE to user's locale, and vice verse.
We want to have text files written out (when narrow) using the current user's language's code page. And when read in, the current user's language's code page should be converted back to UNICODE.
Help!!!
Clarification: I would ideally like to use the MUI language code page rather than the OS default code page. GetACP() returns the system default code page, but I am unaware of a function that returns the user's chosen MUI language (which auto-reverts to system default if no MUI specified / installed).
I agree with the comments by Jon Trauntvein, the GetACP function does reflect the user's language settings in the control panel. Also, based on the link to the "sorting it all out" blog, that you provided, DEFAULT USER INTERFACE LANGUAGE is the language that the Windows user interface will use, which is not the same as the language to be used by programs.
However, if you really want to use DEFAULT USER INTERFACE LANGUAGE then you get it by calling GetUserDefaultUILanguage and then you can map the language id to a code page, using the following table.
Language Identifiers and Locales
You can also use the GetLocaleInfo function to do the mapping, but first you would have to convert the language id that you got from GetUserDefaultUILanguage into a locale id, and I think you will get the name of the code page instead of a numeric value, but you could try it and see.
If all you want to be able to do is configure a locale object to use the currently selected locale settings, you should be able to do something like this:
std::locale loc = std::locale("");
You can also access the current code page in windows using the Win32 ::GetACP() function. Here is an example that I implemented in a string class to append multi-byte characters to a unicode string:
void StrUni::append_mb(char const *buff, size_t buff_len)
{
UINT current_code_page = ::GetACP();
int space_needed;
if(buff_len == 0)
return;
space_needed = ::MultiByteToWideChar(
current_code_page,
MB_PRECOMPOSED | MB_ERR_INVALID_CHARS,
buff,
buff_len,
0,
0);
if(space_needed > 0)
{
reserve(this->buff_len + space_needed + 1);
MultiByteToWideChar(
current_code_page,
MB_PRECOMPOSED | MB_ERR_INVALID_CHARS,
buff,
buff_len,
storage + this->buff_len,
space_needed);
this->buff_len += space_needed;
terminate();
}
}
Just use CW2A() or CA2W() which will take care of the conversion for you using the current system locale (or language used for non-Unicode applications).
FWIW, this is what I ended up doing:
#define _CONVERSION_DONT_USE_THREAD_LOCALE // force CP_ACP *not* CP_THREAD_ACP for MFC CString auto-conveters!!!
In application startup, construct the desired locale: m_locale(FStringA(".%u", GetACP()).GetString(), LC_CTYPE)
force it to agree with GetACP(): // force C++ and C libraries based on setlocale() to use system locale for narrow strings
m_locale = ::std::locale::global(m_locale); // we store the previous global so we can restore before termination to avoid memory loss
This gives me relatively ideal use of MFC's built-in narrow<->wide conversions in CString to automatically use the user's default language when converting to or from MBCS strings for the current locale.
Note: m_locale is type ::std::locale

GetDiskFreeSpaceEx with NULL Directory Name failing

I'm trying to use GetDiskFreeSpaceEx in my C++ win32 application to get the total available bytes on the 'current' drive. I'm on Windows 7.
I'm using this sample code: http://support.microsoft.com/kb/231497
And it works! Well, almost. It works if I provide a drive, such as:
...
szDrive[0] = 'C'; // <-- specifying drive
szDrive[1] = ':';
szDrive[2] = '\\';
szDrive[3] = '\0';
pszDrive = szDrive;
...
fResult = pGetDiskFreeSpaceEx ((LPCTSTR)pszDrive,
    (PULARGE_INTEGER)&i64FreeBytesToCaller,
    (PULARGE_INTEGER)&i64TotalBytes,
(PULARGE_INTEGER)&i64FreeBytes);
fResult becomes true and i can go on to accurately calculate the number of free bytes available.
The problem, however, is that I was hoping to not have to specify the drive, but instead just use the 'current' one. The docs I found online (Here) state:
lpDirectoryName [in, optional]
A directory on the disk. If this parameter is NULL, the function uses the root of the current disk.
But if I pass in NULL for the Directory Name then GetDiskFreeSpaceEx ends up returning false and the data remains as garbage.
fResult = pGetDiskFreeSpaceEx (NULL,
    (PULARGE_INTEGER)&i64FreeBytesToCaller,
    (PULARGE_INTEGER)&i64TotalBytes,
(PULARGE_INTEGER)&i64FreeBytes);
//fResult == false
Is this odd? Surely I'm missing something? Any help is appreciated!
EDIT
As per JosephH's comment, I did a GetLastError() call. It returned the DWORD for:
ERROR_INVALID_NAME 123 (0x7B)
The filename, directory name, or volume label syntax is incorrect.
2nd EDIT
Buried down in the comments I mentioned:
I tried GetCurrentDirectory and it returns the correct absolute path, except it prefixes it with \\?\
it returns the correct absolute path, except it prefixes it with \\?\
That's the key to this mystery. What you got back is the name of the directory with the native api path name. Windows is an operating system that internally looks very different from what you are familiar with winapi programming. The Windows kernel has a completely different api, it resembles the DEC VMS operating system a lot. No coincidence, David Cutler used to work for DEC. On top of that native OS were originally three api layers, Win32, POSIX and OS/2. They made it easy to port programs from other operating systems to Windows NT. Nobody cared much for the POSIX and OS/2 layers, they were dropped at XP time.
One infamous restriction in Win32 is the value of MAX_PATH, 260. It sets the largest permitted size of a C string that stores a file path name. The native api permits much larger names, 32000 characters. You can bypass the Win32 restriction by using the path name using the native api format. Which is simply the same path name as you are familiar with, but prefixed with \\?\.
So surely the reason that you got such a string back from GetCurrentDirectory() is because your current directory name is longer than 259 characters. Extrapolating further, GetDiskFreeSpaceEx() failed because it has a bug, it rejects the long name it sees when you pass NULL. Somewhat understandable, it isn't normally asked to deal with long names. Everybody just passes the drive name.
This is fairly typical for what happens when you create directories with such long names. Stuff just starts falling over randomly. In general there is a lot of C code around that uses MAX_PATH and that code will fail miserably when it has to deal with path names that are longer than that. This is a pretty exploitable problem too for its ability to create stack buffer overflow in a C program, technically a carefully crafted file name could be used to manipulate programs and inject malware.
There is no real cure for this problem, that bug in GetDiskFreeSpaceEx() isn't going to be fixed any time soon. Delete that directory, it can cause lots more trouble, and write this off as a learning experience.
I am pretty sure you will have to retrieve the current drive and directory and pass that to the function. I remember attempting to use GetDiskFreeSpaceEx() with the directory name as ".", but that did not work.

How to reliably and quickly get the MAC address of a network card given its device instance ID

Given a device instance ID for a network card, I would like to know its MAC address. Example device instance ID on my system for integrated Intel Gigabit card:
PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8
So far, the algorithm I have used works as follows:
Call SetupDiGetClassDevs with DIGCF_DEVICEINTERFACE.
Call SetupDiEnumDeviceInfo to get the returned device in a SP_DEVINFO_DATA.
Call SetupDiEnumDeviceInterfaces with GUID_NDIS_LAN_CLASS to get a device interface.
Call SetupDiGetDeviceInterfaceDetail for this returned device interface. This gets us the device path as a string: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
At this point we have an address to the network card driver's interface. Open it with CreateFile using the result from #4.
Call DeviceIoControl with IOCTL_NDIS_QUERY_GLOBAL_STATS and OID of OID_802_3_PERMANENT_ADDRESS to get the MAC address.
This usually works, and has been used successfully on quite a large number of machines. However, it appears that a very select few machines have network drivers that aren't responding properly to the DeviceIoControl request in step #6; the problem persists even after updating network card drivers to the latest. These are newer, Windows 7-based computers. Specifically, DeviceIoControl completes successfully, but returns zero bytes instead of the expected six bytes containing the MAC address.
A clue seems to be on the MSDN page for IOCTL_NDIS_QUERY_GLOBAL_STATS:
This IOCTL will be deprecated in later operating system releases. You
should use WMI interfaces to query miniport driver information. For
more information see, NDIS Support for WMI.
-- perhaps newer network card drivers are no longer implementing this IOCTL?
So, how should I get this working? Is it possible there's an oversight in my approach and I'm doing something slightly wrong? Or do I need to take a much more different approach? Some alternate approaches seem to include:
Query Win32_NetworkAdapter WMI class: provides needed information but rejected due to horrible performance. See Fast replacement for Win32_NetworkAdapter WMI class for getting MAC address of local computer
Query MSNdis_EthernetPermanentAddress WMI class: appears to be the WMI replacement for IOCTL_NDIS_QUERY_GLOBAL_STATS and queries the OID directly from the driver - and this one works on the troublesome network driver. Unfortunately, the returned class instances only provide the MAC address and the InstanceName, which is a localized string like Intel(R) 82567LM-2 Gigabit Network Connection. Querying MSNdis_EnumerateAdapter yields a list which relates the InstanceName to a DeviceName, like \DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852}. I'm not sure how to go from the DeviceName to the plug-and-play device instance ID (PCI\VEN_8086......).
Call GetAdaptersAddresses or GetAdaptersInfo (deprecated). The only non-localized identifier I can find in the return value is the adapter name, which is a string like {28FD5409-15BD-4C06-B62F-004D3A06F852} - same as the DeviceName returned by the WMI NDIS classes. So again, I can't figure out how to relate it to the device instance ID. I'm not sure if it would work 100% of the time either - e.g. for adapters without TCP/IP protocol configured.
NetBIOS method: requires specific protocols to be set up on the card so won't work 100% of time. Generally seems hack-ish, and not a way to relate to device instance ID anyway that I know of. I'd reject this approach.
UUID generation method: rejected for reasons I won't elaborate on here.
It seems like if I could find a way to get the "GUID" for the card from the device instance ID, I'd be well on my way with one of the remaining two ways of doing things. But I haven't figured out how yet. Otherwise, the WMI NDIS approach would seem most promising.
Getting a list of network cards and MAC addresses is easy, and there are several ways of doing it. Doing it in a fast way that lets me relate it to the device instance ID is apparently hard...
EDIT: Sample code of the IOCTL call if it helps anyone (ignore the leaked hFile handle):
HANDLE hFile = CreateFile(dosDevice.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
wcout << "GetMACAddress: CreateFile on " << dosDevice << " failed." << endl;
return MACAddress();
}
BYTE address[6];
DWORD oid = OID_802_3_PERMANENT_ADDRESS, returned = 0;
//this fails too: DWORD oid = OID_802_3_CURRENT_ADDRESS, returned = 0;
if (!DeviceIoControl(hFile, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), address, 6, &returned, NULL)) {
DWORD err = GetLastError();
wcout << "GetMACAddress: DeviceIoControl on " << dosDevice << " failed." << endl;
return MACAddress();
}
if (returned != 6) {
wcout << "GetMACAddress: invalid address length of " << returned << "." << endl;
return MACAddress();
}
The code fails, printing:
GetMACAddress: invalid address length of 0.
So the DeviceIoControl returns non-zero indicating success, but then returns zero bytes.
Here's one way to do it:
Call GetAdaptersAddresses to get a list of IP_ADAPTER_ADDRESSES structs
Iterate over each adapter and get its GUID from the AdapterName field (I'm not sure if this behaviour is guaranteed, but all the adapters in my system have a GUID here, and the documentation says the AdapterName is permanent)
For each adapter read the registry key from HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID (if it exists) (got this idea from here; searching on Google that key seems to be well documented, so it's not likely to change)
From this key you get the device ID for the adapter (something like: PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4)
Do this for each adapter until you find a match. When you get your match just go back to the IP_ADAPTER_ADDRESSES and look at the PhysicalAddress field
Get a beer (optional)
It wouldn't be Windows if there weren't a million ways to do something!
I wound up using SetupDiGetDeviceRegistryProperty to read SPDRP_FRIENDLYNAME. If that's not found, then I read SPDRP_DEVICEDESC instead. Ultimately, this gets me a string like "VirtualBox Host-Only Ethernet Adapter #2". I then match this against the InstanceName property in the WMI NDIS classes (MSNdis_EthernetPermanentAddress WMI class). Both properties must be read in case there are multiple adapters sharing the same driver (i.e. "#2", "#3", etc.) - if there's only one adapter then SPDRP_FRIENDLYNAME isn't available, but if there is more than one then SPDRP_FRIENDLYNAME is required to differentiate them.
The method makes me a little nervous because I'm comparing what seems like a localized string, and there's no documentation that I've found that guarantees what I'm doing will always work. Unfortunately, I haven't found any better ways that are documented to work, either.
A couple other alternate methods involve groveling in undocumented registry locations. One method is spencercw's method, and the other would be to read SPDRP_DRIVER, which is the name of a subkey under HKLM\SYSTEM\CurrentControlSet\Control\Class. Underneath the driver key, look for the Linkage\Export value which then seems like it could be matched to the DeviceName property of the MSNdis_EnumerateAdapter class. But there's no documentation I could find that says these values can be legally matched. Furthermore, the only documentation I found about Linkage\Export was from the Win2000 registry reference and explicitly said that applications shouldn't rely on it.
Another method would be to look at my original question, step 4: "SetupDiGetDeviceInterfaceDetail for this returned device interface". The device interface path actually can be used to reconstruct the device path. Start with device interface path: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}. Then, remove everything before the final slash, leaving you with: {28fd5409-15bd-4c06-b62f-004d3a06f852}. Finally, prepend \Device\ to this string and match it against the WMI NDIS classes. Again, however, this seems to be undocumented and relying on an implementation detail of a device interface path.
In the end, the other methods I investigated had their own undocumented complications that sounded at least as serious as matching the SPDRP_FRIENDLYNAME / SPDRP_DEVICEDESC strings. So I opted for the simpler approach, which was to just match those strings against the WMI NDIS classes.
I guess you want to get the MAC address in order to implement some sort of DRM, inventory, or classification system, since you tried to get the permanent MAC address instead of the current one.
You seem to forget that there's even an administratively super-imposed MAC address (in other words: a "forced" MAC address).
Some drivers let you do this from the Device Property page, under the Advanced tab (for example: my Marvell network adapter let me do this), while some others don't let you do that (read: they don't support that property).
However, it all ends in a Registry value: HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress, with a REG_SZ type.
Here you can set a different MAC address than the original one, in the form "01020304abcd" (6 bytes, plain hexadecimal, without : separators or 0x prefix).
After you set it, reboot the machine, and on power-up the new MAC address will have effect.
I happen to have a motherboard with two Marvell integrated NICs, and a NETGEAR USB WiFi NIC. The Marvell one supports changing the MAC address: if you set the NetworkAddress value in the Registry, you see the new value in the driver properties page, too, and it has effect immediately, without the need to restart (if you change it from device Property Page).
Here follows the results of reading the MAC address with different methods:
GetAdaptersInfo: new MAC address
IOCTL_NDIS_QUERY_GLOBAL_STATS: original MAC address
MSNdis_EthernetPermanentAddress: original MAC address
I tried adding the NetworkAddress value in the Registry for the NETGEAR USB WiFi NIC, and the results are:
GetAdaptersInfo: new MAC address
IOCTL_NDIS_QUERY_GLOBAL_STATS: new MAC address
MSNdis_EthernetPermanentAddress: new MAC address
The original MAC addres is gone.
So, in order to not be fooled by a "malicious" user, you always need to check the HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress Registry value. If that is set, i guess it's better to not trust that Network Adapter at all, since it is up to the driver implementation to decide what will be presented to you using the different methods.
Some background for getting to that Registry key:
Microsoft documentation about the HKLM\SYSTEM\CurrentControlSet\Class key
According to the Microsoft documentation on that page,
There is a subkey for each class that is named using the GUID of the
setup class
So we choose the {4D36E972-E325-11CE-BFC1-08002BE10318} subkey (aka GUID_DEVCLASS_NET, defined in <devguid.h>, and further documented here)
Again, according to Microsoft documentation,
Each class subkey contains other subkeys known as software keys (or, driver keys) for each device instance of that class installed in the system. Each of these software keys is named by using a device instance ID, which is a base-10, four-digit ordinal value
The xxxx part is a 4-character textual representation of a positive integer, starting from 0
So, you can traverse the subkeys up from 0000, 0001, 0002, up to the number of network adapters in your system.
The documentation stops here: I didn't find any other documentation about the different registry values, or such.
However, in each of these subkeys, you can find REG_SZ values that can help you link the GetAdaptersInfo(), MSNdis_EthernetPermanentAddress, Win32_NetworkAdapter, and Device Instance ID worlds (and this answers your question).
The Registry values are:
DeviceInstanceID: its value is, no surprise, the Device Instance ID
NetCfgInstanceId: its value is the AdapterName member of the IP_ADAPTER_INFO struct, returned by GetAdaptersInfo(). It is also the GUID member of the Win32_NetworkAdapter WMI class.
Don't forget the NetworkAddress one: should a valid MAC address exist here, a driver may report it as the MAC address in use by GetAdaptersInfo(), MSNdis_EthernetPermanentAddress, and IOCTL_NDIS_QUERY_GLOBAL_STATS!
Then, as you already said, the only connection between the MSNdis_EthernetPermanentAddress WMI Class and the rest of the "world" is by its InstanceName member. You can relate it to the Description member of the IP_ADAPTER_INFO struct, returned by GetAdaptersInfo(). Although it may be a localized name, it seems to be unique for the system (For my two integrated Marvell NICs, the second one has a " #2" appended to its name).
Final note:
Said all the above, the user could choose to disable WMI...

Is a Program Running in Compatibility Mode

Is there a C++ .NET function that I can call that will detect if my program is running in compatibility mode? If there is not one, could someone show me the code for one? Thanks.
For example:
Program loads up
Compatibility Mode check
if true then exit
else run
From another forum
After a few google searches went in
vain, I decided to experiment myself.
I found that the compatibility
settings for each executable are
stored - as I thought it would be - in
the windows registry.
The key where the settings are stored
is
HKEY_CURRENT_USER\Software\Microsoft\Windows
NT\CurrentVersion\AppCompatFlags\Layers
For each application that has its
compatibility settings specified,
there exists a value under that key
whose name is the path to the
executable and the data is a string
consisting of the compatibility
settings.
The keywords in the string that
specify the compatibility settings
are: WIN95 WIN98 NT4SP5
WIN2000 256COLOR 640X480
DISABLETHEMES DISABLECICERO
If multiple settings are specified (or
are to be specified), the data
consists of the settings above
separated by a space each. The first
four settings are mutually exclusive,
i.e. only one of them is to be
specified (if at all). I haven't
tested the consequences of specifying
multiple operating systems.
So, back to addressing your problem.
To check if an executable (let's say,
"C:\path\executable.exe") is set to
run in 256 color mode, there would be
a value named "C:\path\executable.exe"
(without the quotes, even if the path
contains spaces) under the key
[HKEY_CURRENT_USER\Software\Microsoft\Windows
NT\CurrentVersion\AppCompatFlags\Layers],
and the data associated with the value
would contain the string "256COLOR".
If it is also set to run in
compatibility mode under Windows
98/ME, the data would be "WIN98
256COLOR".
So, the approach is simple. Test if
there is a value with the full path of
the executable under the key I
mentioned above. If there isn't, the
executable has not been specified any
compatibility settings. If the value
exists, retrieve its data and check
for the presence of "256COLOR" in the
data. Accordingly, the presence of
"WIN95" or "WIN98" or "NT4SP5" or
"WIN2000" would mean that the
executable is set to run in
compatibility mode for that particular
operating system.
Get the version of the operation system that is returned from GetVersionEx and compare it to the file version on kernel32.dll. When in application compatibility mode GetVersionEx will always return the version of the operating system that is being 'emulated'. If both versions are different then you are in application compatibility mode.
The answer above helped me to get a "solution" for the question at hand. It is probably not the most elegant, but seems to work. Obviously you can get a bit more creative on the return type. Booleon does not suffice here. I think a native API would be good.
typedef VOID (NTAPI* TRtlGetNtVersionNumbers)(LPDWORD pdwMajorVersion, LPDWORD pdwMinorVersion, LPDWORD pdwBuildNumber);
bool IsRunningCompatMode()
{
TRtlGetNtVersionNumbers RtlGetNtVersionNumbers = (TRtlGetNtVersionNumbers)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetNtVersionNumbers");
assert(RtlGetNtVersionNumbers);
if(RtlGetNtVersionNumbers)
{
OSVERSIONINFO osInfo = {0};
osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osInfo);
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
RtlGetNtVersionNumbers(&dwMajorVersion, &dwMinorVersion, &dwBuildNumber);
dwBuildNumber &= 0x0000FFFF;
if(osInfo.dwBuildNumber != dwBuildNumber)
{
return true;
}
}
return false;
};