Saturday, November 04, 2006

Programmatically Determine Windows Mobile Processor

On a Windows Mobile device, the KernelIoControl function can provide some rather useful information. One such piece of information is related to the processor in the device. Processor information can be retrieved from the device using the IOCTL_PROCESSOR_INFORMATION control code.

Here's how ...

First, some constants:

private const int PROCESSOR_FLOATINGPOINT = 0x00000001;
private const int PROCESSOR_DSP = 0x00000002;
private const int PROCESSOR_16BITINSTRUCTION = 0x00000004;

private const int FILE_DEVICE_HAL = 0x00000101;
private const int METHOD_BUFFERED = 0;
private const int FILE_ANY_ACCESS = 0;

private const int PROCESSOR_INFORMATION_SIZE = 576;

At this point, you might be asking yourself where these values came from. All of the values with the exception of the PROCESSOR_INFORMATION_SIZE came from Windows header files. The PROCESSOR_INFORMATION_SIZE value was calculated based on the PROCESSOR_INFO structure which looks like this:

(Source: MSDN)

typedef __PROCESSOR_INFO {
WORD wVersion;
WCHAR szProcessCore[40];
WORD wCoreRevision;
WCHAR szProcessorName[40];
WORD wProcessorRevision;
WCAHR szCatalogNumber[100]; [sic]
WCHAR szVendor[100];
DWORD dwInstructionSet;
DWORD dwClockSpeed;

A WORD is analogous to a C# short (Int16 - 2 bytes), and a DWORD is analogous to a C# int (Int32 - 4 bytes). Because the strings are Unicode (denoted by WCHAR or Wide Char), those string byte values double when calculating the true size.
wVersion = 2
szProcessorCore = 80
wCoreRevision = 2
szProcessorName = 80
wProcessorRevision = 2
szCatalogNumber = 200
szVendor = 200
dwInstructionSet = 4
dwClockSpeed = 4

This gives us 2 + 80 + 2 + 80 + 2 + 200 + 200 + 4 + 4 = 574. So why am I using 576 above and not 574? The reason has to do with how structures are word aligned which is beyond the scope of this blog post. Just recognize that 576 is divisible by 4 and 574 isn't.

Next, we define the information necessary to calculate the actual control code from the device type, function, method, and file access:


private static int CTL_CODE(int DeviceType, int Function, int Method, int Access)
return ((DeviceType) << 16) | ((Access) << 14) | ((Function << 2)) | (Method);

Then, we define out P/Invoke functions:

[DllImport("coredll", EntryPoint = "KernelIoControl", SetLastError = true)]
private static extern bool KernelIoControlOutBytes(int dwIoControlCode, IntPtr lpInBuf, int nInBufSize,
byte[] lpOutBuf, int nOutBufSize, ref int lpBytesReturned);

private static extern int GetLastError();

Notice I'm using a modified version of KernelIoControl where for the out buffer I am using a byte array. Why? Because it's much easier and cleaner than marshalling structures.

Next, the code to actually retrieve the values:

private void btnGetInfo_Click(object sender, EventArgs e)
byte[] processorInfo = new byte[size];
int bytesReturned = 0;
bool result = KernelIoControlOutBytes(IOCTL_PROCESSOR_INFORMATION, IntPtr.Zero, 0,
processorInfo, size, ref bytesReturned);
if (result)
int version = BitConverter.ToInt16(processorInfo, 0);
txtProcessorCore.Text = UnicodeEncoding.Unicode.GetString(processorInfo, 2, 80);
txtCoreRevision.Text = BitConverter.ToInt16(processorInfo, 82).ToString();
txtProcessorName.Text = UnicodeEncoding.Unicode.GetString(processorInfo, 84, 80);
txtProcessorRevision.Text = BitConverter.ToInt16(processorInfo, 164).ToString();
txtCatalogNumber.Text = UnicodeEncoding.Unicode.GetString(processorInfo, 166, 200);
txtVendor.Text = UnicodeEncoding.Unicode.GetString(processorInfo, 366, 200);
txtInstructionSet.Text = BitConverter.ToInt32(processorInfo, 568).ToString();
txtClockSpeed.Text = BitConverter.ToInt32(processorInfo, 572).ToString();

Some items to recognize:

  • BitConverter.ToInt16 is used to get WORD values

  • BitConverter.ToInt32 is used to get DWORD values

  • UnicodeEncoding.Unicode.GetString is used to get WCHAR values and that the number of bytes specified is double the number specified in the PROCESSOR_INFO structure

  • In between the WCHAR value for the vendor and the DWORD for the Instruction Set, there is a two byte gap. This is where our extra 2 bytes which take us from 574 to 576 come into play.

When I ran this on a Pharos GPS 525 device, here are the values I received:
Processor Core: (Empty)
Core Revision: 0
Processor Name: SC32442-300MHz
Processor Revision: 0
Catalog Number: (Empty)
Vendor: Samsung
Clock Speed: 305

No comments: