GPS Intermediate Driver for Windows Mobile (and getting it to work!)

My last two Windows Mobile phones have both had GPS, so I’ve played with code to get my location from the GPS a few times. These have generally been quick hacked-together bits and pieces. In all of them, I wrote my own GPS code. I knew that the GPS device would be accessible through a serial port, so I just connected to the relevant COM port and started reading. It’s easy to parse – NMEA sentences are written with an update on each line, in comma-separated strings.

After sharing my OpenCellId client last week, I was encouraged to try rewriting the GPS code for it using the Windows Mobile GPS Intermediate Driver. So I had a quick try. I was vaguely aware of it before, but hadn’t taken a proper look. In this post, I’ll describe briefly what it is, it’s benefits over home-grown hacks such as my own, and share a couple of things that I had to do to get it to work.

What is the GPS Intermediate Driver?
The GPS Intermediate Driver is a Windows Mobile DLL providing a GPS API. Instead of accessing the serial port yourself, you call GPS functions to get the data via the OS. You can even get access to the data already parsed into location information.

Why use it?
MSDN documents several benefits, but I think that the main ones are:

  • By not accessing the COM port yourself, you don’t need to know which COM port to use. I have already had a few emails from people asking how to work out what value to put into the COM port box on the OpenCellId client. Using the GPS Intermediate driver means that I’ve been able to remove this setting – I don’t need to know how a user’s GPS device is configured.
  • By not keeping the COM port to yourself, you allow the possibility for multiple applications to access the GPS device at the same time. Using the GPS Intermediate driver means that a user could potentially run the OpenCellId client at the same time as a sat nav program.

These were enough of a benefit to justify throwing out my serial port and string parsing code.

How do I use it?
If you want to access it from unmanaged, C++ code, the API is well-documented in MSDN.

If you want to access it from managed, C# code, the Windows Mobile 5 and 6 SDKs each ship with a sample project which uses the GPS Intermediate driver.

How did I get it to work?

  1. Avoid the WM5 sample – use the WM6 one
    Windows Mobile 6 is still not completely widespread, so I try to target WM5 to make my code useful to as many devices as possible. So I started with the GPS sample from the WM5 SDK.

    It is a little buggy. The code threw a few DivideByZero exceptions, but even after fixing those I couldn’t get any useful values from it.

    I had a look at the WM6 sample – doing a diff between the two projects shows that these bugs were found and corrected in time for the WM6 SDK. It looks like there is a bug in the WM5 sample in the expectation of the format of the latitude and longitude values – with some parsing methods in the WM6 sample being rewritten.

    I couldn’t find anything in the WM6 sample that is unsupported in WM5, so (rather than keep trying to fix the WM5 sample!) I took a copy of the WM6 sample and used “Change target platform” to repurpose it for WM5. It compiles fine without needing any changes.

  2. Handling newer WM6 devices
    Even then, I couldn’t get anything out of it – each attempt to get a location returned latitudes and longitudes of zero.

    Stepping through the code with the Visual Studio debugger tracked down the problem to the call to GPSGetPosition. It was returning an error code of 87 : ERROR_INVALID_PARAMETER. This was a surprise – why would the Microsoft sample code be using invalid parameters for the main GPS API call?

    A bit of Googling uncovered an MSDN blog post with the answer. The blog post is worth a read if you’re interested in the details, but in short, one of the structures used in the GPS code has changed in newer WM6 builds, and when allocating space for the structure the sample was using a hard-coded value for the old size of the structure.

    The new structure size is 376 bytes. I tested this with a quick hack to use a structure size of 376 if a first attempt fails with a return code of ERROR_INVALID_PARAMETER, which lets it get location updates okay.

    Stepping through with the debugger shows that we do go through this hack on my WM6 ‘Advantage’, but go through the original code on an old WM5 device of mine.

    public GpsPosition GetPosition(TimeSpan maxAge)
    {
        GpsPosition gpsPosition = null;
        if (Opened)
        {
            // allocate the necessary memory on the native side.  We have a class (GpsPosition) that 
            // has the same memory layout as its native counterpart
            IntPtr ptr = Utils.LocalAlloc(Marshal.SizeOf(typeof(GpsPosition)));
    
            // fill in the required fields 
            gpsPosition = new GpsPosition();
            gpsPosition.dwVersion = 1;
            gpsPosition.dwSize = Marshal.SizeOf(typeof(GpsPosition));
    
            // Marshal our data to the native pointer we allocated.
            Marshal.StructureToPtr(gpsPosition, ptr, false);
    
            // call native method passing in our native buffer
            int result = GPSGetPosition(gpsHandle, ptr, 500000, 0);
            if (result == 0)
            {
                // native call succeeded, marshal native data to our managed data
                gpsPosition = (GpsPosition)Marshal.PtrToStructure(ptr, typeof(GpsPosition));
    
                if (maxAge != TimeSpan.Zero)
                {
                    // check to see if the data is recent enough.
                    if (!gpsPosition.TimeValid || DateTime.Now - maxAge > gpsPosition.Time)
                    {
                        gpsPosition = null;
                    }
                }
            }
            else if (result == 87) // ERROR_INVALID_PARAMETER)
            {
                // 
                // TEMPORARY HACK
                // 
                //  http://blogs.msdn.com/cenet/archive/2007/12/06/gpsid-problem-workaround-on-recent-wm6-release.aspx
                // 
    
                Utils.LocalFree(ptr);
    
                // allocate the necessary memory on the native side.  We have a class (GpsPosition) that 
                // has the same memory layout as its native counterpart
                ptr = Utils.LocalAlloc(376);
    
                // fill in the required fields 
                gpsPosition = new GpsPosition();
                gpsPosition.dwVersion = 1;
                gpsPosition.dwSize = 376;
    
                // Marshal our data to the native pointer we allocated.
                Marshal.StructureToPtr(gpsPosition, ptr, false);
    
                // call native method passing in our native buffer
                result = GPSGetPosition(gpsHandle, ptr, 500000, 0);
    
                if (result == 0)
                {
                    // native call succeeded, marshal native data to our managed data
                    gpsPosition = (GpsPosition)Marshal.PtrToStructure(ptr, typeof(GpsPosition));
    
                    if (maxAge != TimeSpan.Zero)
                    {
                        // check to see if the data is recent enough.
                        if (!gpsPosition.TimeValid || DateTime.Now - maxAge > gpsPosition.Time)
                        {
                            gpsPosition = null;
                        }
                    }
                }
            }
    
            // free our native memory
            Utils.LocalFree(ptr);
        }
    
        return gpsPosition;    
    }

    The MSDN blog post suggests that GPSGetDeviceState has a similar problem, but I’ve not tried it for myself.

Tags: , , , , , , , , ,

17 Responses to “GPS Intermediate Driver for Windows Mobile (and getting it to work!)”

  1. Nice article…

    I just upgraded my old Q to a Q9c, Windows Mobile 6. I have been learning via the blogs I’ve read that Verizon Wireless blocks all access to the GPS functionality outside of their own “VZ Navigator”. Is this correct, or will this GPS Intermediate Driver allow me to use it? I would think that maybe Verizon simply takes out a .DLL somewhere…

    Thanks in advance for your response,

    Chris

  2. mike says:

    Is the source code available?

  3. dale says:

    @Chris – No idea, sorry

    @Mike – This is all based upon source made available by Microsoft – it’s included in the Windows Mobile SDK. The changes I’ve made to that source are included in my post.

  4. midday says:

    I have a similar problem, but i have not ERROR_INVALID_PARAMETER on this devices. I receive a dwFlags == GPS_DATA_FLAGS_HARDWARE_OFF. Why ? 🙁

  5. dale says:

    @midday – The doc says that GPS_DATA_FLAGS_HARDWARE_OFF means:

    The GPS Intermediate Driver does not have a connection to GPS hardware. The returned data has been retrieved from the GPS Intermediate Driver cache.

    Have you enabled the intermediate driver?

  6. Martin says:

    Hi Dale,

    is there a C# work-around for GPSGetDeviceState available ? I tried to use the MSDN blog post to convert it to C# code, but it ain’t working.

    Thanks in advance.

  7. dale says:

    @Martin – Dunno about workarounds, but I would have thought that you should be able to pinvoke it okay. I’ve not tried this for myself though

  8. Alon says:

    Hi,

    if I change the “public DateTime Time” property in GpsPosition.cs to this:
    public DateTime Time
    {
    get
    {
    //DateTime time = new DateTime(stUTCTime.year, stUTCTime.month, stUTCTime.day, stUTCTime.hour, stUTCTime.minute, stUTCTime.second, stUTCTime.millisecond);
    return System.DateTime.Now;
    //return time;
    }

    }

    It all works fine (without the fix in your blog)…
    of course I lose the real GPS time (and hence the GpsPosition(MaxAge) ?)

    but I wonder why it works… if the problem is the structure size – both the original ‘time’ and my ‘System.DateTime.Now’ suppose to have the same size, no??

  9. Alon says:

    Actually, running your code now gives me the same old exception I got before I fixed the DateTime property –

    System.ArgumentOutOfRangeException was unhandled
    Message=”Specified argument was out of the range of valid values.”
    StackTrace:
    at System.DateTime.DateToTicks(Int32 year, Int32 month, Int32 day)
    at System.DateTime..ctor(Int32 year, Int32 month, Int32 day, Int32 hour, Int32 minute, Int32 second, Int32 millisecond)
    at MobileWorkforceManager.Mobile6GPSWrraper.GpsPosition.get_Time()
    at MobileWorkforceWM_WinGUI.frmMain.ConstructData2Office()
    at MobileWorkforceWM_WinGUI.frmMain.tmrIntervals_Tick(Object sender, EventArgs e)
    at System.Windows.Forms.Timer._WnProc(WM wm, Int32 wParam, Int32 lParam)
    at System.Windows.Forms.ApplicationThreadContext._InternalContextMessages(WM wm, Int32 wParam, Int32 lParam)
    at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
    at System.Windows.Forms.Application.Run(Form fm)
    at MobileWorkforceWM_WinGUI.frmMain.Main()

    any idea what is going on here?

  10. Michael says:

    Isnt there just a program or a fix I can upload to my moto q?

    If I have to get into the reg then use what program? lol

  11. dale says:

    @Michael – Sorry, I’m not sure what you mean. What is it you want to do? (or what did you think this post was about?)

  12. dale says:

    @Alon – Sorry, I’ve not really played with this code since writing this post! So everything I know is pretty much contained here.

    I didn’t make a massive amount of changes to the Microsoft-supplied samples, so if there is a problem with the p/invoking or something like that, you might get more help somewhere like the MSDN forums for Windows Mobile development.

    Good luck!

  13. […] location sharing is something that I keep coming back to: from finding where my phone is using GPS, Bluetooth, WiFi Access Points, GSM Cell Ids, using my own hand-rolled systems or newer services […]

  14. Chris says:

    Superb, many thanks – this was exactly what I wanted. I couldn’t work out from the MS blog what the guy was proposing we actually *do* in our code to fix the bug! 🙂

  15. Roysx says:

    Hello To Everybody.

    I Have a problem with the GPS Intermediate Driver

    I Use the Position.Latitude And Position.Longitude to get the coordinates
    BUT i don’t really understand what are this numbers (3146.9139,3441.2905)
    represent!

    1. My Position in google maps does not cross with those numbers
    2. When i make a search for those numbers they don’t even Exists!

    Can anyone help me? thanks for advanced…

  16. B says:

    Roysx,
    They are GPS coordinates in degrees and fractional degrees, multiplied by 100.
    So your position is 31.469139; 34.412905, the fractional part mening fractions of a degree.

  17. ushah says:

    Hi

    i understand about GPS intermediate driver.My only concern is that what we have to do when we want to use Inbuilt GPS in windows mobile.here we have to do port settings in windows mobile 6 emulator.And we can write here out attached GPS device port number But if we want to use Inbuilt GPS of windows mobile than what?