Click here to Skip to main content
Click here to Skip to main content

GPS and Web Service using C++ ATL/WTL (Windows Mobile 6, Standard)

, 18 Jan 2008
Rate this:
Please Sign up or sign in to vote.
This article explains how to use the GPS API and Web Service on a SmartPhone to show demographics information for the current location.

Demo Application UI

Contents

Introduction

There are a lot of examples on how to use a Web Service from the managed world, but sometimes, having the whole runtime installed is an unneeded luxury. If you want something light, this article will give you a taste of how this can be done.

For the past few years, Web Service development has been one of my main areas of interest. I have been designing and developing all kinds of Web Services as well as client applications that consume Web Services. I have used different environments/languages to build web based clients as well as standalone desktop applications. One thing I didn't try before is to build a Web Service client application on a mobile device. This is one of the reasons why I have decided to build a mobile application for this article. I also wanted to have something a little bit more exciting than a stock ticker app or Amazon product search app that will use the capabilities of the device it will be hosted on. Fortunately, I have just received a new GPS enabled SmartPhone, MOTO Q 9h. Having a GPS on my phone was the second reason for a mobile application example, because I have a good web service to complete the picture, which is described below.

Background

The demo application was tested on the MOTO Q 9h SmartPhone. Screen size on that phone is 320x240. The demo application is not orientation and/or size aware. It uses s static 320x240 bitmap for background image. However, it would not be hard to make it orientation/size aware.

Using the code

Make sure you have read the article and has everything that is needed. Download, unzip, compile, deploy, and enjoy! I have also included a pre-built CAB file.

ATL, WTL

I will be using the ATL library which has built-in support for Web Services. For the UI, I will be using one of my favorites - Windows Template Library. WTL is a lightweight extension to ATL. WTL provides template based wrappers for Windows controls and various Win32 APIs. WTL can be downloaded here. This article uses WTL 8.0, which integrates very well with Visual Studio 2005. After downloading and installing WTL, please run setup80.js from the AppWizMobile folder. That script will install the Application Wizard for Visual Studio 2005.

App Wizard Installation

Application

The following section provides a step by step instruction on how to create the application project and add a Web Service reference.

Creating the project using the WTL Wizard

Let's go ahead and start Visual Studio 2005.

If you have installed WTL and registered (by running setup80.js) the WTL Application Wizard, you will have a new "WTL Mobile Application Wizard" template under the Visual C++/WTL Project types category.

New Project Dialog

Name your project, select the location, and click "OK". I have named my project GPSDemographics. The WTL Mobile Application Wizard dialog has four pages:

1. General Overview Page

WTL Wizard - Overview

  • Click "Next >".

2. Platforms Page

WTL Wizard - Paltforms

  • Select Windows Mobile 6 Standard SDK, and remove all other SDKs from the "Selected SDKs" section.
  • Click "Next >".

3. Application Type Page

WTL Wizard - Application

  • Select "SDI Application"
  • Check "Enable ActiveX Control Hosting". This option sets some threading related defines in the project that are used by the ATL SOAP implementation. Leaving it unchecked will generate an error with the explanation of what define has to be set.
  • Check Generate .cpp files.
  • Click "Next >".

4. User Interface Features Page

WTL Wizard - UI

  • We will be using the View window, and "Generic window" as a View type.
  • Click "Finish".

Project settings

Since we are using WTL, we have to let Visual Studio know where the header files are located. There are several ways of doing that, which I am sure you are aware of. But just in case, I will show you how to do that using the project properties dialog.

Open the project properties window. Project -> Properties.

Please add the path to your WTL include folder for the following properties:

  1. Configuration Properties > C/C++ > General > Additional Include Directories.

    Project properties

  2. Configuration Properties > Resources > General >Additional Include Directories.

    Project properties

Web Service

I will be using CDYNE's Demographics Web Service. This service provides socio-economic data for a given address.

Web Service

The Web Service has 15 different methods. I will be using the GetLocationInformationByLatitudeLongitude method that takes the geographical position as an input.

Web Method

Adding Web Reference

Make sure your Solution Explorer is open.

  1. Right click on the project item in the Solution Explorer. Select "Add Web Reference...".

    Add Web Reference

  2. Type the Web Service URL or the WSDL URL into URL edit box. In our case, it is: http://ws.cdyne.com/DemographixWS/DemographixQuery.asmx.
  3. Click "Go".

    If everything is right, you will see the Web Service page. The name of the service in this case is DemographixQuery.

    Add Web Reference Dialog

  4. Click "Add Reference" to finish it.

At this point, you should be able to compile the project. You might receive an error that looks like this:

Error

This error is due to a name conflict. In order to fix it, add the following to the stdafx.h file just before #include "WebService.h".

#ifdef lstrlenW
#undef lstrlenW 
#endif

GPS

There are several ways of working with the GPS hardware. In this article, I will be using the GPS Intermediate Driver API. The main benefit of using the GPS Intermediate Driver is that it provides an intermediate layer that hides the complexity and implementation details of the actual GPS device. Applications that use the GPS Intermediate Driver architecture should work with any GPS hardware.

There are two modes to working with the GPS hardware under the GPS Intermediate Driver:

  • Parsed mode, using the GPS Intermediate Driver API.
  • Raw mode, using CreateFile/ReadFile/CloseHandle.

I will be using Parsed mode. The API consists of four functions:

  • GPSOpenDevice
  • GPSCloseDevice
  • GPSGetPosition
  • GPSGetDeviceState

GPSOpenDevice is the first call you have to make. The first two parameters are the most important for that function. These two parameters tell the driver what kind of information you are interested in as well as provide the means of notifying the application that the information is available. The first parameter is for location information: it is handle to an Event object, created using the CreateEvent function. The driver sets that event whenever it has new GPS location information. The second parameter is a handle to a Windows event as well. The driver uses that event to signal any device state changes.

GPSGetPosition and GPSGetDeviceState functions are for information retrieval. Whenever an event is set, use the appropriate function to get new information.

When you are done with the driver, close it by calling GPSCloseDevice.

Code

Pretty simple, right? OK, let's get to the actual implementation. In my demo application, I have a wrapper class for the GPS API - CGPSDevice. This class is responsible for creating event objects, opening the device and closing the device, as well as events monitoring. Events monitoring is done on a separate thread. Whenever an event is set, the monitoring thread calls one of the two methods that are wrapped into the custom interface, IGPSSink. That interface has to be implemented by the application. In my case, the application view implements that interface and reflects any changes in its OnPaint handler.

#pragma once
//IGPSSink.h

#include <gpsapi.h>

interface IGPSSink
{
    virtual HRESULT SetGPSPosition( GPS_POSITION gps_Position ) = 0;
    virtual HRESULT SetGPSDeviceInfo( GPS_DEVICE gps_Device ) = 0;
};

#pragma once
//GPSDevice.h

#include <GPSApi.h>
#include "IGPSSink.h"

class CGPSDevice
{
private:
    //Singleton instance
    static CGPSDevice * s_pInstance;

    //Device handle
    HANDLE m_hGPS_Device;

    //Event for location data updates
    HANDLE m_hNewLocationData;

    //Event for device state changes
    HANDLE m_hDeviceStateChange;

    //Thread's handle and id
    HANDLE m_hThread;
    DWORD m_dwThreadID;

    //Exit event
    HANDLE m_hExitThread;

    //Pointer to sink interface
    IGPSSink * m_pSink;

private:
    //Our wrapper is singleton make constructor private
    CGPSDevice(void);

    HRESULT StartThread();
    HRESULT StopThread();

    static CGPSDevice * Instance();
    static DWORD WINAPI GPSThreadProc(__opt LPVOID lpParameter);
public:

    ~CGPSDevice(void);

    static HRESULT TurnOn(IGPSSink * pSink);
    static HRESULT TurnOff();
};

//GPSDevice.cpp
#include "StdAfx.h"
#include "GPSDevice.h"

CGPSDevice * CGPSDevice::s_pInstance = NULL;

#define MAX_WAIT    5000
#define MAX_AGE     3000
#define GPS_CONTROLLER_EVENT_COUNT 3

CGPSDevice::CGPSDevice(void)
{
    m_pSink = NULL;
    m_hGPS_Device = NULL;
    m_hNewLocationData = NULL;
    m_hDeviceStateChange = NULL;
    m_hExitThread = NULL;
}

CGPSDevice::~CGPSDevice(void)
{
}

CGPSDevice * CGPSDevice::Instance()
{
    if( CGPSDevice::s_pInstance == NULL )
    {
        s_pInstance = new CGPSDevice();
    }

    return CGPSDevice::s_pInstance;
}

DWORD WINAPI CGPSDevice::GPSThreadProc(__opt LPVOID lpParameter)
{
    DWORD dwRet = 0;
    GPS_POSITION gps_Position = {0};
    GPS_DEVICE gps_Device = {0};


    CGPSDevice * pDevice = (CGPSDevice*) lpParameter;

    HANDLE gpsHandles[GPS_CONTROLLER_EVENT_COUNT] = 
    {    pDevice->m_hNewLocationData, 
    pDevice->m_hDeviceStateChange,
    pDevice->m_hExitThread
    };

    gps_Position.dwSize = sizeof(gps_Position);
    gps_Position.dwVersion = GPS_VERSION_1;

    gps_Device.dwVersion = GPS_VERSION_1;
    gps_Device.dwSize = sizeof(gps_Device);

    do
    {
        dwRet = WaitForMultipleObjects(
            GPS_CONTROLLER_EVENT_COUNT, 
            gpsHandles, FALSE, INFINITE);

        if (dwRet == WAIT_OBJECT_0)
        {
            dwRet = GPSGetPosition(
                pDevice->m_hGPS_Device, 
                &gps_Position, MAX_AGE, 0);

            if (ERROR_SUCCESS != dwRet)
                continue;
            else
                pDevice->m_pSink->SetGPSPosition(gps_Position);
        }
        else if (dwRet == WAIT_OBJECT_0 + 1)
        {
            dwRet = GPSGetDeviceState(&gps_Device);    

            if (ERROR_SUCCESS != dwRet)
                continue;
            else
                pDevice->m_pSink->SetGPSDeviceInfo(gps_Device);
        }
        else if (dwRet == WAIT_OBJECT_0 + 2)
            break;
        else
            ASSERT(0);

    } while( TRUE );

    return 0;
}

HRESULT CGPSDevice::StartThread()
{
    HRESULT hr = E_FAIL;
    DWORD dwRet = 0;

    m_hNewLocationData = CreateEvent(NULL, FALSE, FALSE, NULL);

    if ( m_hNewLocationData )
    {
        m_hDeviceStateChange = CreateEvent(NULL, FALSE, FALSE, NULL);

        if (m_hDeviceStateChange)
        {

            m_hExitThread = CreateEvent(NULL, FALSE, FALSE, NULL);
            if (m_hExitThread)
            {

                m_hThread = ::CreateThread(NULL, NULL, 
                    GPSThreadProc, this, NULL, &m_dwThreadID);

                if ( m_hThread )
                    hr = S_OK;
            }
        }
    }

    if( FAILED(hr) )
    {
        dwRet = GetLastError();
        hr = HRESULT_FROM_WIN32(dwRet);
    }

    if (FAILED(hr))
    {
        if (m_hNewLocationData)
        {
            CloseHandle(m_hNewLocationData);
            m_hNewLocationData = NULL;
        }

        if (m_hDeviceStateChange)
        {
            CloseHandle(m_hDeviceStateChange);
            m_hDeviceStateChange = NULL;
        }

        if (m_hExitThread)
        {
            CloseHandle(m_hExitThread);
            m_hExitThread = NULL;
        }
    }
    return hr;    
}

HRESULT CGPSDevice::StopThread()
{
    HRESULT hr = E_FAIL;
    DWORD dwRet = 0;

    if( SetEvent(m_hExitThread) )
    {
        dwRet = WaitForSingleObject(m_hThread, MAX_WAIT);
        if(WAIT_OBJECT_0 == dwRet)
            hr = S_OK;
    }

    if( FAILED(hr) )
    {
        dwRet = GetLastError();
        hr = HRESULT_FROM_WIN32(dwRet);
    }

    if (m_hNewLocationData)
    {
        CloseHandle(m_hNewLocationData);
        m_hNewLocationData = NULL;
    }

    if (m_hDeviceStateChange)
    {
        CloseHandle(m_hDeviceStateChange);
        m_hDeviceStateChange = NULL;
    }

    if (m_hExitThread)
    {
        CloseHandle(m_hExitThread);
        m_hExitThread = NULL;
    }

    if (m_hThread)
    {
        CloseHandle(m_hThread);
        m_hThread = NULL;
        m_dwThreadID = 0;
    }

    return hr;
}

HRESULT CGPSDevice::TurnOn(IGPSSink * pSink)
{
    if( !pSink )
        return E_INVALIDARG;

    CGPSDevice * pDevice = Instance();

    //We already have a device opened
    if( pDevice->m_hGPS_Device )
        return E_UNEXPECTED;

    if( pDevice->m_pSink != NULL )
        return E_UNEXPECTED;

    pDevice->m_pSink = pSink;

    HRESULT hr = pDevice->StartThread();

    if( SUCCEEDED(hr) )
    {
        pDevice->m_hGPS_Device = GPSOpenDevice( 
            pDevice->m_hNewLocationData, 
            pDevice->m_hDeviceStateChange, 
            NULL, NULL);

        if( pDevice->m_hGPS_Device )
            hr = S_OK;
        else
            hr = HRESULT_FROM_WIN32(GetLastError());
    }

    if( FAILED(hr) )
        TurnOff();

    return hr;
}

HRESULT CGPSDevice::TurnOff()
{
    CGPSDevice * pDevice = Instance();

    if( !pDevice->m_hGPS_Device )
        return E_UNEXPECTED;    

    HRESULT hr = pDevice->StopThread();


    pDevice->m_pSink = NULL;

    DWORD dwRet = GPSCloseDevice(pDevice->m_hGPS_Device);
    pDevice->m_hGPS_Device = NULL;

    if( SUCCEEDED(hr) )
    {
        if( ERROR_SUCCESS != dwRet )
            hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}

As I mentioned before, the view class implements IGPSSink.

//DemographicsView.h
class CDemographicsView : 
    public CWindowImpl<cdemographicsview />,
    public IGPSSink
{
...
    HRESULT SetGPSPosition(GPS_POSITION gps_Position);
    HRESULT SetGPSDeviceInfo(GPS_DEVICE gps_Device);
...
}

The most interesting method is SetGPSPosition. This is the place where the actual call to the Web Service is made. I have to point out that for performance reasons, I have a distance threshold of about 0.5 mile to limit the number of times the Web Service is called. It is a good practice to make sure that the Web Service is called only when it is needed.

//DemographicsView.cpp
HRESULT CDemographicsView::SetGPSPosition(GPS_POSITION gps_Position)
{
    ::EnterCriticalSection(&m_cs);

    m_gpsPosition = gps_Position;

    //We will update only if we have data
    if( m_gpsPosition.dwSatelliteCount != 0 )
    {
        double dist = (m_oldLat - gps_Position.dblLatitude) * 
            (m_oldLat - gps_Position.dblLatitude) +
            (m_oldLong - gps_Position.dblLongitude) * 
            (m_oldLong - gps_Position.dblLongitude);

        if( dist > DIST_THRESHOLD )
        {
            m_oldLat = gps_Position.dblLatitude;
            m_oldLong = gps_Position.dblLongitude;

            //This is the web service class that 
            //has been generated by Visual Studio
            DemographixQuery::CDemographixQuery webService;

            //Invoke the method we are interested in.
            HRESULT hr = webService.GetLocationInformationByLatitudeLongitude( 
                m_gpsPosition.dblLatitude,
                m_gpsPosition.dblLongitude, 
                CComBSTR(DEMO_LICENSE),
                &m_info);                
        }

        //Update the view
        Invalidate();
    }

    ::LeaveCriticalSection(&m_cs);

    return S_OK;
}

HRESULT CDemographicsView::SetGPSDeviceInfo(GPS_DEVICE gps_Device)
{
    ::EnterCriticalSection(&m_cs);

    m_gpsDevice = gps_Device;

    Invalidate();

    ::LeaveCriticalSection(&m_cs);

    return S_OK;
}

Resources

History

  • January 17, 2008 - Added "Contents" section.
  • January 16, 2008 - Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Valentin Ivanov
Chief Technology Officer CDYNE Corporation
United States United States
Developer, Maker and Tinkerer

Comments and Discussions

 
Generalsame problem and Aneesh Abraham Pinmember002s0s15-May-09 21:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140821.2 | Last Updated 18 Jan 2008
Article Copyright 2008 by Valentin Ivanov
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid