Send mail to the author(s)

    July 27, 2007

    HOWTO: Retrieve the device ID from the desktop

    Over in the OpenNETCF Forums, someone asked how they could invoke KernelIoControl through RAPI, as they wanted to retrieve the device ID from the PC the device is connected to. The short answer is that you cannot directly invoke KernelIoControl using RAPI.

    However, not all is lost. You can create your a function that will allow you to call KernelIoControl via RAPI.

    The first step is to create your own RAPI function that you can remotely invoke. Unfortunately for the managed code developers there, there is no way to do this in C# or VB.NET. You must use C. Your custom RAPI function must have a method signature that is something like this:

    int MyRAPIFunction(DWORD cbInput, BYTE * pInput, BYTE * cbOutput, BYTE **ppOutput, IRAPIStream *pIRAPIStream);

    When creating RAPI functions there are two approaches that you can take:

    • Block mode
      When you call your remote function through RAPI in block mode, your application will make the call synchronously and control will not be returned until the remote function has completed.
    • Stream mode
      When you call a remote function using stream mode, your application will return immediately after making the call. You will need to use an IRAPIStream COM interface to send data back and forth between the desktop and the device.

    For the purposes of this HOWTO, we're going to call the remote method using block mode. This means we can somewhat simplify the C method signature to the signature below since we have no need for a IRAPIStream interface:

    int MyRAPIFunction(DWORD cbInput, BYTE * pInput, BYTE * cbOutput, BYTE **ppOutput, PVOID pVoid);

    Given that we want to retrieve the device ID remotely, let's call our RAPI function CeGetDeviceID. Below, you'll find the full method implementation in C, including the declarations needed to export CeGetDeviceID.

    #include <windows.h>
    #include <uniqueid.h>

    #ifdef __cplusplus
    extern "C" {
    #endif
    __declspec(dllexport) int CeGetDeviceID(DWORD, BYTE *, DWORD *, BYTE **, PVOID);
    #ifdef __cplusplus
    }
    #endif

    BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
    {
        return TRUE;
    }

    int CeGetDeviceID(DWORD cbInput, BYTE *pInput, DWORD *pcbOutput, BYTE **ppOutput, PVOID pVoid)
    {
        INT cbBuffer = 36;
        BYTE *pBuffer = NULL;
        DWORD *pcbBuffer = NULL;
        DWORD dwOutBytes;
        
        pBuffer = (BYTE *)LocalAlloc(LMEM_FIXED, cbBuffer);

        pcbBuffer = (DWORD *)&pBuffer[0];
        KernelIoControl(IOCTL_HAL_GET_DEVICEID, 0, 0, pBuffer, *pcbBuffer, &dwOutBytes);
        
        *ppOutput = (BYTE *)LocalAlloc(LPTR, dwOutBytes);
        if(*ppOutput)
        {
            *ppOutput = (BYTE *)pBuffer;
            *pcbOutput = *pcbBuffer;
        }
        else
        {
            *pcbOutput = 0;
        }
        return 0;
    }

    Compile and link this to a Win32 DLL called RapiEx.dll.

    Next, we have to create some code that will invoke CeGetDeviceID through RAPI from a desktop application. All you managed code developers can breathe a collective sigh of release as we can do this in the .NET language of your choice. The way in which you invoke a remote function through RAPI is through the CeRapiInvoke method. This is a Win32 call and has the following signature:

    HRESULT CeRapiInvoke(LPCWSTR pDllPath, LPCWSTR pFunctionName, DWORD cbInput, BYTE * pInput, DWORD *pcbOutput, BYTE **ppOutput, IRAPIStream **ppIRAPIStream, DWORD dwReserved);

    However, calling this Win32 method is a lot easier if you use the Desktop Communication libary as we provide a nice managed interface to this method as an instance method in the RAPI class, namely Invoke. The Invoke method has the following signature:

    public void Invoke(string DLLPath, string FunctionName, byte[] InputData, out byte[] OutputData);

    In your code, you'd call it like this if you were using Visual Basic:

    Dim rapi As New RAPI()
    rapi.Connect()

    If rapi.Connected Then
        Dim outputBuffer() As Byte
        Dim nullArray(0) As Byte
        rapi.Invoke("RapiEx", "CeGetDeviceID", nullArray, outputBuffer)
    End If

    rapi.Disconnect()

    Please note that the above code assumes you have deployed RapiEx.dll to the \Windows folder on your device. Following the call, the outputBuffer byte array is populated with the bytes of the device ID. All that is left is to convert this into a human-readable string. So let's put it all together using C#:

    public static string GetDeviceId()
    {
        string deviceId = string.Empty;

        using (RAPI rapi = new RAPI())
        {
            rapi.Connect();
            if (rapi.Connected)
            {
                byte[] outputBuffer;
                rapi.Invoke("RapiEx", "CeGetDeviceID", new byte[0] { }, out outputBuffer);
                if (outputBuffer == null)
                    return string.Empty;

                Int32 PresetIDOffset = BitConverter.ToInt32(outputBuffer, 4);
                Int32 PlatformIDOffset = BitConverter.ToInt32(outputBuffer, 0xc);
                Int32 PlatformIDSize = BitConverter.ToInt32(outputBuffer, 0x10);

                StringBuilder sb = new StringBuilder();
                sb.Append(String.Format("{0:X8}-{1:X4}-{2:X4}-{3:X4}-",
                     BitConverter.ToInt32(outputBuffer, PresetIDOffset),
                     BitConverter.ToInt16(outputBuffer, PresetIDOffset + 4),
                     BitConverter.ToInt16(outputBuffer, PresetIDOffset + 6),
                     BitConverter.ToInt16(outputBuffer, PresetIDOffset + 8)));

                for (int i = PlatformIDOffset; i < PlatformIDOffset + PlatformIDSize; i++)
                {
                    sb.Append(String.Format("{0:X2}", outputBuffer[i]));
                }

                deviceId = sb.ToString();
            }
        }

        return deviceId;
    }

    The GetDeviceId method will return a string that looks something like this: 3FBF5000-7351-0801-090F-8F532A5903CE.

    Let's take this a little further. One of benefits of using managed code is that the .NET Framework is language agnostic, meaning you can use any .NET language you like for the managed code projects in your solution. While this is limited to C# and VB .NET for the .NET Compact Framework, the options are far wider if you writing applications for the desktop environment. I like to play around with dynamic languages, Python in particular, so let's look at how we can retrieve the device ID using IronPython:

    import clr
    clr.AddReference("OpenNETCF.Desktop.Communication")

    from System import *
    from OpenNETCF.Desktop.Communication import *

    def ConvertToString(outputBuffer):
        presetIdOffset = BitConverter.ToInt32(outputBuffer, 4)
        platformIdOffset = BitConverter.ToInt32(outputBuffer, 12)
        platformIdSize = BitConverter.ToInt32(outputBuffer, 16)
        
        deviceId = Text.StringBuilder()
        deviceId.AppendFormat("{0:X8}-{1:X4}-{2:X4}-{3:X4}-",
            BitConverter.ToInt32(outputBuffer, presetIdOffset),
            BitConverter.ToInt16(outputBuffer, presetIdOffset + 4),
            BitConverter.ToInt16(outputBuffer, presetIdOffset + 6),
            BitConverter.ToInt16(outputBuffer, presetIdOffset + 8)
        )
        
        i = platformIdOffset;
        while i < (platformIdOffset + platformIdSize):
            deviceId.AppendFormat("{0:X2}", outputBuffer[i])
            i += 1
            
        return deviceId.ToString()
        
    rapi = RAPI()
    rapi.Connect()

    if rapi.Connected:
        outputBuffer = rapi.Invoke("RapiEx", "CeGetDeviceID", Array.CreateInstance(Byte,0))
        deviceId = ConvertToString(outputBuffer)
        print "Device ID: " + deviceId
        
    rapi.Dispose()