Retrieving the Windows CE Device ID with the Microsoft .NET Compact Framework

.NET Compact Framework 1.0

Neil Cowburn
Content Master Ltd

March 2003

Applies to:
    Microsoft® .NET Compact Framework
    Microsoft Visual Studio® .NET 2003
    Microsoft Windows® CE .NET

Summary: Learn how to retrieve the Device ID from a Windows CE device using the .NET Compact Framework. (5 printed pages)


Platform Invocation Services (P/Invoke)
The IOCTL Code
The KernelIoControl Method
Putting it all Together
More Information


Microsoft® Windows® CE provides a mechanism that uniquely identifies a Windows CE device. To implement this feature, Microsoft has provided a standard structure and I/O Control (IOCTL) Code that can be used by OEMs who need unique identifications. An IOCTL code is a bit-mask for sending commands directly to the device hardware. Not all OEMs require their devices to be uniquely identified, so Microsoft chose an IOCTL code over an OEM-specific function call to reduce the work for an OEM that does not require unique identifications.

The Device ID could be used in asset management to identify individual devices. Also, the Device ID could be used in licensing software. By using the Device ID to generate a reference code, an application can be licensed to an individual device.

Retrieving the Device ID is no simple task. There is no managed support within the .NET Compact Framework for querying devices for their IDs. Instead, you have to resort to using the Platform Invocation Services (P/Invoke) to call an unmanaged Win32 API function, namely KernelIoControl.

This article assumes you have a working knowledge of the .NET Compact Framework and Visual C# .NET, and that you have installed Microsoft Visual Studio® .NET 2003 Final Beta (available from the MSDN® Subscriber Downloads site).

Platform Invocation Services (P/Invoke)

Platform Invocation Services enables managed code to call unmanaged functions implemented in native (non-ActiveX) DLLs. You can use P/Invoke semantically in the same way as in the full .NET Framework, but the .NET Compact Framework has limited support for the objects and types you can marshal between unmanaged and managed code.

P/Invoke is housed in the System.Runtime.InteropService namespace. In your application code, you declare the unmanaged function you want to call. This is carried out using the DllImport attribute. The description includes the module name (the specific DLL filename), entry point name (name of the unmanaged function you are calling), calling convention, and the SetLastError flag. The calling convention describes the way in which parameters are passed to the unmanaged method. In the .NET Compact Framework, the only calling convention that is supported is the WinAPI convention, which corresponds to the __cdecl convention in C++. By Default, C# sets the SetLastError flag to false. Setting this to true allows you to call Marshal.GetLastWin32Error to retrieve the last unmanaged error raised.

As an example, to call the FindWindow function in the unmanaged DLL, coredll.dll, you would include the following code in your project:

public extern IntPtr FindWindow(string ClassName, string WindowName);

The IOCTL Code

The IOCTL_HAL_GET_DEVICEID IOCTL returns the current Device ID assigned to the Windows CE device. A request to retrieve the Device ID is made by passing IOCTL_HAL_GET_DEVICEID to KernelIoControl.

The IOCTL_HAL_GET_DEVICEID IOCTL contains four pieces of information:

  • The device type
  • The access check value (Any/Read/Write)
  • Function to perform (in this instance, retrieve the Device ID)
  • The method code for how the buffers are passed

To define the IOCTL, you need the following constants:

private static Int32 FILE_DEVICE_HAL = 0x00000101;
private static Int32 FILE_ANY_ACCESS = 0x0;
private static Int32 METHOD_BUFFERED = 0x0;

In C#, IOCTL_HAL_GET_DEVICEID is defined as follows:

private static Int32 IOCTL_HAL_GET_DEVICEID = 
    ((FILE_DEVICE_HAL) << 16) | ((FILE_ANY_ACCESS) << 14) 
     | ((21) << 2) | (METHOD_BUFFERED);

These values and the definition of IOCTL_HAL_GET_DEVICEID were taken from the C++ header file, winioctl.h.

The KernelIoControl Method

The KernelIoControl method has been provided by Microsoft as a standard way of sending IOCTLs. KernelIoControl does not perform any processing on the IOCTLs other than sending them through to the requested device.

The declaration for the KernelIoControl function looks like this in C#:

private static extern bool KernelIoControl(Int32 IoControlCode, IntPtr
  InputBuffer, Int32 InputBufferSize, byte[] OutputBuffer, Int32
  OutputBufferSize, ref Int32 BytesReturned);

The parameters are defined as follows:

  • IoControlCode. Set to IOCTL_HAL_GET_DEVICEID to retrieve the DEVICE_ID structure.
  • InputBuffer. Defined by the OEMs for their specific purposes.
  • InputBufferSize. Set to the size of the OEM InputBuffer data.
  • OutputBuffer. The DEVICE_ID data buffer.
  • OutputBufferSize. Set to the size of the DEVICE_ID data buffer prior to calling the function.
  • BytesReturned. Set to the size of the DEVICE_ID data after calling the function.

If the OEM has provided support for the IOCTL_HAL_GET_DEVICEID IOCTL, KernelIoControl will return true. If the OEM has not provided support for IOCTL_HAL_GET_DEVICEID, or the request fails, then false is returned. If false is returned, call Marshal.GetLastWin32Error for details. An error code of 122 indicates that the allocated DEVICE_ID buffer is too small to store all the information. In this situation, the first 4 bytes of OutputBuffer will contain the required buffer size. Simply reallocate the size of OutputBuffer and request the information again.

The Device ID is actually two IDs, a Preset ID and a Platform ID. Calling KernelIoControl will set the output buffer to a byte array containing the Preset ID and Platform ID. To convert the byte array into a useful form, separate out the Preset ID and Platform ID from the byte array and assemble into a string that resembles a globally unique identifier (GUID), as demonstrated in the following example.

Putting it all Together

Below is an example of calling KernelIoControl to retrieve the Device ID.

private static string GetDeviceID()
    byte[] OutputBuffer = new byte[256];
    Int32 OutputBufferSize, BytesReturned;
    OutputBufferSize = OutputBuffer.Length;
    BytesReturned = 0;

    // Call KernelIoControl passing the previously defined
    // IOCTL_HAL_GET_DEVICEID parameter
    // We don’t need to pass any input buffers to this call
    // so InputBuffer and InputBufferSize are set to their null
    // values
    bool retVal = KernelIoControl(IOCTL_HAL_GET_DEVICEID, 
            ref BytesReturned);

    // If the request failed, exit the method now
    if (retVal == false)
        return null;
    // Examine the OutputBuffer byte array to find the start of the 
    // Preset ID and Platform ID, as well as the size of the
    // PlatformID. 
    // PresetIDOffset – The number of bytes the preset ID is offset
    //                  from the beginning of the structure
    // PlatformIDOffset - The number of bytes the platform ID is
    //                    offset from the beginning of the structure
    // PlatformIDSize - The number of bytes used to store the
    //                  platform ID
    // Use BitConverter.ToInt32() to convert from byte[] to int
    Int32 PresetIDOffset = BitConverter.ToInt32(OutputBuffer, 4); 
    Int32 PlatformIDOffset = BitConverter.ToInt32(OutputBuffer, 0xc);
    Int32 PlatformIDSize = BitConverter.ToInt32(OutputBuffer, 0x10);

    // Convert the Preset ID segments into a string so they can be 
    // displayed easily.
    StringBuilder sb = new StringBuilder();
         BitConverter.ToInt32(OutputBuffer, PresetIDOffset), 
         BitConverter.ToInt16(OutputBuffer, PresetIDOffset +4), 
         BitConverter.ToInt16(OutputBuffer, PresetIDOffset +6), 
         BitConverter.ToInt16(OutputBuffer, PresetIDOffset +8))); 

    // Break the Platform ID down into 2-digit hexadecimal numbers
    // and append them to the Preset ID. This will result in a 
    // string-formatted Device ID
    for (int i = PlatformIDOffset; 
         i < PlatformIDOffset +   PlatformIDSize; 
         i ++ )  
        sb.Append( String.Format("{0:X2}", outbuff[i]));
    // return the Device ID string
    return sb.ToString();

More Information

For additional information, please see the following resources:

  • The Windows CE .NET KernelIoControl Reference page
  • A Closer Look at Platform Invoke