Welcome to MSDN Blogs Sign in | Join | Help

Visual Basic .Net Sample for WPD

We've created a Visual Basic .Net sample for WPD and posted it to CodePlex: Microsoft's open source project-hosting web site. Take a look at http://www.codeplex.com/wpdtempsensor

In addition to the Visual Basic project and source files, we've posted a document that describes the sample. This document also describes how you can address and resolve the COM Interop issues associated with .Net and WPD.

Posted by wpdblog | 2 Comments
Filed under:

Creating a Temperature Sensor Gadget for Windows Sidebar with C++

Summary: Written for application developers interested in displaying portable device data in Windows Sidebar. The paper and accompanying code describe a gadget for Windows Sidebar that displays the temperature reading from a temperature sensor device. Similar applications could be written to display: battery-level or available memory for a portable media device, contact lists from a mobile phone or PDA, route lists from a GPS device, and so on.

Introduction

Microcontrollers are used in a variety of applications from industrial to automotive to the home. One use that stretches across these boundaries is the microcontroller-based sensor which monitors parameters such as temperature, humidity, acceleration, distance, and light.

While the microcontroller device-universe has grown exponentially, there hasn’t been a common interface for integrating these devices with the PC. As a result, device and application developers were forced to write custom software to handle operations like data retrieval and display.

Microsoft recently introduced Microsoft® Windows® Portable Devices (WPD), a technology that addresses the challenges of integrating microcontroller-based devices with your PC. A microcontroller-based device can be as simple as a remote sensor or as complex as a mobile phone, a portable media player, or a digital camera. WPD consists of a driver model that runs in the user-mode driver framework (UMDF) and an API that simplifies the creation of applications for these devices.

This paper describes the use of the WPD API to create a Windows Console application that retrieves temperature data from a temperature sensor device. In addition, it describes a corresponding gadget for Windows Sidebar that consumes the data collected by the application.

The temperature sensor used by this application is based on the BASIC Stamp Activity Kit for Windows Portable Devices. This kit is offered by the Parallax Corporation in Rocklin, California. You can order the kit from the company’s Web site. (http://www.parallax.com/detail.asp?product_id=910-90005)

The console application obtains real-time temperature data from a Parallax temperature sensor device. It writes this data to a file on disk. A script in the gadget’s HTML file retrieves the data and renders it in Windows Sidebar.

The console application was written in Microsoft Visual Studio® 8 using the C++ programming language. The WPD gadget was written in HTML and Jscript®.

Downloading the Sample Application and Sidebar Gadget

To download the sample application, see this page on the Microsoft Downloads site.

To download the gadget, see this page on the Microsoft Downloads site.

Running the Sample Application

The sample application requires that the WPD temperature sensor driver (WpdTempSensorDriver.dll) is installed and that a temperature sensor device is connected over a standard RS232 port.

The WPD temperature sensor driver exposes a Temperature Sensor object. This object, in turn, exposes three programmatic elements: two properties and an event. These elements are described in the following table.

Programmatic Element

Description

Interval property

This read/write property specifies the frequency at which the device should return the current temperature. (This property is specified in milliseconds.)

Temperature property

This read-only property specifies the current temperature in degrees Kelvin.

Temperature-reading event

This event is fired each time the device retrieves the current temperature. (The Interval property specifies the frequency at which the device will fire this event.)

 

The sample console application, described in this white paper, registers to receive the Temperature-reading event notification each time it’s fired. Upon receiving the notification, the application writes the temperature data as HTML and saves it in an ASCII text file. The gadget, in turn, reads the HTML from this file and renders it in Sidebar.

Building and Installing the Temperature Sensor Device

The temperature sensor device referenced in this paper is based on a Parallax BS2 microcontroller and an AD592 Temperature Sensor transducer.

Parallax supplies a complete hardware kit that includes all of the required components. To order the kit, see this page on the Parallax site. (http://www.parallax.com/detail.asp?product_id=910-90005)

You can download the Basic Stamp source code for the temperature sensor device from the WHDC site. (http://www.microsoft.com/whdc/device/media/WPD_drv.mspx>

Building and Installing the WPD Temperature Sensor Driver

In order to install the driver, download the source code and build it. For information about downloading the driver, see the following topic on the WHDC Web site. (http://www.microsoft.com/whdc/device/media/WPD_drv.mspx)

Installing the Gadget

The download package referenced above in this article contains a file named WpdTemperatureSensor.gadget. Copy this file to your local machine, open Windows Explorer, and double-click this file. You will be asked whether you want to install the gadget. Click Install.

The gadget will be installed in the Desktop\\AppData\Local\Microsoft\Windows Sidebar\Gadgets folder.

Now you’re ready to build and run the sample application.

Building and Running the Sample Application

The download package included with this white paper contains the project, source, and header files for the sample console application that retrieves temperature data from the sensor.

After you’ve installed the temperature sensor device, driver, and gadget files, you’ll need to extract the contents of the download package to your development machine. This includes the following files.

·         stdafx.h

·         stdafx.cpp

·         deviceevents.cpp

·         sidebar_console.cpp

·         sidebar_console.vcproj

For a description of these files, see the Temperature Sensor Console Application section that follows.

If you haven’t installed the Windows SDK for Windows Vista, you’ll need to do so before building the project. For information about installing the Windows SDK, see this Web page. (http://www.microsoft.com/downloads/details.aspx?familyid=7614fe22-8a64-4dfb-aa0c-db53035f40a0&displaylang=en)

Once the Windows SDK is installed, open the project file (sidebar_console.vcproj) in Visual Studio 8. Select the Build_Sidebar console option from the Build menu. Visual Studio 8 will build an executable file and place it in the \Debug subdirectory. You can test the gadget and verify that it works by selecting Start Debugging from the Debug menu.

WPD Application and Gadget Overview

The WPD application and gadget consists of two separate processes: a server and a client. The server is a console application which monitors the temperature sensor device for event notifications. Each time this application receives a notification, it writes corresponding temperature data formatted as HTML to a file on disk. The client is a script running in the gadget’s HTML file. This script retrieves the data from the file created by the console application and renders it in Sidebar.

The WPD Console Application registers to receive temperature events from the device. These events are fired at an interval specified by the device’s Interval property. (The default value for this property is 2,000 milliseconds.)

The event handler in the console application writes the temperature as a string of HTML to an ASCII text file on the local disk. This string has the following appearance.

<P>Office Temperature<P>304&deg; Kelvin

The gadget’s script executes every 3,000 milliseconds. The script reads the string of HTML created by the console application and inserts that string into the body of the gadget’s HTML.

The Temperature Sensor Console Application

The temperature sensor console application is a simple application that accomplishes the following tasks.

·         Searches for the temperature sensor device and opens a connection if the device is found.

·         Registers to receive event notifications from the device.

·         Enters a loop and responds to any one of three inputs from the user.

·         While listening for user input, and for as long as the event registration is intact, writes temperature data to the local disk.

The temperature sensor application project consists of the following files:

File

Description

stdafx.h

Contains #defines for the target platform as well as #includes for the standard system include file.

deviceevents.cpp

Implements IPortableDeviceEventCallback which is required by any WPD application that registers to receive device events.

sidebar_console.cpp

Implements the entry point for the console application and the helper functions which perform tasks like searching for the device and establishing a connection.

stdafx.cpp

Contains #include for stdafx.h

sidebar_console.vcproj

The Visual Studio 8 project file.

 

Most of work accomplished by the application is found in the module named sidebar_console.cpp; the exception is the event handling code which is found in the module deviceevents.cpp.

Opening a Connection to the Temperature Sensor Device

The WPD API provides a set of interfaces that a Windows programmer can use to accomplish tasks like enumerating connected devices, opening a device, closing a device, enumerating objects on a device, reading and writing properties on a device, sending a command to a device, and registering to receive events from a device.

One of the primary interfaces is the IPortableDevice interface which supports the methods a programmer calls to open a device, receive events, send a command, and so on.

The first task the sample application accomplishes is to open (establish a connection to) the temperature sensor device. It does this by calling the IPortableDevice::Open method. This method takes two parameters: a pointer to a string that specifies a special identifier for the device and a pointer to an array of key/value pairs that specify information about the calling application.

hr = CoCreateInstance(CLSID_PortableDevice,

                         NULL,

                         CLSCTX_INPROC_SERVER,

                         IID_IPortableDevice,

                         (VOID**) &pIPortableDevice);

   if (SUCCEEDED(hr))

   {

       if (pIPortableDevice != NULL)

       {

            hr = (pIPortableDevice)->Open(DeviceID, pClientInformation);

           if (FAILED(hr))

           {

               // Release the IPortableDevice interface

               // because we cannot proceed with an

               // unopen device.

               pIPortableDevice = NULL;

            }

           else

           {

               printf("Device successfully opened.\n\r\n\r");

           }

        }

In order to retrieve the device identifier DeviceID, which is passed as the first parameter to IPortableDevice::Open, the application first needs to determine whether the temperature sensor device is connected to the PC and, if it is, retrieve the identifier.

The FindDevice helper function in the Sidebar_Console.cpp module accomplishes this work. The FindDevice function has two arguments: the first is a “friendly” name for the target device; the second is the special Plug and Play identifier that the WPD API returns as a match for this friendly name.

The FindDevice helper function accomplishes the following tasks.

1.       Retrieves a count of devices connected to the PC.

2.      Retrieves an array of friendly names for each device.

3.      Compares the friendly name passed in its first argument to the friendly names for each of the connected devices.If a match is found, it retrieves the Plug and Play identifier for the target device.

Retrieving a Count of Connected Devices

The first task of the FindDevice function is the retrieval of a count of connected devices. This is done in two stages: first, by creating an IPortableDeviceManager object, and second, by calling the IPortableDeviceManager::GetDevices method. (In order to retrieve a count of connected devices, the first argument to GetDevices is set to NULL.)

// CoCreate the IPortableDeviceManager interface to enumerate

// portable devices and to get information about them.

hr = CoCreateInstance(CLSID_PortableDeviceManager,

              NULL,

              CLSCTX_INPROC_SERVER,

              IID_IPortableDeviceManager,

              (VOID**) &pPortableDeviceManager);

if (FAILED(hr))

{

     printf("! Failed to CoCreateInstance CLSID_PortableDeviceManager\n\r\n\r");

     return hr;

}

 

// First, pass NULL as the LPWSTR array pointer to get the total

// number of devices found on the system.

if (SUCCEEDED(hr))

{

    hr = pPortableDeviceManager->GetDevices(NULL, &cPnPDeviceIDs);

    if (FAILED(hr))

    {

        printf("! Failed to get number of devices on the system\n\r\n\r");

        return hr;

    }

}

Retrieving a Plug and Play name for the Temperature Sensor

WPD supports two types of device names: friendly names and Plug and Play names. The friendly names are the names applications display to the user. The friendly name for the temperature sensor device is: “Parallax BS2 Temperature Sensor”. The Plug and Play names are used internally by WPD to identify devices.  The Plug and Play name for the temperature sensor device is:

\\?\root#wpd#0000#{6ac27878-a6fa-4155-ba85-f98f491d4f33}

This string uniquely identifies the device instance and the device interface, and it is defined as part of the Windows Driver Model (WDM). For more information, refer to this MSDN topic. (http://msdn2.microsoft.com/en-us/library/ms791083.aspx)

After retrieving a count of connected devices and storing it in the cPnPDeviceIDs variable, FindDevice iterates through the available devices until it finds a device whose friendly name matches the string “Parallax BS2 Temperature Sensor”. If this match is made, FindDevice returns the corresponding Plug and Play name in the DeviceID argument.

        if (cPnPDeviceIDs > 0)

        {

            pPnpDeviceIDs = new LPWSTR[cPnPDeviceIDs];

            if (pPnpDeviceIDs != NULL)

            {

                DWORD dwIndex = 0;

 

                hr = pPortableDeviceManager->GetDevices(pPnpDeviceIDs, &cPnPDeviceIDs);

                if (SUCCEEDED(hr))

                {

                    // For each device found, retrieve the friendly

                    // name and compare it to the submitted name.

                    for (dwIndex = 0; dwIndex < cPnPDeviceIDs; dwIndex++)

                    {

                        CAtlStringW FriendlyName;

                        hr = RetrieveFriendlyName(pPortableDeviceManager, pPnpDeviceIDs[dwIndex], FriendlyName);

                        if ((hr == S_OK) && (DeviceName.CompareNoCase(FriendlyName) == 0))

                        {

                             DeviceID = pPnpDeviceIDs[dwIndex];

                             bFoundDevice = TRUE;

                             break;

                        }

                    }

                }

                else

                {

                    printf("! Failed to get the device list from the system\n\r\n\r");

                }

 

                if (SUCCEEDED(hr) && (!bFoundDevice))

                {

                    printf("! Failed to find a matching device\n\r\n\r");

                    hr = E_FAIL;

                }

 

 

                // Free all returned PnPDeviceID strings by using

                // CoTaskMemFree.

                // NOTE: CoTaskMemFree can handle NULL pointers, so

                //       no NULL check is needed.

                for (dwIndex = 0; dwIndex < cPnPDeviceIDs; dwIndex++)

                {

                    CoTaskMemFree(pPnpDeviceIDs[dwIndex]);

                    pPnpDeviceIDs[dwIndex] = NULL;

                }

 

                // Delete the array of LPWSTR pointers.

                delete [] pPnpDeviceIDs;

                pPnpDeviceIDs = NULL;

        }

        else

        {

            printf("! Failed to allocate memory for LPWSTR array\n\r\n\r");

            hr = E_OUTOFMEMORY;

        }

       }

 

    return hr;

}

The application passes the returned Plug and Play name as the first argument to IPortableDevice::Open when it opens the device.

Note that the FindDevice function calls the RetrieveFriendlyName helper function to retrieve a friendly name for each Plug and Play name returned by IPortableDeviceManager::GetDevices. The RetrieveFriendlyName function calls the IPortableDeviceManager::GetFriendlyName method twice to retrieve the friendly name string. The first time, it calls this method to retrieve a count of characters in the friendly name string; the second time, it calls this method to retrieve the actual string.

    // First, pass NULL as the LPWSTR return string parameter to get

    // the total number of characters to allocate for the string

    // value.

    hr = pPortableDeviceManager->GetDeviceFriendlyName(pPnPDeviceID, NULL, &cchFriendlyName);

    if (FAILED(hr))

    {

        printf("! Failed to get number of characters for device friendly name.\n\r\n\r");

        return hr;

    }

 

    // Second, allocate the number of characters needed and retrieve

    // the string value.

    if ((hr == S_OK) && (cchFriendlyName > 0))

    {

        wszFriendlyName = new WCHAR[cchFriendlyName];

        if (wszFriendlyName != NULL)

        {

            hr = pPortableDeviceManager->GetDeviceFriendlyName(pPnPDeviceID, wszFriendlyName, &cchFriendlyName);

            if (SUCCEEDED(hr))

            {

                FriendlyName = wszFriendlyName;

            }

            else

            {

                printf("! Failed to get device friendly name\n\r\n\r");

            }

 

            // Delete the allocated friendly name string.

            delete [] wszFriendlyName;

            wszFriendlyName = NULL;

        }

...

Receiving Device Events

The WPD API and driver model were designed so devices can issue events and applications can register to receive notifications when these events occur. Applications that receive events must implement the IPortableDeviceEventCallback interface. This interface supports a single OnEvent method. The driver calls this method each time an event is fired. However, in order for a driver to call this method, an application first needs to register with the driver. An application registers with the driver by calling the IPortableDevice::Advise method. The third argument for this method, pCallback, is a pointer to the IPortableDeviceEventCallback interface that the application implemented.

The temperature sensor device issues a single temperature-reading event that the sample application registers to receive. Each time the driver calls the OnEvent method, the sample application writes the received temperature to a local file on disk. The gadget’s script then reads from this file to update the gadget with the most recent office temperature.

Implementing IPortableDeviceEventCallback

The sample application implements the IPortableDeviceEventCallback interface in the DeviceEvents.cpp module. Most of the code in this module was taken from the WpdApiSample application that ships with the Windows SDK. The following changes were made to the original file.

·         The TEMPERATURE_SENSOR_READING property key was defined and declared.

·         The OnEvent method was modified to process the TEMPERATURE_SENSOR_READING property and write the new temperature value to disk.

The IPortableDeviceEventCallback interface is implemented in the CPortableDeviceEventsCallback class in DeviceEvents.cpp. This class supports the following methods.

Method

Description

CPortableDeviceEventsCallback

Object constructor

~CPortableDeviceEventsCallback

Object destructor

QueryInterface

Returns a pointer to the IPortableDeviceEventCallback interface.

AddRef

Increments the object’s reference count.

Release

Decrements the object’s reference count.

OnEvent

Event handler. WPD will call this method if the application is registered to receive event notifications.

 

In addition to implementing IPortableDeviceEventCallback, the DeviceEvents.cpp module also contains two functions that the application calls in order to register and unregister for event notifications. These functions are described in the following table.

Function

Description

RegisterForEventNotifications

Registers the application to receive event notifications. (This method is called when the application first starts.It’s also called if the user requests registration by entering the number 2 at the command prompt.)

UnregisterForEventNotifications

Unregisters the application from receiving event notifications. (This method is called if the user requests cancellation of registration by entering the number 1 at the command prompt.)

 

Implementing the IPortableDeviceEventCallback::OnEvent Method

Once an application registers to receive event notifications, the WPD device driver will call the IPortableDeviceEventCallback::OnEvent method whenever the device fires an event. Each time the driver calls this method, it passes data associated with the given event in an IPortableDeviceValues interface. The pEventParameters argument is a pointer to this interface.

The temperature sensor device fires one event per temperature reading update, and the accompanying data is a single, signed integer value which specifies the current temperature in degrees Kelvin. The sample application retrieves this value by calling the IPortableDeviceValues::GetSignedIntegerValue method and passing the property key for the TEMPERATURE_SENSOR_READING property as the first argument. (This property key was declared and defined at the top of DeviceEvents.cpp.)

HRESULT __stdcall OnEvent(

    IPortableDeviceValues* pEventParameters)

{

    HRESULT hr = S_OK;

    LONG i=0;

    HANDLE hFile;

    DWORD dwBytesWritten;

    char strHtmlPrefix[]="<P>Office Temperature<P>";

    char strHtmlSuffix[]="&deg; Kelvin";

    char strHtml[40];

 

    if (pEventParameters != NULL)

    {

        pEventParameters->GetSignedIntegerValue(TEMPERATURE_SENSOR_READING, &i);

        sprintf_s(strHtml, "%s%d%s\0", strHtmlPrefix, i, strHtmlSuffix);

 

        hFile = CreateFileW(_T("c:\\temp\\tempdata.txt"),

            GENERIC_WRITE,

            0,

            NULL,

            CREATE_ALWAYS,

            FILE_ATTRIBUTE_NORMAL,

            NULL);

 

        if (WriteFile(hFile, strHtml, sizeof(strHtml), &dwBytesWritten, NULL))

        {

            printf("Current temperature: %d Kelvin\n\r", i);

            printf("Temperature written to target file.\n\r\n\r");

        }

        else

        {

            printf("Temperature not written to target file.\n\r");

            printf("WriteFile failed with error %d.\n\r\n\r", GetLastError());

        }

        CloseHandle(hFile); // We need to close the handle.

    }

     return hr;

}

Upon retrieving the current temperature, the OnEvent method writes a string of HTML to a temporary file on disk (c:\temp\tempdata.txt). The gadget reads this string of HTML from the temporary file and incorporates it into the gadget’s own HTML. In addition, the OnEvent method writes the current temperature to the console window.

Implementing the RegisterForEventNotifications Function

The sample application supports a single function, RegisterForEventNotfications, which it calls at startup and if the user explicitly requests registration. This function performs the following tasks.

4.      Examines the event registration cookie (g_strEventRegistrationCookie) to determine whether the application is already registered.

5.      If not, creates an instance of the CPortableDeviceEventsCallback object.

6.      Calls the IPortableDevice::Advise method to complete the registration.

7.      If registration is successful, assigns the event cookie returned by the Advise method to the g_strEventRegistrationCookie variable.

void RegisterForEventNotifications(IPortableDevice* pDevice)

{

    HRESULT                        hr = S_OK;

    LPWSTR                         wszEventCookie = NULL;

    CPortableDeviceEventsCallback* pCallback = NULL;

 

    if (pDevice == NULL)

    {

        return;

    }

 

    // Check to see if we already have an event registration cookie.

    // If so, then avoid registering again.

    // NOTE: An application can register for events as many times as.

    //       wanted. This sample only keeps a single registration

    //       cookie around for simplicity.

    if (g_strEventRegistrationCookie.GetLength() > 0)

    {

        printf("This application has already registered to receive device events.\n\r\n\r");

        return;

    }

 

    // Create an instance of the callback object. This will be

    // called when events are received.

    if (hr == S_OK)

    {

        pCallback = new CPortableDeviceEventsCallback();

        if (pCallback == NULL)

        {

            hr = E_OUTOFMEMORY;

            printf("Failed to allocate memory for IPortableDeviceEventsCallback object, hr = 0x%lx\n\r\n\r",hr);

        }

    }

 

    // Call Advise to register the callback and receive events.

    if (hr == S_OK)

    {

        hr = pDevice->Advise(0, pCallback, NULL, &wszEventCookie);

        if (FAILED(hr))

        {

            printf("! Failed to register for device events, hr = 0x%lx\n\r\n\r",hr);

        }

    }

 

    // Save the event registration cookie if event registration was

    // successful.

    if (hr == S_OK)

    {

        g_strEventRegistrationCookie = wszEventCookie;

    }

 

    // Free the event registration cookie if one was returned.

    if (wszEventCookie != NULL)

    {

        CoTaskMemFree(wszEventCookie);

        wszEventCookie = NULL;

    }

 

    if (hr == S_OK)

    {

        printf("This application has registered for device event notifications.\r\n\r\n");

    }

 

    // If a failure occurs, remember to delete the allocated callback

    // object if one exists.

    if (pCallback != NULL)

    {

        pCallback->Release();

    }

}

Implementing the UnregisterForEventNotifications Function

The sample application supports a single function, UnregisterForEventNotfications, which it calls if the user explicitly cancels registration. This function performs the following tasks.

8.      Calls the IPortableDevice::Unadvise method and passes the registration cookie (which is also the event cookie returned by the IPortableDevice::Advise method).

9.      Sets the internal event registration-cookie variable g_strEventRegistrationCookie to an empty string.

10.   

void UnregisterForEventNotifications(IPortableDevice* pDevice)

{

    HRESULT hr = S_OK;

 

    if (pDevice == NULL)

    {

        return;

    }

 

    hr = pDevice->Unadvise(g_strEventRegistrationCookie);

    if (FAILED(hr))

    {

        printf("! Failed to unregister for device events using registration cookie '%ws', hr = 0x%lx\n\r\n\r",g_strEventRegistrationCookie.GetString(), hr);

    }

 

    if (hr == S_OK)

    {

        printf("This application used the registration cookie '%ws' to unregister from receiving device event notifications\n\r\n\r", g_strEventRegistrationCookie.GetString());

    }

 

    g_strEventRegistrationCookie = L"";

}

Handling User Input

The sample application displays a simple menu in the console window when it begins execution. This menu has three options corresponding to three integer values.

Option/Value

Description

1

Unregister for device notifications. (The application calls the UnregisterForEventNotifications function.)

2

Register for device notifications. (The application calls the RegisterForEventNotifications function.)

99

Exit. (The application exits.)

 

The code that supports the menu and its options is found in the ProcessInput function in the Sidebar_Console.cpp module.

void ProcessInput(CComPtr<IPortableDevice> pIPortableDevice)

    {

        HRESULT hr              = S_OK;

        UINT    uiSelection     = 0;

        CHAR    szSelection[81] = {0};

 

        printf("\n\n");

        printf("===============================================\r\n");

        printf("1.  Unregister for device notifications.\r\n");

        printf("2.  Register for device notifications.\r\n");

        printf("99. Exit.\r\n");

        printf("===============================================\r\n");

        printf("\n\n");

 

        while (uiSelection != 99)

        {

            ZeroMemory(szSelection, sizeof(szSelection));

   

            hr = StringCbGetsA(szSelection,sizeof(szSelection));

 

            if (SUCCEEDED(hr))

            {

                uiSelection = (UINT) atoi(szSelection);

                switch (uiSelection)

                {

                    case 1:

                        // Unregister to receive device events

                        UnregisterForEventNotifications(pIPortableDevice);

                    break;

                    case 2:

                        // Register to receive device events

                        RegisterForEventNotifications(pIPortableDevice);

                    default:

                    break;

                }

            }

            else

            {

                printf("! Failed to read menu selection string input, hr = 0x%lx\n\r\n\r",hr);

            }

        }

    }

The Temperature Sensor Gadget

The temperature sensor gadget displays the current office temperature in degrees Kelvin in Windows Sidebar.

If you’ve never created a gadget for Windows Sidebar before, refer to the Gadget Development Overview. (http://msdn2.microsoft.com/en-us/library/aa965850.aspx)

The Temperature Sensor Gadget Files

The temperature sensor gadget consists of three files: an HTML file, an XML file, and a JPEG file. These files are described in the following table.

File Name

Description

UpdateTemperatureReading.htm

An HTML file that contains the script which retrieves the updated temperature data from the local disk every 3,000 milliseconds.

gadget.xml

The gadget manifest. This file specifies properties such as the name of the gadget, the name of its HTML file, a string of descriptive text, and so on.

background.jpg

A 163 x 58 pixel image that produces the gadget’s two-tone blue background color.

 

Retrieving the Current Temperature

The gadget’s HTML file, UpdateTemperatureReading.htm, contains a script which opens the file created by the console application (c:\temp\tempdata.txt) and copies the contents of this file into a span element identified as “temperature_data” within the body of the gadget’s HTML.

This script creates a FileSystemObject which it uses to open and read the text file. It uses the OpenTextFile method to open the file and the ReadAll method to retrieve its contents.

The script uses the setTimeout method on the window object to recursively execute the update function every 3,000 milliseconds. (This function contains the code that retrieves the contents of c:\temp\tempdata.txt.)

<html>

 

<head>

 

<script>

var fileSystemObject = null;

 

function loaded()

{

       fileSystemObject = new ActiveXObject("Scripting.FileSystemObject");

       update();

}

 

function update()

{

      var textStream = fileSystemObject.OpenTextFile("c:\\temp\\tempdata.txt");

      try

          {

             temperature_data.innerHTML = textStream.ReadAll();

           }

         finally

           {

             textStream.Close();

             window.setTimeout(update, 3000);

           }

}

 

</script>

 

<style>

      body

        {

               margin: 0;

               width: 125px;

               height: 62px;

               font: 'normal 8pt Arial';

               line-height: 12pt;

               text-align:center;

      }

 

</style>

 

</head>

 

<body onload="loaded()">

<g:background src="background.png">

<span id="temperature_data"></span>

</g:background>

</body>

</html>

The HTML file also contains a description of the gadget’s window between the <style> and </style> tags that follow the script. Note that the width and height attributes match the width and height of the background image (background.png). In addition to specifying the size of the gadget’s window, the attributes found here specify the font used to render text, the line height (which creates padding between the two lines of text), and the text alignment.

The g:background element specifies the source file (background.png) for the gadget’s background image.

The span element “temperature_data” identifies the location of the HTML which is copied from c:\temp\tempdata.txt by the Update function.

For More Information

·         For general information about developing gadgets for Windows Sidebar, see the Gadget Development Overview (http://msdn2.microsoft.com/en-us/library/aa965850.aspx).

·         To learn more about the BASIC Stamp Activity Kit for Windows Portable Devices, refer to the Parallax site (http://www.parallax.com/detail.asp?product_id=910-90005).

Web addresses can change, so you might be unable to connect to the Web site or sites mentioned here.

 

 

 

Posted by wpdblog | 1 Comments

Creating a Temperature-Sensor Gadget for Windows Sidebar with C#

This article was written for application developers who are interested in displaying portable device data in the Windows Sidebar. It describes a Microsoft .Net application written in C# as well as a gadget for Windows Sidebar. The application was written using Microsoft Visual Studio 8. The WPD gadget was written in HTML and JScript.

The .Net application retrieves temperature data from a Parallax temperature-sensor device and writes this data to a file on the local disk. A script in the gadget's HTML file reads the contents of this local file and displays the temperature in the Sidebar. 

The temperature sensor used by this application is based on the BASIC Stamp Activity Kit for Windows Portable Devices. This kit is offered by the Parallax Corporation in Rocklin, California. You can order the kit from the company’s website:

http://www.parallax.com/Store/Microcontrollers/BASICStampModules/tabid/134/txtSearch/WPD/List/1/ProductID/430/Default.aspx?SortField=ProductName%2cProductName

Running the Application and Viewing the Gadget

The application and gadget described in this article require that you install the WPD temperature sensor driver (WpdTempSensorDriver.dll) and that you connect the temperature sensor device over a standard RS232 port.

The WPD temperature-sensor driver exposes a Temperature Sensor object. This object, in turn, exposes three programmatic elements: two properties and an event. These elements are described in the following table.

Programmatic Element

Description

Interval property

This read/write property specifies the frequency at which the device should return the current temperature. (This property is specified in milliseconds.)

Temperature property

This read-only property specifies the current temperature in degrees Kelvin.

Temperature-reading event

This event is fired each time the device retrieves the current temperature. (The Interval property specifies the frequency at which the device will fire this event.)

Building and Installing the Temperature Sensor Device

The temperature sensor device, referenced in this article, is based on a Parallax BS2 microcontroller and an AD592 Temperature Sensor transducer.

Parallax supplies a complete hardware kit that includes all of the required components. To order the kit, see the following page on the Parallax site:

http://www.parallax.com/Store/Microcontrollers/BASICStampModules/tabid/134/txtSearch/WPD/List/1/ProductID/430/Default.aspx?SortField=ProductName%2cProductName

You can download the Basic Stamp source code for the temperature sensor device from the WHDC site: http://www.microsoft.com/whdc/device/media/WPD_drv.mspx

Building and Installing the WPD Temperature Sensor Driver

In order to install the driver, you’ll first need to download the source code and build it. For information about downloading the driver, see the following topic on the WHDC web site: http://www.microsoft.com/whdc/device/media/WPD_drv.mspx

Installing the Gadget

The sidebar gadget is found on the Microsoft Download Center at: http://www.microsoft.com/downloads/details.aspx?FamilyID=4deae5b8-fb78-421e-9fa0-1ee1336fa441&DisplayLang=en

Download this file to your local machine, open Windows Explorer, and double-click on the icon. Vista will ask you whether or not you want to install the gadget. Choose the Install button.

The gadget will be installed under the Desktop\<user name>\AppData\Local\Microsoft\Windows Sidebar\Gadgets folder.

Installing the Windows SDK

Because there’s no native .Net support for the WPD API, you’ll need to use the WPD COM interfaces if you are writing a .Net application. Your application will access these interfaces through the COM Interoperability (interop) feature of .Net.

When working with the type libraries for COM objects, it’s sometimes necessary to make minor revisions to the type library metadata. Microsoft provides tools you can use to make these changes in the Windows SDK; so, you’ll want to download this SDK onto your development machine.

You can download the Windows SDK from the Microsoft Download Center. http://www.microsoft.com/downloads/details.aspx?FamilyId=7614FE22-8A64-4DFB-AA0C-DB53035F40A0&displaylang=en

A Client/Server Model

The .Net application described in this article is a server while the gadget acts as its client. The server monitors the temperature sensor device for event notifications. Each time it receives a notification, this application writes corresponding temperature data to a file on disk. The data is formatted as HTML. The client (or gadget) retrieves the data from the file created by the application and renders it in the sidebar.

The .Net application sets a timer event to fire every three seconds. Each time the event fires, the application retrieves the current temperature from the WPD temperature-sensor device. The application then writes the temperature as a string of HTML to an ASCII text file on the local disk. This string has the following appearance:

<P>Office Temperature<P>70&deg; Farenheit

 

The gadget’s script executes every 3,000 milliseconds; this script reads the string of HTML created by the application and inserts the string into the body of the gadget’s HTML

The Temperature-Sensor Application

The temperature-sensor application described in this article is a simple application that accomplishes the following tasks:

·         It searches for the temperature-sensor device and opens a connection if it’s found.

·         It sets a timer event which is fired ever 3 seconds.

·         Each time the event fires, it retrieves the current temperature from the connected device and writes it to local disk.

A Windows temperature-sensor application project, written in C# and .Net, might consist of the following files:

File

Description

Form1.cs

Searches for the temperature-sensor device and opens a connection if it’s found. Sets the timer event and retrieves the current temperature from the device, and, writes the temperature value to a file on local disk.

Form1.Designer.cs

Defines the InitializeComponent method (a method describing the application’s form and its contents).

Form1.resx

Defines the resources found on the form (a single timer resource).

Interop.PortableDeviceApiLib.dll

Revised interop assembly that correctly handles the call to the IPortableDeviceManager::GetDevices method. (See http://blogs.msdn.com/dimeby8 and the Enumerating WPD devices in C# topic.)

Portabledeviceconstants.cs

A file containing definitions for the property-keys used by the WPD API as well as the property key for the TEMP_SENSOR_READING property.

Program.cs

Defines the application’s main entry point.

WpdSidebarGadget.csproj

The Visual Studio 8 project file.

Accessing the WPD API through .Net

Because there’s no native .Net support for the WPD API, you’ll need to use the WPD COM interfaces through the COM Interoperability (interop) feature of .Net. This feature lets your .Net application “think” that it’s accessing a .Net version of WPD, when, in fact, it’s accessing the unmanaged objects instead.

COM objects (like the WPD API) are described by type libraries. These libraries give the Visual Studio 8 compiler a definition of the types, interfaces, and methods that WPD supports.

When working with the type libraries for COM objects, it’s sometimes necessary to make minor revisions to the metadata associated with these libraries. Microsoft provides the tools necessary to make these changes in the Windows SDK, so, you’ll want to download this SDK onto your development machine.

Your application will use the COM interfaces found in the WPD API to accomplish tasks like: enumerating connected devices, opening a device, closing a device, enumerating objects on a device, reading and writing properties on a device, sending a command to a device, and registering to receive events from a device.

In order to access these interfaces and their methods from your C# .Net application, you’ll need to add references to the PortableDeviceApiLib and the PortableDeviceTypes Type Libraries in your Visual Studio 8 .Net project.  You’ll add these references through the Project/Add Reference menu and the COM tab found on the associated dialog.

Once you add the references to these two type libraries, you’ll need to resolve several marshalling restrictions in the Interop assembly that Visual Studio generates for the PortableDeviceApiLib. (You’ll use a couple of tools found in the Windows SDK to resolve these restrictions.)

Resolving the Marshalling Restrictions

Create a Visual Studio 8 project for a C# Windows application and add the references to the two type libraries listed above (PortableDeviceApiLib and PortableDeviceTypes). Next, build your application.

When you built your application, Visual Studio invoked the type library importer and created what’s called an Interop Assembly for each of the type libraries that you’d added. You’ll find these assemblies in the \bin\debug folder of your project (if you built a debug version of your application); otherwise, you’ll find these assemblies in the \bin\release folder.

An Interop Assembly lists the COM types that your application can access via COM interoperability. These assemblies contain metadata that the .Net compiler uses to resolve method calls.

Unfortunately, some of the metadata generated by the type library importer is incorrect for C# applications. For example, if you examine the metadata for the IPortableDeviceManager::GetDevices method, you’ll see that the first argument (an array of string pointers) is specified to be:

[in][out] string&  marshal( lpwstr) pPnPDeviceIDs 

You’ll need to replace unary & operator with the square brackets which are used to specify an array type in C#. And, you’ll need to replace the lpwstr type after the marshal keyword with the [] operator. So, the new description of this argument would appear as below:

[in][out] string[]  marshal([]) pPnPDeviceIDs 

In general, for WPD methods that take an array or buffer as an argument, you’ll want to replace any occurrence of the unary & operator found in the type-libraries metadata with the square brackets. (And, you may also need to add the marshal keyword and arguments.)

To edit the metadata in the interop assembly, you’ll need to use the IL Disassembler (ILDASM.EXE) and the IL Assembler (ILASM.EXE). The disassembler creates an editable text file from an interop assembly file. After you’ve edited this text file (and revised the metadata), you’ll rebuild the interop assembly using the assembler.

The following steps describe how you would create an editable text file from the PortableDeviceApi interop assembly, how you would revise the metadata for the GetDevices and GetDeviceFriendlyName methods, and how you would create a new, revised assembly.

1.     Open an instance of the Windows SDK command shell.

2.     Use the “cd” command to change directory to your project’s \debug folder that contains the file Interop.PortableDeviceApiLib.dll

3.     Use the following command to disassemble the PortableDeviceAPI interop assembly into an editable text file:
ildasm Interop.PortableDeviceApiLib.dll /out:pdapi.il

4.     Open the text file pdapil.il in notepad and replace all occurrences of the pPnPDeviceIDs argument for the GetDevices method with the correct metadata. Replase the following string:
      instance void  GetDevices([in][out] string&  marshal( lpwstr) pPnPDeviceIDs,
with the revised string:
      instance void  GetDevices([in][out] string[]  marshal([]) pPnPDeviceIDs,

5.     For the sample described in this article, you’ll also want to  make the following changes to the description of the pDeviceFriendlyName argument for the GetDeviceFriendlyName method. Replace the following string:
   
[in][out] uint16& pDeviceFriendlyName
with the revised string:
   
[in][out] uint16[] marshal( []) pDeviceFriendlyName

6.     Assemble the revised metadata text file into a new interop assembly using the following command:
ilasm pdapi.il /dll /output=Interop.PortableDeviceApiLib.dll

7.     Move the newly created assembly to the root folder of your project. (If you leave in the \debug folder, Visual Studio will overwrite it the next time you build.)

8.     Remove the reference in our Visual Studio project to the original interop assembly and create a new reference to the newly modified assembly that resides in the root folder of your project.

If your application invokes other WPD methods that use arrays or buffers as arguments, you would repeat the revisions described in steps 4. and 5. above before you created the new interop assembly.

Accessing the WPD Properties and Commands

WPD properties and commands are identified by a PROPERTYKEY structure. This structure has two parts: a GUID and a DWORD. (For more information, see the discussion of WPD PROPERTYKEYS on MSDN: http://msdn2.microsoft.com/en-us/library/ms740204.aspx).

In order to use the common WPD PROPERTYKEYs, you’ll need to add a file containing their definition to your C# project. For a complete description of how this is done, refer to the following link on the dimeby8 blog: http://blogs.msdn.com/dimeby8/archive/2006/12/06/where-are-the-wpd-property-keys-in-c.aspx.

In the sample code found in this article, we refer to a file portabledeviceconstants.cs which we created using the guidelines in the dimeby8 blog (above).

 

Creating the WPD Objects

One of the first tasks a .Net will accomplish is the creation of the WPD objects that are used throughout the application to invoke methods, retrieve property values, and so on. The following snippet demonstrates how an application might create instances of the device-manager object, a device-values collection, and a portable device object.

namespace WpdSidebarGadget

{

    public partial class Form1 : Form

    {

 

        //

        // Get an instance of the device manager

        //

        PortableDeviceApiLib.PortableDeviceManagerClass devMgr

            = new PortableDeviceApiLib.PortableDeviceManagerClass();

 

        // Create our client information collection

        PortableDeviceApiLib.IPortableDeviceValues pValues =

            (PortableDeviceApiLib.IPortableDeviceValues)

                new PortableDeviceTypesLib.PortableDeviceValuesClass();

 

        // Create a new IPortableDevice instance

        PortableDeviceApiLib.PortableDeviceClass pPortableDevice =

                new PortableDeviceApiLib.PortableDeviceClass();

The following table lists the variable corresponding to each object and describes its use later in the application.

Variable

Description

devMgr

Used to retrieve a count of connected devices, to retrieve Plug and Play identifiers for those devices, and to retrieve friendly names for those devices,

pValues

Used to specify client information when opening a connection to the device.

pPortableDevice

Used to open a connection to the temperature-sensor device. Also used to retrieve the value of the TEMP_SENSOR_READING property.

 

Opening a Connection to the Temperature-Sensor Device

The process of opening a connection to a device is done in two phases: in the first phase we check to see if the device is connected to the PC; if it’s found, we open the connection in the second phase.

Searching for a Specific Device

The IPortableDeviceManager::GetDevices method serves two purposes. If the first parameter is set to null, it returns a count of connected devices. Otherwise, it returns a list of Plug and Play identifiers for each connected device.

The Plug and Play names are used internally by WPD to identify devices.  The Plug and Play name for the temperature sensor device is: “\\?\root#wpd#0000#{6ac27878-a6fa-4155-ba85-f98f491d4f33}”. This string uniquely identifies the device instance and the device interface and is defined as part of the Windows Driver Model (WDM). For more information, refer to the following topic: http://msdn2.microsoft.com/en-us/library/ms791083.aspx.

The IPortableDeviceManager::GetFriendlyName method maps a given Plug and Play identifier back to the given device’s friendly name. This is the name commonly associated with a device by the user. For example: “Parallax BS2 Temperature Sensor”. For more details about the use of the GetFriendlyName method, see the following section titled: “Retrieving the Friendly Name for a Given Device”.

The following code demonstrates how an application used the GetDevices method to retrieve a count of connected devices and then, subsequently, retrieve the Plug and Play identifier for each device. This code then retrieves the friendly name for each connected device (by calling the RetrieveFriendlyName helper function), and checks to see if any of the connected devices have a friendly name that matches the string: “Parallax BS2 Temperature Sensor”. If such a device exists, its Plug and Play identifier is passed to the StartProcessing helper function which opens a connection to the device and begins retrieving temperature data.

private void Form1_Load(object sender, EventArgs e)

{

    uint i = 0;

    uint cDevices = 0;

    string strDeviceID = String.Empty;

    string strFriendlyName = String.Empty;

 

    // Retrieve a count of connected WPD devices.

    devMgr.GetDevices(null, ref cDevices);

 

    // Iterate through the connected WPD devices--searching for

    // a TemperatureSensor device. If it's found, open the device

    // and begin retrieving the temperature property.

    if (cDevices > 0)

    {

        // Retrieve the PnP identifiers for each

        // connected device.

        string[] strPnPDeviceIDs = new string[cDevices];

        devMgr.GetDevices(strPnPDeviceIDs, ref cDevices);

         // For each connected device, retrieve the friendly

         // name and compare it to the TempSensor's name.

         for (i = 0; i < cDevices; i++)

         {

             strFriendlyName = RetrieveFriendlyName(devMgr, strPnPDeviceIDs[i]);

             if (strFriendlyName == "Parallax BS2 Temperature Sensor")

             {

                 // Open the device and begin retrieving the temperature

                 StartProcessing(pPortableDevice, pValues, strPnPDeviceIDs[i]);

              }

          }

      }

  }

Retrieving the Friendly Name for a Given Device

As noted in the previous section, the IPortableDeviceManager::GetFriendlyName method maps a given Plug and Play identifier back to the given device’s friendly name. In addition to retrieving a device’s friendly name, this method also returns a count of characters in the friendly-name string--if the second parameter is set to null.

The following helper function, RetrieveFriendlyName, demonstrates how your application could use the GetFriendlyName method to retrieve the count of characters in the friendly-name string, allocate memory for the string, and then retrieve it.

string RetrieveFriendlyName(

                        PortableDeviceApiLib.PortableDeviceManagerClass PortableDeviceManager,

                        string PnPDeviceID)

{

    uint   cFriendlyName = 0;

    ushort[] usFriendlyName;

    string strFriendlyName = String.Empty;

 

    // First, pass NULL as the LPWSTR return string parameter to get the total number

    // of characters to allocate for the string value.

    PortableDeviceManager.GetDeviceFriendlyName(PnPDeviceID, null, ref cFriendlyName);

           

    // Second allocate the number of characters needed and retrieve the string value.

 

    usFriendlyName = new ushort[cFriendlyName];

    if (usFriendlyName.Length > 0)

    {

        PortableDeviceManager.GetDeviceFriendlyName(PnPDeviceID, usFriendlyName, ref cFriendlyName);

 

        // We need to convert the array of ushorts to a string, one

        // character at a time.

        foreach (ushort letter in usFriendlyName)

            if (letter != 0)

                strFriendlyName += (char)letter;

 

        // Return the friendly name

        return strFriendlyName;

    }

    else

        return null;

}

 

Opening a Connection to the Device

The IPortableDevice::Open method opens a connection to a device that’s attached to a PC. This method takes two arguments: the first is the Plug and Play identifier for the target device; the second is an IPortableDeviceValues object that specifies client information for the device. At a minimum, the client information must include the following:

Client Information

Description

Name

A friendly-name for the device.

Major Version Number

The device’s major version number.

Minor Version Number

The device’s minor version number.

Revision Number

The device’s most recent revision number.

 

The following StartProcessing helper function configures the client information in an IPortableDeviceValues object and then calls the Open method. Once the connection is opened, the StartProcessing method calls a RetrieveTemp method to retrieve the current temperature from the connected temperature-sensor device. After calling the RetrieveTemp method, a timer is enabled so that it fires an event every 3 seconds.

 

void StartProcessing(

                    PortableDeviceApiLib.PortableDeviceClass pPortableDevice,

                    PortableDeviceApiLib.IPortableDeviceValues pValues,

                    string PnPDeviceID)

{

 

   // We have to provide at the least our name, version, revision

   pValues.SetStringValue(

   ref PortableDevicePKeys.WPD_CLIENT_NAME, "Temp-Sensor Client");

   pValues.SetUnsignedIntegerValue(

   ref PortableDevicePKeys.WPD_CLIENT_MAJOR_VERSION, 1);

   pValues.SetUnsignedIntegerValue(

   ref PortableDevicePKeys.WPD_CLIENT_MINOR_VERSION, 0);

   pValues.SetUnsignedIntegerValue(

   ref PortableDevicePKeys.WPD_CLIENT_REVISION, 2);

 

   // We are now ready to open a connection to the device

   // We'll assume deviceID contains a valid WPD device path and connect to it

   pPortableDevice.Open(PnPDeviceID, pValues);

 

   // Retrieve and display the initial temperature

   strVal = RetrieveTemp(pPortableDevice, "Temperature");

   iVal = Convert.ToInt32(strVal);

 

   // Enable the timer for UI

   timer1.Interval = 3000;  // 3 second intervals

   timer1.Enabled = true;

 

}

 

Retrieving the Temperature Property

You can retrieve a collection of readable properties for a WPD device by calling the IPortableDeviceProperties::GetValues method. This method takes a collection of property keys as input and returns a collection of property values as output. Once the WPD driver returns the collection of values, you can retrieve an individual property’s value by calling one of the Getxxx methods on the IPortableDeviceValues interface. In the case of the temperature property, we’ll call the IPortableDeviceValues::GetSignedIntegerValue method. And, when we call this method, we’ll pass the corresponding property key (TEMP_SENSOR_CURRENT_VALUE) which we’ve added to the file portabledeviceconstants.cs.

The new entry in portabledeviceconstants.cs has the following form:

 

namespace PortableDeviceConstants

{

    class PortableDevicePKeys

    {

        static PortableDevicePKeys()

        {

          TEMP_SENSOR_READING.fmtid = new Guid(0xA7EF4367, 0x6550,      0x4055, 0xB6, 0x6F, 0xBE, 0x6F, 0xDA, 0xCF, 0x4E, 0x9F);

          TEMP_SENSOR_READING.pid = 2;

 

The following helper function, RetrieveTemp, retrieves the temperature property from the temperature sensor device and returns that value in string form.

string RetrieveTemp(

                    PortableDeviceApiLib.PortableDeviceClass pPortableDevice,

                    string objectID)

{

    string strTemp = String.Empty;  // Receives current temp

 

    //

    // Retrieve IPortableDeviceContent interface required to

    // obtain the IPortableDeviceProperties interface

    //

    PortableDeviceApiLib.IPortableDeviceContent pContent;

    pPortableDevice.Content(out pContent);

 

    //

    // Retrieve IPortableDeviceProperties interface required

    // to get all the properties

    //

    PortableDeviceApiLib.IPortableDeviceProperties pProperties;

    pContent.Properties(out pProperties);

 

    //

    // Create a key collection

    //

    PortableDeviceApiLib.IPortableDeviceKeyCollection pKeyCollection =

        (PortableDeviceApiLib.IPortableDeviceKeyCollection)

        new PortableDeviceTypesLib.PortableDeviceKeyCollectionClass();

 

    // Add the TEMP_SENSOR_READING property to the key collection

    // This value is defined in PortableDeviceConstans.cs

     pKeyCollection.Add(ref PortableDevicePKeys.TEMP_SENSOR_READING);

 

    //

    // Call the GetValues to retrieve the prop values for the given

    // key. Then, retrieve the TEMP_SENSOR_CURRENT_VALUE property

    // by calling the GetStringValue method.

    //

    PortableDeviceApiLib.IPortableDeviceValues pPropValues;

    pProperties.GetValues(objectID, pKeyCollection, out pPropValues);

    int iTemp = 0;

    pPropValues.GetSignedIntegerValue(ref PortableDevicePKeys.TEMP_SENSOR_READING, out iTemp);

          

    if (iTemp > 0)

        return iTemp.ToString();

    else

        return "Invalid temperature!";

 

}

 

There are two approaches to retrieving the current temperature for the temperature-sensor device: an application can register to receive a temperature-update event from the device, or, it can retrieve the temperature value directly. This article focuses on the latter. In this article we’ve used an application-defined timer event to retrieve the temperature property.

The first step in enabling an application-defined timer event requires that you add a timer object to your application’s form. (You’ll find the timer in the Components section of the Visual Studio Toolbox when viewing your application’s form in Design mode.)

Once you add the timer to your form, you can specify the interval at which the corresponding event will be fired, and you can enable the timer object. Both of these tasks were accomplished in the final lines of the StartProcessing method which was described previously in the Open a Connection to the Device section.

 

   // Enable the timer for UI

   timer1.Interval = 3000;  // 3 second intervals

   timer1.Enabled = true;

 

Each time the timer event fires, it calls the application’s Tick callback function. In our sample, the code for the timer1_Tick method retrieves the temperature from the device, converts it from degrees Kelvin to degrees Farenheit and then writes a string of HTML to disk. (The gadget script will read the contents of this file and use it to render the current temperature in the Sidebar.)

private void timer1_Tick(object sender, EventArgs e)

{

    // Use DateTime structure to retrieve timestamp

    DateTime dtNow = DateTime.Now;

 

    // Retrieve the temp prop

    strVal = RetrieveTemp(pPortableDevice, "Temperature");

    iVal = KelvinToFarenheit(strVal);

 

     // Use the StreamWriter object for file I/O

    StreamWriter sw = new StreamWriter("c:\\temp\\tempdata.txt");

 

    // Write the data to the file

    sw.WriteLine("<P>Office Temperature<P>" + Convert.ToString(iVal) + "&deg; Farenheit");

           

    // Close the streamwriter object

    sw.Close();

 

    // Update the Window text

    this.Text = dtNow.Hour + ":" + dtNow.Minute + ":" + dtNow.Second + "  " + Convert.ToString(iVal) + "°";

          

}

 

The following snippet contains the code for the KelvinToFarenheit method which converts the temperature value.

private int KelvinToFarenheit(string strTemp)

{

    double dTemp = 0.0;

    int iVal = 0;

 

    dTemp = Convert.ToDouble(strTemp);

    iVal = (int)(((dTemp - 273.15) * 1.8) + 32);

    return iVal;

}

 

The Temperature-Sensor Gadget

The temperature-sensor gadget displays the current office temperature in degrees Farenheit in the Windows Sidebar.

If you’ve never created a gadget for Windows Sidebar before, you should refer to the Gadget Development Overview at: http://msdn2.microsoft.com/en-us/library/ms723694.aspx.

The Temperature-Sensor Gadget Files

The temperature-sensor gadget consists of three files: an HTML file, an XML file, and a .jpg file. These files are described in the following table.

File Name

Description

UpdateTemperatureReading.htm

An HTML file that contains the script which retrieves the updated temperature data from the local disk every 3,000 milliseconds.

gadget.xml

The gadget manifest. This file specifies properties such as the name of the gadget, the name of its HTML file, a string of descriptive text, and so on.

background.jpg

A 163 x 58 pixel image that produces the gadget’s two-tone blue background color.

 

Retrieving the Current Temperature

The gadget’s HTML file, UpdateTemperatureReading.htm, contains a script which opens the file created by the application (c:\temp\tempdata.txt) and copies the contents of this file into a span element identified as “temperature_data” within the body of the gadget’s HTML.

This script creates a FileSystemObject which it uses to open and read the text file. It uses the OpenTextFile method to open the file and the ReadAll method to retrieve its contents.

The script uses the setTimeout method on the window object to recursively execute the update function every 3,000 milliseconds. (This function contains the code that retrieves the contents of c:\temp\tempdata.txt.)

<html>

 

<head>

 

<script>

var fileSystemObject = null;

 

function loaded()

{

       fileSystemObject = new ActiveXObject("Scripting.FileSystemObject");

       update();

}

 

function update()

{

   var textStream = fileSystemObject.OpenTextFile("c:\\temp\\tempdata.txt");

   try

          {

             temperature_data.innerHTML = textStream.ReadAll();

           }

         finally

           {

             textStream.Close();

             window.setTimeout(update, 3000);

           }

}

 

</script>

 

<style>

   body

        {

               margin: 0;

               width: 125px;

               height: 62px;

               font: 'normal 8pt Arial';

               line-height: 12pt;

              text-align:center;

   }

 

</style>

 

</head>

 

<body onload="loaded()">

<g:background src="background.png" mce_src="background.png">

<span id="temperature_data"></span>

</g:background>

</body>

</html>

 

The HTML file also contains a description of the gadget’s window in the <style></style> tags that follow the script. Note that the width and height attributes match the width and height of the background image (background.png). In addition to specifying the size of the gadget’s window, the attributes found here specify the font used to render text, the line-height (which creates padding between the two lines of text), and the text alignment.

The g:background element specifies the source file (background.png) for the gadget’s background image.

The span element “temperature_data” identifies the location of the HTML which is copied from c:\temp\tempdata.txt by the Update function.

 

 

Posted by wpdblog | 1 Comments

Driver Dev Guide: Client Context Management

Client Context Management in WPD Drivers

A WPD driver provides the communication channel between applications and the physical device.   There can be multiple WPD applications running at any time, and the driver needs to handle requests from different clients and identify the clients based on the queued requests.   In other words, the driver needs an efficient and easy way to store client data on a per-connection basis, and retrieve the data per request.    Fortunately, UMDF supports context areas, a generic mechanism to save data with a framework object.   A WPD driver can allocate a data structure or object to hold the data, assign it to the framework object's context area, and retrieve the context at a later time.  The appropriate per-connection WDF framework object to use is the WDF file object.

 

Step 1: Assigning the Context

The driver assigns the context when the client opens a connection to it.   When a WPD application calls IPortableDevice::Open, the WPD API creates a handle to the driver using Win32 CreateFile.   Under the hood, UMDF initializes an IWDFFile object and forwards it, along with the Creation request, to the driver's IQueueCallbackCreate::OnCreateFile method.   The IWDFFile in this case represents a Win32 HANDLE that is used for subsequent communication from this client to the driver.  

A example of a CreateFile callback implementation is WpdWudfSampleDriver's CQueue::OnCreateFile.  A driver-specific ContextMap COM object is used to store client data (application name, version, in-progress enumeration and resource contexts, etc).   Note that the use of COM objects as context data is NOT required by UMDF - UMDF sees the context data as an opaque PVOID.   If you are using a COM object for storing context data, your driver needs to maintain the reference count for that COM object, and ensure that its resources are freed in the appropriate cleanup methods.

To save context data, the driver initializes a new ContextMap object, and calls IWDFObject::AssignContext for the IWDFFile object handed in by UMDF.    The parameters for AssignContext are the pointers to an IObjectCleanup object [containing the context cleanup code], and the newly-created ContextMap [containing the data to store].   IObjectCleanup::OnCleanup will be called when the file object is destroyed during CloseHandle.   See "Step 3" for further details on how to implement OnCleanup.  

In addition, only one context can be assigned to the file object (or any UMDF framework object).  Subsequent calls to AssignContext will fail if a context has already been assigned.   To add/remove client-specific data dynamically, one way is to implement a mapping object for managing the data (e.g. WpdWudfSampleDriver's ContextMap object), and assign a pointer to that mapping object as file object's context.

 

Step 2: Retrieving and Saving Context Information

To access the client data during requests, the WPD driver gets the context from the IWDFFile object.  

The sequence is:

  1. Call IWDFIoRequest::GetFileObject to get the IWDFFile object.
  2. Call IWDFObject::RetrieveContext on the IWDFFile object to access the context area.   In the sample driver, this will be the pointer to the ContextMap object that was created in CQueue::OnCreateFile during IPortableDevice::Open.
  3. Add/remove data to the ContextMap object directly when processing the WPD commands.  Each client connection (i.e. IPortableDevice::Open) will have its own IWDFFile object and ContextMap object. 
Example code from WpdWudfSampleDriver: 
  • CQueue::OnDeviceIoControl - Retrieving the context map from the WDF request's file object
  • WpdBaseDriver::OnSaveClientInfo - Adding client information to the context map when processing the WPD_COMMAND_COMMON_SAVE_CLIENT_INFORMATION command 

 

Step 3: Cleaning up the Context

When the client application calls IPortableDevice::Close, the WPD API will in turn call CloseHandle on the Win32 handle associated with that open connection.   Before destroying the IWDFFile object in response to the CloseHandle, UMDF calls the file object's IObjectCleanup::OnCleanup method that the driver passed into AssignContext during OnCreateFile.

An example implementation of the IQueueCleanup callback is WpdWudfSampleDriver's CQueue::OnCleanup.   This method retrieves the ContextMap stored in the IWDFObject object (in this case, the instance of IWDFFile from OnCreateFile) and frees the allocated memory, including the objects that the ContextMap holds.   To avoid memory leaks, ensure that the objects are properly cleaned up, and (if applicable) decrement the reference count.

 

References

A great WDF book is Developing Drivers with WDF by Orwick/Smith. Chapter 5 (pages 124-125) covers techniques for object-specific context data storage using UMDF.    The WpdWudfSampleDriver sample code is available in the Windows Driver Kit (v6000).

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 1 Comments
Filed under:

Which version of the WDK do I download?

To develop WPD drivers for Vista SP1:

- Build them using the Vista WDK (version 6000) or from the Longhorn Server Beta 3 WDK build environments.   

- WPD drivers built using Longhorn Server WDK Beta 3 will not run on Vista RTM or Windows XP, this is because the version 1.7 WDF Co-installer shipped in the Beta 3 release does not support downlevel operating systems (Vista RTM, XP, Server 2003).  

 

To develop WPD drivers for Vista RTM:

- Build them from the Vista WDK (version 6000) 

- The drivers can run on Windows XP with INF changes and Windows Media Player 11 or Format SDK Redistributable 11 installed

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 2 Comments
Filed under:

Getting a WPD Sample Driver working with Windows Media Player 11

We mentioned in a previous post that the WPD sample drivers are not designed to enumerate in Windows Media Player 11 (WMP11) due to compatibility issues.   This post covers the changes needed to enable the WPD Comprehensive Sample Driver (WpdWudfSampleDriver) to appear as a portable device in WMP11 and simulate a sync.    

The WpdWudfSampleDriver is featured in this post because it contains a lot more functionality than the WpdHelloWorldDriver, and is closer to meeting the requirements of WMP11 than the WpdHelloWorldDriver.    More specifically, the WpdWudfSampleDriver simulates the following additional functionality:

1. Creating and transferring data objects on the device.

2. Rendering information profiles for creating content to the device.

3. Setting properties on objects.

4. Posting device events.

5. Bulk property operations.

In the list above, items 1-3 provide basic compatibility with WMP11.  Device events are a "good to have" feature, for application responsiveness to changes in the device content and device state.  Bulk property operations are optional, but also provide improved application responsiveness - multiple properties for objects can be sent in batches, instead of multiple per-object "GetProperty" requests.

During sync, the WpdWudfSampleDriver simulates file and folder creation by allowing content to be created and transferred to the device.   The data is not persisted by the driver, and will exist as long as the sample driver is still loaded.   Once the driver is disconnected (e.g. disable in Device Manager), the data goes away.   Typically, your WPD driver would be sending this data to your physical device and persisting it there, so this should not be an issue for real devices.

 

Modifications to the WpdWudfSampleDriver to Enumerate and Sync in WMP11

Some of the changes below involve updating the fake contents on the device for illustration.   The same property requirements will apply for your own driver or device objects.  

1.  Add WPD_DEVICE_SYNC_PARTNER as a writable property for the "Device" object.   This is needed by WMP11 for setting up a sync partnership with the device. 

In deviceobjectfakecontent.h:

class DeviceObjectFakeContent : public FakeContent
{
  ...

    virtual HRESULT GetAllValues(
        IPortableDeviceValues*  pValues)
    {
	...
        // Add WPD_DEVICE_SYNC_PARTNER
        hrSetValue = pValues->SetStringValue(WPD_DEVICE_SYNC_PARTNER, SyncPartner);
        if (hrSetValue != S_OK)
        {
            CHECK_HR(hrSetValue, "Failed to set WPD_DEVICE_SYNC_PARTNER");
            return hrSetValue;
        }
        ...
    }	

    virtual HRESULT WriteValue(
        const PROPERTYKEY& key,
        const PROPVARIANT& Value)
    {
        ...
        else if(IsEqualPropertyKey(key, WPD_DEVICE_SYNC_PARTNER))
        {
            if (Value.vt == VT_LPWSTR)
            {
               SyncPartner = Value.pwszVal;
            }
            else
            {
                hr = E_INVALIDARG;
                CHECK_HR(hr, "Failed to set WPD_DEVICE_SYNC_PARTNER because type was not VT_LPWSTR");
            }
        }
	...
    }
   ...

private:
    ...
    CAtlStringW SyncPartner;
};

 

2. Add WPD_OBJECT_NON_CONSUMABLE as a writable property for all objects.   This allows simulating the saving of sync settings into a file on the device called WMPInfo.xml.   This file is created by WMP11 on the device and marked as "Non Consumable," with Format = WPD_OBJECT_FORMAT_UNSPECIFIED and Content Type = WPD_CONTENT_TYPE_UNSPECIFIED.  

In fakecontent.h:

class FakeContent
{

    virtual HRESULT WriteValue(
        const PROPERTYKEY& key,
        const PROPVARIANT& Value)
    {
        ...
        else if (IsEqualPropertyKey(key, WPD_OBJECT_NON_CONSUMABLE))
        {
            if(Value.vt == VT_BOOL)
            {
                NonConsumable = Value.boolVal;
            }
            else
            {
                hr = E_INVALIDARG;
                CHECK_HR(hr, "Failed to set WPD_OBJECT_NON_CONSUMABLE because type was not VT_BOOL");
            }
        }
        ...
    }
   ...
};

 

3. Add support for WPD_OBJECT_ORIGINAL_FILE_NAME in for all objects.  This involves updating the support properties and fixed attributes tables, and overriding FakeGenericFileContent::GetAllValues() to return this property in addition to the standard properties.

In helpers.cpp:

const PROPERTYKEY* g_SupportedPropertiesForFakeContentFormat[] =
{
    ...
    &WPD_OBJECT_ORIGINAL_FILE_NAME,
    ...
};

KeyAndAttributesEntry g_FixedAttributesTable [] =
{     
    ...
    {&FakeContent_Format, &WPD_OBJECT_ORIGINAL_FILE_NAME, 
         UnspecifiedForm_CanRead_CannotWrite_CannotDelete_Fast},
    // Properties for the device object
    ...
}

In fakecontent.h:

class FakeGenericFileContent : public FakeContent
{
public:

    virtual HRESULT GetAllValues(
        IPortableDeviceValues*  pStore)
    {
        HRESULT             hr          = S_OK;
        PropVariantWrapper  pvValue;

        // Call the base class to fill in the standard properties
        hr = FakeContent::GetAllValues(pStore);
        if (FAILED(hr))
        {
            CHECK_HR(hr, "Failed to get basic property set");
            return hr;
        }

        // Add WPD_OBJECT_ORIGINAL_FILE_NAME
        pvValue = FileName;
        hr = pStore->SetValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &pvValue);
        if (hr != S_OK)
        {
            CHECK_HR(hr, ("Failed to set WPD_OBJECT_ORIGINAL_FILE_NAME"));
            return hr;
        }

        return hr;
    }
    ...
};

 

4. Add support for creation of folders.   These folders need to support WPD_OBJECT_ORIGINAL_FILE_NAME.    This allows WMP11 to create the "Album", "Artist" folders on the device during the sync operation.   

One way to do this is to define a new FakeFolderContent class that inherits from FakeContent, and return WPD_OBJECT_ORIGINAL_FILE_NAME in FakeFolderContent::GetAllValues().

Added new header file: fakefoldercontent.h

class FakeFolderContent : public FakeContent
{
public:
    ...

    virtual HRESULT GetAllValues(
        IPortableDeviceValues*  pStore)
    {
        HRESULT             hr          = S_OK;
        PropVariantWrapper  pvValue;

        // Call the base class to fill in the standard properties
        hr = FakeContent::GetAllValues(pStore);
        if (FAILED(hr))
        {
            CHECK_HR(hr, "Failed to get basic property set");
            return hr;
        }

        // Add WPD_OBJECT_ORIGINAL_FILE_NAME
        pvValue = FileName;
        hr = pStore->SetValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &pvValue);
        if (hr != S_OK)
        {
            CHECK_HR(hr, ("Failed to set WPD_OBJECT_ORIGINAL_FILE_NAME"));
            return hr;
        }

        return hr;
    }
};

All top level folders (e.g. Video Folder, Music Folder) in the sample driver can then initialize a FakeFolderContent instead of a FakeContent.    The value of WPD_OBJECT_ORIGINAL_FILE_NAME is usually the same as WPD_OBJECT_NAME.  Assign the value of FileName in FakeDevice::CreateContentObject() whenever an object is created, so that it will be returned during subsequent "Get Property" queries.

In fakedevice.h:

class FakeDevice
{
    ...   
    HRESULT InitializeContent(
        IPortableDeviceClassExtension *pPortableDeviceClassExtension)
    {
        ...
        
        // Add Media folder: Music folder
        pContent = new FakeFolderContent();
        if (pContent)
        {
            ...
            pContent->FileName  = pContent->Name;  // Set Original File Name property
            ...
        }

        // Repeat for other types of folders declared here
        ...

        // Add generic file content objects to storage 1
        for(DWORD dwIndex = 0; dwIndex < NUM_VERTICAL_OBJECTS; dwIndex++)
        {
            m_dwLastObjectID++;

            pContent = new FakeGenericFileContent();
            if (pContent)
            {
                ...
                pContent->FileName = pContent->Name;
                ...
            }
        }
        ...
    }

    HRESULT CreateContentObject(
        LPCWSTR                 pszObjectName,
        LPCWSTR                 pszParentID,
        REFGUID                 guidContentType,
        IPortableDeviceValues*  pObjectProperties,
        FakeContent**           ppContent)
    {
        ...
        if(guidContentType == WPD_CONTENT_TYPE_FOLDER)
        {
            FakeContent* pContent = NULL;

            pContent = new FakeContent();
            if (pContent)
            {
                ...
                pContent->FileName = pszObjectName;
                ...
            }
        }
   }
   ...
}; 

 

5.  Modify WpdWudfSampleDriver.INF to set EnableLegacySupport to 3 so that the WMDM compatibility component is registered and enabled for this device when it installs.   

; Enable support for legacy WIA and WMDM applications
HKR,,"EnableLegacySupport",0x10001,3


 

6. When returning the list of rendering information profiles in SetRenderingProfiles(), return an additional profile for WPD_OBJECT_FORMAT_WMV.  This profile contains a single WPD_PROPERTY_ATTRIBUTE_FORM_ENUMERATION entry with an IPortableDevicePropVariantCollection containing the list of valid FourCC Codec VT_UI4 values.   

This step is needed because the sample driver reports support for WMV.    In your real driver implementation, you don't need this step unless your device needs to support WPD_OBJECT_FORMAT_WMV.   WMP11 has additional requirements on the attributes of FourCC Codec property, and would not enumerate any content on the device otherwise [Symptom: you will see the device in the Sync Pane, but no content under the device].

In helpers.cpp:

HRESULT GetVideoProfile(
    IPortableDeviceValues** ppProfile)
{
    HRESULT hr = S_OK;
    CComPtr<IPortableDeviceValues> pProfile;
    CComPtr<IPortableDeviceValues> pFourCCCode;

    hr = CoCreateInstance(CLSID_PortableDeviceValues,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IPortableDeviceValues,
                          (VOID**) &pProfile);
    CHECK_HR(hr, "Failed to CoCreateInstance CLSID_PortableDeviceValues");

    if (hr == S_OK)
    {
        hr = CoCreateInstance(CLSID_PortableDeviceValues,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_IPortableDeviceValues,
                              (VOID**) &pFourCCCode);
        CHECK_HR(hr, "Failed to CoCreateInstance CLSID_PortableDeviceValues");
    }

    // Set the value for WPD_OBJECT_FORMAT to indicate this profile applies to WMV objects
    if (hr == S_OK)
    {
        hr = pProfile->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_WMV);
        CHECK_HR(hr, "Failed to set WPD_OBJECT_FORMAT");
    }

    // Set the value for WPD_VIDEO_FOURCC_CODE
    if (hr == S_OK)
    {
        CComPtr<IPortableDevicePropVariantCollection> pFourCCCodeEnumElements;

        if (hr == S_OK)
        {
            hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IPortableDevicePropVariantCollection,
                                  (VOID**) &pFourCCCodeEnumElements);
            CHECK_HR(hr, "Failed to CoCreateInstance 
                     CLSID_PortableDevicePropVariantCollection");
        }

        if (hr == S_OK)
        {
            hr = pFourCCCode->SetUnsignedIntegerValue(
                                 WPD_PROPERTY_ATTRIBUTE_FORM, 
                                 WPD_PROPERTY_ATTRIBUTE_FORM_ENUMERATION);
            CHECK_HR(hr, "Failed to set WPD_PROPERTY_ATTRIBUTE_FORM 
                          for WPD_VIDEO_FOURCC_CODE");
        }

        if (hr == S_OK)
        {
            // Only 1 sample value is set here, add more as appropriate for your device
            PROPVARIANT pvValue;
            PropVariantInit(&pvValue);
            pvValue.vt = VT_UI4;
            pvValue.ulVal = MAKEFOURCC('W', 'M', 'V', '3');
            hr = pFourCCCodeEnumElements->Add(&pvValue);
            CHECK_HR(hr, "Failed to populate the FourCC Code Enumeration Elements");
        }

        if (hr == S_OK)
        {
            hr = pFourCCCode->SetIPortableDevicePropVariantCollectionValue(
                                 WPD_PROPERTY_ATTRIBUTE_ENUMERATION_ELEMENTS, 
                                 pFourCCCodeEnumElements);
            CHECK_HR(hr, "Failed to set WPD_PROPERTY_ATTRIBUTE_ENUMERATION_ELEMENTS 
                          for WPD_VIDEO_FOURCC_CODE");
        }

        // Now set the Video FourCC Code property to be pFourCCCode
        if (hr == S_OK)
        {
            hr = pProfile->SetIPortableDeviceValuesValue(
                              WPD_VIDEO_FOURCC_CODE, 
                              pFourCCCode);
            CHECK_HR(hr, "Failed to add the WPD_VIDEO_FOURCC_CODE attributes 
                          to the WPD_OBJECT_FORMAT_WMV rendering profile");
        }
    }

    // Set the output result
    if (hr == S_OK)
    {
        hr = pProfile->QueryInterface(IID_PPV_ARGS(ppProfile));
        CHECK_HR(hr, "Failed to QI for IPortableDeviceValues");
    }

    return hr;
}

HRESULT SetRenderingProfiles(
    IPortableDeviceValues*          pValues)
{
    ...
    CComPtr<IPortableDeviceValues>  pVideoProfile;
    ...

    // Get the video profile
    if (hr == S_OK)
    {
        hr = GetVideoProfile(&pVideoProfile);
        CHECK_HR(hr, "Failed to get video profile properties");
    }

    // Add the profile to the collection
    if (hr == S_OK)
    {
        hr = pProfiles->Add(pVideoProfile);
        CHECK_HR(hr, "Failed to add video profile to profile collection");
    }
    ...
}

 

Known Issue with Multiple Storages

When a device that contains multiple storages is disconnected, one of the storages will still remain in the treeview.   This is a known bug in WMP11.   Since the WpdWudfSampleDriver emulates multiple storages, you'll see this bug when you disable the driver from Device Manager while WMP11 is running.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 1 Comments
Filed under: ,

New Driver and Application Whitepapers Are Here

We've published two new WPD developer-centric whitepapers at the Microsoft WHDC website for WinHEC.   

The first covers how to do WMDRM metering and license synchronization from a WPD application; the second describes how to port the existing WPD "Hello World" sample driver to communicate with a Parallax Basic Stamp II microcontroller to read temperature sensor data.   Both contain sample code.  

 

I. Accessing WMDRM APIs from a WPD Application:

http://www.microsoft.com/whdc/device/media/WMDRM_API.mspx

This whitepaper describes how to associate a WPD device instance with a WMDM device instance for MTP devices; the WMDM device instance is then used for calling Windows Media Digital Rights Management (WMDRM) application interfaces. To build the sample WPD application, you need the Windows Media Format SDK v11 and the Windows SDK (v6000).  

 

II. Creating a WPD Driver for a Microcontroller-Based Sensor:

http://www.microsoft.com/whdc/device/media/WPD_drv.mspx

This sample is based on the Parallax Basic Stamp II microcontroller to provide an example of a simple hardware device that can have a WPD driver for it.   Parallax provides an activity kit that contains the hardware components for this sample driver.  To build the driver, you need the Windows Driver Kit (v6000).  

 

References

All WPD-related whitepapers are posted here.  

More information on downloading the various SDKs.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 1 Comments
Filed under: , , ,

Driver Dev Guide: Supporting WPD_CLIENT_EVENT_COOKIE

WPD Applications can specify a unique string "cookie" in the client information when calling IPortableDevice::Open:

 

WPD_CLIENT_EVENT_COOKIE VT_LPWSTR Client supplied cookie returned by the driver in events posted as a direct result of operations issued by this client.
 

This is in addition to the client information properties described on MSDN, and declared in PortableDevice.h.

Depending on the application, this value will typically contain the application's DLL name or CLSID, or a unique application-generated identifier if the application supports multiple instances of itself running simultaneously.    The actual string contents does not matter to the driver, it is passed back to the application when events are sent.

When your driver sends an event and sets the cookie in the event parameters, the application can retrieve the cookie to determine if the event is a result of actions that originated from itself or another program.   For example, Application A creates an object on the device, and receives an ObjectAdded event containing its client event cookie, it can choose not to refresh its view of the device contents because the view has been updated at the time the object was created.    If Application B creates another object on the device, Application A will receive an ObjectAdded event with a different cookie (or no cookie), and knows that it needs to refresh the device contents view, because the object was added by some other client application.

In an environment where there can be two or more client applications communicating with your driver (Explorer is virtually guaranteed to be one client through the WPD Shell Namespace Extension), supporting the event cookie mechanism is a good WPD programming practice that may help reduce the client traffic to your device in response to events, and to streamline application-side event handling. 

 

How to support WPD_CLIENT_EVENT_COOKIE in your WPD driver:

1. Add a handler for the WPD_COMMAND_COMMON_SAVE_CLIENT_INFORMATION command.  The WpdWudfSampleDriver from the WDK contains an example of this in WpdBaseDriver::OnSaveClientInfo().

2. In OnSaveClientInfo(), if the application sets WPD_CLIENT_EVENT_COOKIE in the client information parameters, save the cookie with your context info. Some applications may choose not to send this cookie, in which case your driver does not have to do anything here.

LPWSTR pszEventCookie = NULL; 

hr = pClientInfo->GetStringValue(WPD_CLIENT_EVENT_COOKIE, &pszEventCookie);

if (hr == S_OK && pszEventCookie != NULL)
{
// Store the cookie value with the client context
pContext->EventCookie = pszEventCookie;
}

CoTaskMemFree(pszEventCookie);

 

3. When posting events, if the client event cookie is available, send it along with the event parameters.   In the sample driver, this code will be added to the PostWpdEvent() function.

HRESULT hrEventCookie = GetClientEventCookie(pCommandParams, &pszEventCookie);

if (hrEventCookie == S_OK && pszEventCookie != NULL)
{
// Add it to the event parameters
// The application's OnEvent callback will match this with its cookie
hrEventCookie = pEventParams->SetStringValue(WPD_CLIENT_EVENT_COOKIE, pszEventCookie);
}

CoTaskMemFree(pszEventCookie);

 

And here's an outline of the GetClientEventCookie() helper function.   It uses the client information context supplied in the command parameters to look up the saved client cookie in the context map.

ClientContext* pClientContext = NULL;   // This is a context helper object defined by the sample driver

hr = pCommandParams->GetStringValue(WPD_PROPERTY_COMMON_CLIENT_INFORMATION_CONTEXT, &pszClientContext);

if (hr == S_OK)
{
hr = GetClientContext(pCommandParams, pszClientContext, (IUnknown**)&pClientContext);

if (hr == S_OK && pClientContext != NULL && pClientContext->EventCookie.GetLength() > 0)
{
// Allocate the cookie string to return
*ppszEventCookie = AtlAllocTaskWideString(pClientContext->EventCookie);
}

if (pClientContext != NULL) 
    pClientContext->Release();

CoTaskMemFree(pszClientContext);
}

 

For an overview on posting events, refer to this post.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 0 Comments
Filed under:

Cancellation Behavior of MTP USB Devices

The MTP Specification contains this definition of the CancelTransaction Event:

G.2.2 CancelTransaction
Event Code: 0x4001
Parameter 1: None
Parameter 2: None
Parameter 3: None

This event is used to initiate the cancellation of a transaction over transports which do not have their own mechanism for canceling transactions. The details of how a transaction is cancelled may be transport-specific. When an initiator or responder receives this event, it should cancel the transaction identified by the TransactionID in the event dataset. If the transaction has already completed, this event should be ignored.

After receiving a CancelTransaction event from the initiator during an object transfer, the responder should send an Transaction_Cancelled response for the transfer which was in progress.


MTP USB Transport Cancellation Behavior

Why then does an MTP/USB device send a Status_OK (0x2001) instead of Transaction_Cancelled (0x201F) response when it receives a CancelTransaction (0x4001) from the application? 

The reason is that the MTP USB transport has defined how cancellation is to be implemented.   As pointed out in the MTP Specification above, CancelTransaction is intended for "transports which do not have their own mechanism for canceling transactions", and will not apply to MTP/USB devices.

Here are the MTP/USB specifications for each cancellation scenario.   In the cases below, "Host" can be a PC running Windows Media Player 11, and "Device" can be a MTP music player or a PTP camera that connects to the PC through USB.

Case A. When the Host Cancels
When the device receives a cancellation request from the host, it should return Device_Busy (0x2019) for a GetDeviceStatus query.    The device should then perform cancellation and clean up, and return Status_OK (0x2001) for subsequent GetDeviceStatus requests. The device should NOT send a response for the cancelled transaction through the BULK-IN pipe.

After the host receives a Status_OK response from a GetDeviceStatus query on the device, it may issue the next transaction through the BULK-OUT pipe. 

Case B. When the Device Cancels
If the device wants to cancel, it should STALL both BULK-IN and BULK-OUT endpoints.  When the device receives a GetDeviceStatus request from host, the device should return Transaction_Cancelled (0x201F) as the reason for the STALL, and the STALL-ed endpoint numbers.

The host may then issue the ClearFeature request to clear the STALL-ed endpoints. The device should clear, get ready for next command, and return Status_OK for next GetDeviceStatus query.

Case C. When the Host and Device Both Cancel
When the device receives a cancel request from the host while it is trying to cancel, it should clear any pending device-initiated cancellations, respond to the host with Device_Busy (0x2019) for subsequent GetDeviceStatus requests, and proceed with normal host-initiated cancellation.

When the device wants to cancel (this could be initiated by the user), it first checks if there are any pending host-initiated cancellation.  If yes, the device can simply return.  If not, the device should STALL the BULK endpoints.

 

Cancellation Examples 

For cancellation examples, refer to the Picture Transfer Protocol (PTP) Specification - ISO 15740 Annex D: Sections D.7.2.1.2 (for device behavior) and D.9, available on the I3A PTP website.

In addition, the PTP USB Transport specification also contains a definition for the GetDeviceStatus and further descriptions of the BULK cancellation behavior.
 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 4 Comments
Filed under: ,

Installing the Sample Drivers on XP and Vista

A question was raised today on how to run the WPD sample drivers on Windows XP.    WPD drivers and applications can run downlevel on Windows XP as long as the WPD and UMDF runtimes are both installed.

 

Compiling a WPD Driver for your OS

For the driver to run on Windows XP, it has to be built using a Windows XP x86 build environment provided by the Windows Driver Kit (v6001).   Similarly, for running on Windows Vista, use one of the Windows Vista build environments.

If the runtime OS does not match the build environment, you may get a "Code 10 - This Device Cannot Start" in error message in Device Manager after installing the driver.

 

Installing WPD on the XP machine

Either one of these supply the WPD runtime components for Windows XP:

1. Install Windows Media Player 11, or

2. Install the Windows Media Format SDK 11 redistributable: WMFDist11-WindowsXP-X86-ENU.exe package, typically under c:\WMSDK\WMFSDK11\Redist folder from a Windows Media Format 11 SDK install.

 

Obtaining the WUDF Co-Installer

The UMDF runtime is distributed and installed by referencing its co-installer in the driver INF.   For Windows Vista and XP, the recommended co-installer to use is version 1.7 that is distributed in the Windows Driver Kits: under \WinDDK\6001.xxxxx\redist\wdf\x86\WUDFUpdate_01007.dll.   Copy this to the same location as your updated driver INF before installing your driver.

 

Modifying the Sample Driver INF

The following example shows the INF changes to enable the WpdHelloWorldDriver to install in Windows XP using the v1.7 UMDF co-installer.   The modified/added entries are highlighted in red, and the same updates would apply to both WPD driver samples.

The changes involve adding a CopyFiles directive that references WUDFUpdate_01007.dll, and updating the AddReg entries to use UMDF v1.7.

[SourceDisksFiles]
WpdHelloWorldDriver.dll=1
WUDFUpdate_01007.dll=1
...

[Basic_Install.CoInstallers]
AddReg=Basic_Install.CoInstallers_AddReg
CopyFiles=CoInstallers_CopyFiles
...

; Add the co-installer DLL to the CopyFiles list
[CoInstallers_CopyFiles]
WUDFUpdate_01007.dll

...


; Update the registration to use the correct binary
[Basic_Install.CoInstallers_AddReg]
HKR,,CoInstallers32,0x00010000,"WUDFUpdate_01007.dll"

...


; Update the UMDF version to 1.7.0
[WpdHelloWorldDriver_Install]
UmdfLibraryVersion=1.7.0

...


; Set the Co-Installer CopyFiles destination to system32
[DestinationDirs]
System32Copy=12,UMDF ; copy to system32\drivers\umdf
Basic_Install.CoInstallers_CopyFiles=11
...

 

The same updated INF will also work on Windows Vista, as long as the v1.7 UMDF co-installer DLL is provided with your driver files during install AND you've compiled the driver DLL with the correct WDK build environment (see the first section of this post).  

On Windows Vista, installing the WPD runtime components is not required because WPD is available inbox for most SKUs.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 7 Comments

Driver Dev Guide: WPP Tracing in your WPD Driver

WPP Software Tracing is the recommended way to log trace and error messages in your WPD driver.  WPP, short for Windows Software Trace PreProcessor, provides an efficient real-time event logging mechanism.    In addition, WPP traces include the system timestamp and can be used as a way to measure performance, for example by calculating the elapsed time between function calls. 

 

From OutputDebugString to WPP

The WPD sample drivers in the Windows Driver Kit (WDK) log error messages using a CHECK_HR function that wraps around OutputDebugString.   While OutputDebugString is simple to use during development, it necessitates an active debugger connection in order to view the traces, and should be used minimally in shipping code.   WPP tracing is much more lightweight, flexible, and preferable for a range of tracing applications: logging errors for diagnosing failures, tracing code execution during development, to name a few.    

How to switch your WPD driver's tracing from OutputDebugString to WPP transparently:

Step 1. Remove the original CHECK_HR() function definition (it can be found in WpdHelloWorldDriver.cpp or WpdWudfSampleDriver.cpp for the WPD samples) and its declaration in stdafx.h.  

 

Step 2. Add the following code (include the WPP config comment blocks) to stdafx.h:

// TODO: Choose a different trace ID for your WPD driver
#define MYDRIVER_TRACING_ID L"Microsoft\\WPD\\MyWpdDriver"

// TODO: Choose a different trace control GUID for your WPD driver
// TODO: Define trace flag bits for your WPD driver

#define WPP_CONTROL_GUIDS \
WPP_DEFINE_CONTROL_GUID( \
MyWpdDriverCtrlGuid, (da5fbdfd,1eae,4ecf,b426,a3818f325ddb), \
\
WPP_DEFINE_BIT(TRACE_FLAG_ALL) \
WPP_DEFINE_BIT(TRACE_FLAG_DRIVER) \
WPP_DEFINE_BIT(TRACE_FLAG_DEVICE) \
)

//
// Prefer trace statement that is based on
// - the standard trace LEVEL (evntrace.h), and
// - specific flag bit.
//
#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
WPP_LEVEL_LOGGER(flags)

#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
(WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)

//
// This comment block is scanned by the trace preprocessor to define our
// Trace and TraceEvents function.
//
// begin_wpp config
// FUNC Trace{FLAG=TRACE_FLAG_ALL}(LEVEL, MSG, ...);
// FUNC TraceEvents(LEVEL, FLAGS, MSG, ...);
// end_wpp
//

// MACRO: CHECK_HR
// Configuration block that defines trace macro. It uses the PRE/POST macros to include
// code as part of the trace macro expansion. CHECK_HR is equivalent to the code below:
//
// {if (hrCheck != S_OK){ // This is the code in the PRE macro
// DoTraceMessage(TRACE_FLAG_ALL, "%!STDPREFIX!" MSG " hr= %!HRESULT!", ..., hrCheck)
// ;}} // This is the code in the POST macro
//

// begin_wpp config
// USEPREFIX (CHECK_HR,"%!STDPREFIX!");
// FUNC CHECK_HR{FLAG=TRACE_FLAG_ALL}(hrCheck, MSG, ...);
// USESUFFIX (CHECK_HR, " hr= %!HRESULT!", hrCheck);
// end_wpp

#define WPP_FLAG_hrCheck_PRE(FLAGS, hrCheck) {if(hrCheck != S_OK) {
#define WPP_FLAG_hrCheck_POST(FLAGS, hrCheck) ; } }
#define WPP_FLAG_hrCheck_ENABLED(FLAGS, hrCheck) WPP_FLAG_ENABLED(FLAGS)
#define WPP_FLAG_hrCheck_LOGGER(FLAGS, hrCheck) WPP_FLAG_LOGGER(FLAGS)

 

Step 3. Add a RUN_WPP directive to your sources file:

RUN_WPP= $(SOURCES) -scan:stdafx.h

 

Step 4. Add the WPP initialization and cleanup routines to DllMain:

// DLL Entry Point
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if(DLL_PROCESS_ATTACH == dwReason)
{
g_hInstance = hInstance;

//
// Initialize tracing.
//
WPP_INIT_TRACING(MYDRIVER_TRACING_ID);

}
else if (DLL_PROCESS_DETACH == dwReason)
{
//
// Cleanup tracing.
//
WPP_CLEANUP();

}

return _AtlModule.DllMain(dwReason, lpReserved);
}

Step 5. For each .cpp file, add a #include <filename>.tmh after the regular includes:

//
// WpdBaseDriver.cpp
//

#include "stdafx.h"
#include "WpdBaseDriver.h"

// Include the WPP generated Trace Message Header (tmh) file for this .cpp
#include "WpdBaseDriver.tmh"


...

Repeat Step 5 for all other .CPP files. 

 

Step 6. Rebuild your driver.  Since we replaced the CHECK_HR function with an equivalent WPP macro that has the same signature (our WPP changes were "under the hood"), no changes are needed for any code that uses CHECK_HR.   You can continue to use CHECK_HR the same way as before :).

The WPP preprocessor will process stdafx.h for tracing macros, and generate a Trace Message Header (tmh) file for each cpp file in the obj folders.  

Inspecting the preprocessor output for each .CPP file that calls CHECK_HR (generated using make WpdBaseDriver.pp), you will notice that all CHECK_HR trace statements have been wrapped by WPP function calls, while the tracing text and WPP functions are defined in WpdBaseDriver.tmh.

The most common WPP-related compilation errors you may encounter are due to mismatched format identifiers and parameters  (e.g. if your format string contains an extra %d that does not match an argument) or missing entries in the WPP_CONTROL_GUIDS macro.

 

More WPP Tracing Macros

We've also defined TraceEvents and Trace functions in stdafx.h that are general purpose WPP trace macros wrapping DoTraceMessage.   These can be used for both error and non-error trace logging using a combination of levels and flags.   For examples on how to use them in your driver, refer to the UMDF sample drivers in the WDK.

To understand the WPP declarations used in stdafx.h, customize the CHECK_HR macro, or to define your own tracing macros, visit the WPP macros guide which describes the WDK tracedrv sample in detail.

 

Generating and Viewing Traces

To generate traces, you can use Traceview  (UI) or Tracelog (command line).   TraceView also allows you to view traces in real-time as they are logged. These are available in the WDK.   A quick way to get started is to create a new log session from Traceview using the PDB file that contains the private symbols for your driver.   Alternatively, you use a .CTL file that specifies your driver as a trace provider (replace own values below):

da5fbdfd-1eae-4ecf-b426-a3818f325ddb    MyWpdDriverCtlGuid

 

Converting Binary Traces to A Human-Readable Form

WPP traces log to a binary ETL format.  To convert them to text, supply Trace Format (TMF and TMC) files that can be generated from the .PDB file using TracePDB.  For more details, refer to the tracing tools section in the MSDN docs.

 

This posting is provided "AS IS" with no warranties, and confers no rights.
Posted by wpdblog | 1 Comments
Filed under: ,

WPD and WMDM Side-by-side: An Application Development Perspective

This post will compare WPD and the latest version of Windows Media Device Manager (WMDM), from an application developer's point of view.

Availability (aka SDK and Runtime Requirements)

The Windows Vista SDK contains the complete build environment (headers, libs, sample code ...) for developing a WPD application for both 32 and 64-bit.

The Windows Media Format 11 SDK (WMFSDK11) provides headers and libs for WMDM 11.  Since WMFSDK11 is released in 32-bit, only 32-bit WMDM applications can be built.  On 64-bit Windows, a 32-bit WMDM application will run in SysWOW mode. 

WMDM 11 WPD
Operating Systems Windows XP + Windows Media Format 11 Redistributable, Windows Vista Windows XP + Windows Media Format 11 Redistributable, Windows Vista
Processors X86, AMD64 (SysWOW mode only) X86, AMD64
SDKs Windows Media Format SDK 11 Windows Vista SDK

 

Common Device-level Operations

Both WPD and WMDM support basic device-level operations: enumeration of connected devices, managing device-level properties, and querying of static device capabilities.

WMDM's IWMDMDeviceControl operations internally delegate to the service provider (through IMDSPDeviceControl), and would require the service provider to support these operations. While device playback control is not natively supported by the WPD API, WPD's extensibility mechanism makes it easy to add this as a custom command to your driver. For example, your WPD driver can support a custom Playback command that your application can invoke using IPortableDevice::SendCommand.

Operation Example WMDM API Suggested WPD API
Enumerate attached devices IWMDeviceManager2::EnumDevices2 IPortableDeviceManager::GetDevices
Enumerating device content IWMDMDevice::EnumStorage IPortableDeviceContent::EnumObjects
Examine device capabilities IWMDMDevice::GetFormatSupport, IWMDMDevice2::GetFormatCapability IPortableDeviceCapabilities:: GetSupportedContentTypes, GetSupportFormats

Retrieve device properties

IWMDMDevice::GetManufacturer IPortableDeviceManager:: GetDeviceManufacturer OR
IPortableDeviceProperties::GetValues(WPD_DEVICE_OBJECT_ID)
Write device properties IWMDMDevice3::SetProperty IPortableDeviceProperties::SetValues(WPD_DEVICE_OBJECT_ID)
Controlling device playback IWMDMDeviceControl::Play -

 

Common Object-level Operations

A WPD object is analogous to a WMDM storage, and most WMDMStorage operations have their equivalent WPD API.   WPD is ObjectID-based, so each interface method that supports object-level operations receives an Object Identifier as a parameter.   WMDM is interface-based, IWMDMStorage represents a storage or a file, and IWMDMStorageControl implements the operations on a storage or a file.   

For data object creation, both WPD and WMDM support transfer of protected Windows Media Digital Rights Management (WMDRM) content.

Operation Example WMDM API Suggested WPD API
Enumerating objects IWMDMDevice::EnumStorage, IWMDMEnumStorage::Next IPortableDeviceContent::EnumObjects, IEnumPortableDeviceObjectIDs::Next
Create a folder object IWMDMStorageControl::Insert3 IPortableDeviceContent::  CreateObjectWithPropertiesOnly
Create a data object IWMDMStorageControl::Insert3 IPortableDeviceContent:: CreateObjectWithPropertiesAndData
Transfer object data from device IWMDMStorageControl::Read IPortableDeviceResources::GetStream
Move objects IWMDMStorageControl::Move IPortableDeviceContent::Move
Copy objects - IPortableDeviceContent::Copy
Delete objects IWMDMStorageControl::Delete IPortableDeviceContent::Delete
Retrieve object properties IWMDMStorage4::GetSpecifiedMetadata IPortableDeviceProperties::GetValues
Write object properties IWMDMStorage3::SetMetadata IPortableDeviceProperties::SetValues
Delete object properties - IPortableDeviceProperties::Delete

 

Device Events

There are only 4 events that WMDM applications can receive: media arrival/removal, and device arrival/removal. WPD supports device-level and the more granular object-level events, plus built-in extensibility for custom events.

Operation Example WMDM API Suggested WPD API
Monitor object-level property changes - IPortableDeviceEventCallback::OnEvent (WPD_EVENT_OBJECT_UPDATED)
Monitor object-level arrival and removal - IPortableDeviceEventCallback::OnEvent (WPD_EVENT_OBJECT_ADDED, WPD_EVENT_OBJECT_REMOVED)
Monitor media arrival and removal IWMDMNotification::WMDMMessage (WMDM_MSG_MEDIA_ARRIVAL, WMDM_MSG_MEDIA_REMOVAL) IPortableDeviceEventCallback::OnEvent (WPD_EVENT_OBJECT_ADDED, WPD_EVENT_OBJECT_REMOVED)
Monitor device removal IWMDMNotification::WMDMMessage (WMDM_MSG_DEVICE_REMOVAL) IPortableDeviceEventCallback::OnEvent (WPD_EVENT_DEVICE_REMOVED)

Monitor device arrival

IWMDMNotification::WMDMMessage (WMDM_MSG_DEVICE_ARRIVAL) Subscribe to WPD interface arrival PnP event

 

Advanced Operations

Both WPD and WMDM provide some extensibility mechanism for sending device-specific commands.   IWMDMDevice3::DeviceIoControl is used for packaging and sending commands only to Media Transfer Protocol (MTP) portable media players.

WPD API methods that map to Command GUIDs internally delegate to IPortableDevice::SendCommand.  Applications can invoke these commands with additional options or parameters, such as the use of Read or Write IOCTLs for access control.

In addition, for better performance, WPD supports batching of property writes and retrievals across multiple objects, either per format or using a list of Object Identifiers.

Operation Example WMDM API Suggested WPD API
Retrieve properties of multiple objects - IPortableDevicePropertiesBulk::  QueueGetValuesByObjectList, OR QueueGetValuesByObjectFormat
Set properties of multiple objects - IPortableDevicePropertiesBulk:: QueueSetValuesByObjectList
Send device-specific commands IWMDMDevice::SendOpaqueCommand OR IWMDMDevice3::DeviceIoControl IPortableDevice::SendCommand
Cancel an operation - IPortableDevice*::Cancel, e.g. IPortableDeviceProperties::Cancel

 

Stability

WMDM loads registered service providers (SPs) within the same process as the application. Any crash or instability in the device driver or SP could cause undue crashes in the application process, or worse, trigger bluescreens if kernel mode drivers are involved.

WPD drivers are sandboxed in the User Mode Driver Framework (UMDF) host process, and gain the stability and security benefits of a UMDF driver. If a WPD driver crashes, only the sandbox process will be affected; the WPD application process is isolated from the driver instability. In a similar way, a WPD driver's process is not affected when a WPD client application crashes.

 

The Verdict

WPD and WMDM have a lot of overlap in terms of the device operations that each enables.   WMDM is geared specifically towards portable media player applications and scenarios, with media-centric metadata schema, support for device playback control, and direct access to Windows Media Digital Rights Management (WMDRM) application interfaces.   WPD supports much of what WMDM does (including WMDRM transfers), plus many key advantages over WMDM, including stability, UMDF integration, comprehensive property and content type schema (beyond portable media devices), extensibility for custom device solutions, broader device support, and not to mention, great development tools.

For applications that would like to connect to, control, and manage content across a wide range of devices (digital still cameras, portable media players, cellular phones, third-party device classes, and more), WPD is the clear choice.   

 

This posting is provided "AS IS" with no warranties and confers no rights.

Posted by wpdblog | 1 Comments
Filed under: ,

How to Receive WPD Arrival Event Notifications

If you followed the standard WPD events link in our previous post on WPD driver events, you may have seen the following event definition:

WPD_EVENT_DEVICE_REMOVED This event is sent when a driver for a device is being unloaded. This is typically a result of the device being unplugged.

Clients should release the IPortableDevice interface they have open on the device specified in WPD_EVENT_PARAMETER_PNP_DEVICE_ID.

In a nutshell, when a WPD device disconnects, IPortableDeviceEventCallback::OnEvent is called with this specific event GUID and the PnP Device ID in the event parameters, allowing applications to clean up the associated WPD interfaces for that device instance, display UI, etc. 

Having an event to tell an application when a device is disconnected can help applications build richer or smarter device scenarios.    Why isn't there a device arrival event, ala WPD_EVENT_DEVICE_ARRIVED, to cover the converse Disconnected -> Connected case?

The reason this hypothetical event doesn't exist is that WPD events utilize the IWDFDevice::PostEvent mechanism, which requires the WPD driver to up and running (with the device connected).    Consequently, to receive such events, a WPD application has to invoke IPortableDevice::Advise and maintain an open IPortableDevice connection.  

 

Device Interface Arrival Events to the Rescue

Fortunately, Plug-and-Play provides your application with the ability to receive broadcast device notifications on device interfaces, including WPD's.  This standard mechanism has been around before WPD, and is documented here in MSDN.   The following code snippets show how to register for WPD device interface notifications, and how to handle the corresponding arrival event.

//
// Step 1: Registering for WPD device interface PnP notification events
//

// The WPD device interface GUID
#define WPD_DEVINTERFACE L"{6ac27878-a6fa-4155-ba85-f98f491d4f33}"

GUID guidDevInterface            = GUID_NULL;
DEV_BROADCAST_DEVICEINTERFACE db = {0};
db.dbcc_size = sizeof(db);
db.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
CLSIDFromString(WPD_DEVINTERFACE, &guidDevInterface);
db.dbcc_classguid  = guidDevInterface;

hDevNotify = RegisterDeviceNotificationW(hWnd, &db, DEVICE_NOTIFY_WINDOW_HANDLE);

do error checking ...


//
// Step 2: Processing the DBT_DEVICEARRIVAL event when a WPD device is connected
//

LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{  
   switch (uMsg)
   {
       ....

       case WM_DEVICECHANGE:
       {
          switch(wParam) 
          {	    
             case DBT_DEVICEARRIVAL:
             {
                 PDEV_BROADCAST_HDR pdbh = (PDEV_BROADCAST_HDR)lParam;
                 if (pdbh != NULL && pdbh->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
                 {
                     PDEV_BROADCAST_DEVICEINTERFACE pdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lParam;
            
                     if (IsEqualGUID(pdbi->dbcc_classguid, guidDevInterface)) 
                     {
                         // Initialization is skipped here for brevity
                         // Use the symbolic name to open a PortableDevice connection
                         // pClientInfo is an IPortableDeviceValues containing the client information
                         // NOTE: Use a separate thread if your code could block
                         hr = pIPortableDevice->Open(pdbi->dbcc_name, pClientInfo);

                         do more error checking ...
                     }
		 }
                 break;         
             }
             
             ...
 
          } // switch (wParam)
       }
       break;
       ....
   } // switch (uMsg)
}

To summarize, the application will receive a WM_DEVICECHANGE message for the DBT_DEVICEARRIVAL event when the device is connected.   DEV_BROADCAST_DEVICEINTERFACE.dbcc_name is equivalent to the WPD PnP Device ID, which the application can use when calling IPortableDevice::Open.

The guidelines for processing Plug-and-Play messages apply: the event handler should process the event as quickly as possible, using a separate thread if there's a possibility of blocking execution, to avoid undue system hangs.     

 

Choices of Device Removal Events

The application will also receive a DBT_DEVICEREMOVECOMPLETE event, which can be useful for e.g. placing arrival and removal handlers in the same code module.   The difference between this event and WPD_EVENT_DEVICE_REMOVED is subtle.   The latter is a custom PnP event that is sent via WUDF during unloading of the WPD driver (usually caused by a device unplug); whereas DBT_DEVICEREMOVECOMPLETE is triggered by the removal of a device interface (also caused by a device unplug).   Usually, WPD applications that just want a notification when WPD devices are disconnected can subscribe to either event and get the same effect.

 

Further Reading (and more code samples)

To learn more on how to use PnP device notifications, refer to this detailed article from The Code Project.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 1 Comments
Filed under: ,

Driver Dev Guide: How to Post Events from your WPD Driver

DimeBy8 wrote a great post on how a WPD application can receive driver-initiated WPD PnP events; this post will describe the driver's side of our eventing story.   While this mechanism is "under the covers" for most WPD application developers, it helps to get an end-to-end picture of how stuff works in WPD land, especially if you are thinking about writing a WPD driver and application for vertical scenarios that involve posting and receiving custom events.

 

Standard WPD Events and Parameters

Here is the list of standard WPD events.   These GUIDs are defined in PortableDevice.h.   When you begin accessing these events as a WPD application or driver), you may soon realize a missing piece of information: what/where are the parameters for these events?    The Event parameters are described in more detail in the Basic WPD Device Driver Requirements WHDC whitepaper, under Requirements for Event Parameters.  

snippet from the whitepaper that describes the required parameters for WPD_EVENT_OBJECT_UPDATED:

  WPD_EVENT_OBJECT_UPDATED

  Parameters

  WPD_EVENT_PARAMETER_PNP_DEVICE_ID

  Required.

  WPD_EVENT_PARAMETER_EVENT_ID

  Required.

  WPD_OBJECT_PERSISTENT_UNIQUE_ID

  Required.

  WPD_EVENT_PARAMETER_OBJECT_PARENT_PERSISTENT_UNIQUE_ID

  Required.

  WPD_EVENT_PARAMETER_CHILD_HIERARCHY_CHANGED

  Required if the object hierarchy at any level under this object has changed.

Event parameters matter when you're sending a standard WPD event from your driver.   For example, when receiving WPD_EVENT_OBJECT_UPDATED, applications may utilize information such as WPD_OBJECT_PERSISTENT_UNIQUE_ID to determine which object's properties would need to be refreshed in their properties view.   

*Aside: One consequence of supporting any WPD event that requires the Persistent Unique ID of objects is that your driver needs to support this property (but this is a required property anyway for most content objects).   The list of required properties is also covered in Basic WPD Device Driver Requirements.

 

Posting a WPD Event from Your Driver

Now that you know what parameters to send with a WPD event, posting the event is a three step process:

  1. Initialize an IPortableDeviceValues to hold the event parameters.
  2. Serialize the IPortableDeviceValues to a BYTE buffer.
  3. Call IWDFDevice::PostEvent with the EventGuid = WPD_EVENT_NOTIFICATION and the serialized buffer.  Only WdfEventBroadcast events are supported, which means all WPD applications that call IPortableDevice::Advise can receive the event your driver posts.
BYTE* pBuffer  = NULL;
DWORD cbBuffer = 0;

// Populate the event parameters
hr = pIPortableDeviceValues->SetGuidValue(WPD_EVENT_PARAMETER_EVENT_ID, WPD_EVENT_OBJECT_UPDATED);

// Set other required event parameters here ....

if (hr == S_OK)
{
    // Serialize the event parameters to a buffer 
    hr = pWpdSerializer->GetBufferFromIPortableDeviceValues(pIPortableDeviceValues, &pBuffer, &cbBuffer);
}

// Send the event.
if (hr == S_OK)
{
    hr = pIWDFDevice->PostEvent(WPD_EVENT_NOTIFICATION, WdfEventBroadcast, pBuffer, cbBuffer);
}

// Cleanup ...
CoTaskMemFree(pBuffer);
pBuffer = NULL;

 

Defining a Custom WPD Event (and adding it to WpdInfo/WpdMon's Vocab!)

WPD's extensibility mechanism allows for custom events to be defined very easily.   You define a new GUID for your custom event, and define PROPERTYKEYs for your event parameters (or reuse existing PROPERTYKEYs).   On the application side, check for this custom event GUID the same way as you handle a WPD event, and process the event.   Your driver should also report these new event(s) when responding to WPD_COMMAND_CAPABILITIES_GET_SUPPORTED_EVENTS and WPD_COMMAND_CAPABILITIES_GET_EVENT_OPTIONS.

Both the WPDInfo and WPDMon WDK tools can receive and display custom WPD events in the raw GUID form, which means you already have a handy way to validate your driver's event posting functionality without needing to write a WPD application. 

You can even "teach" our tools to translate the custom event GUID to its symbolic name.   This can be done by adding an entry to the WpdInfo.GUIDs file (this is a text file usually located in the same folder path as the tool exes), and restarting the tools to reload this file.   In a similar way, symbolic names for your custom PROPERTYKEYs can go into WpdInfo.Properties file, custom commands in WpdInfo.Commands, etc.  

It's important to note that updating these WpdInfo.* dictionary files only enables the WPD tools in your PC to translate custom GUIDs and PROPERTYKEYs to symbolic string like names, other WPD applications and drivers know nothing on these custom values that are specific to your driver and application.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 4 Comments
Filed under: ,

Getting ERROR_BUSY (0x800700AA) after reading data from an MTP device?

One common cause of the ERROR_BUSY (0x800700AA) in MTP devices is when the application reads resource data from a content object on the portable device using IStream::Read(), and does not complete the read with an IStream::Close().   Subsequent operations that eventually translate into MTP commands into the device could fail with ERROR_BUSY because the resource read operation is not yet complete.     Sometimes the failure could appear delayed -  after the resource read, there could be several successful WPD API calls before ERROR_BUSY appears, because the interim calls that succeeded were handled by the MTP driver (i.e. do not require the driver to talk to the device).

From the device's perspective, reading resource data translates to a sequence of operations involving the MTP command "GetObject".    This has to be completed or cancelled before further MTP commands to the device could proceed.   

For example, if your application does the following:


IStream* pObjectDataStream = NULL;

// 1. Open the default resource of an object with read access
HRESULT hr = pIPortableDeviceResources->GetStream(wszObjectID, 
                                                     WPD_RESOURCE_DEFAULT, 
                                                     STGM_READ, 
                                                     &cbOptimalTransferSize,
                                                     &pObjectDataStream);

// 2. Read some data from the resource stream created by GetStream
if (hr == S_OK)
{
  hr = pObjectDataStream->Read(pObjectData, cbOptimalTransferSize, &cbBytesRead);
}

// 3. Do other operations that translate into MTP commands to the device
// and they could result in 0x800700AA as the resource read operation is not complete

To fix this, ensure that IStream::Release() is called to complete or cancel the current read operation before proceeding


IStream* pObjectDataStream = NULL;

// 1. Open the default resource of an object with read access
HRESULT hr = pIPortableDeviceResources->GetStream(wszObjectID, 
                                                     WPD_RESOURCE_DEFAULT, 
                                                     STGM_READ, 
                                                     &cbOptimalTransferSize,
                                                     &pObjectDataStream);

// 2. Read some data from the resource stream created by GetStream
if (hr == S_OK)
{
  hr = pObjectDataStream->Read(pObjectData, cbOptimalTransferSize, &cbBytesRead);
}

// 3. IMPORTANT: Release the IStream if no longer using it to ensure that the read operation
// completes.   
if(pObjectDataStream)
{
    pObjectDataStream->Release();  // If using a CComPtr, set it to NULL
}

// 4. Proceed on to the other operations

If your application is reading resource data from an MTP device, and you are seeing ERROR_BUSY, check that you are always calling Release() on the IStream objects that you have opened.    This also applies if you are using smart pointers (e.g. CComPtr<IStream>), in which case you can explicitly set the pointer to NULL to tell the smart pointer to Release() itself before you proceed on to other device operations.   This will release the pointer after you have completed your resource read/write operation(s), instead of relying on the IStream smart pointer to get out of scope.   

Similarly, if your application is writing resource data to an MTP device, ensure that you're calling IStream::Commit() or IStream::Revert() to complete the write operation. 

[June 11, 2007] Thanks to drlily for pointing out that IStream::Close does not actually exist.  It should have been IStream::Release for a read IStream, and IStream::Commit, IStream::Revert, or IStream::Release for a write IStream.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by wpdblog | 1 Comments
Filed under: ,
More Posts Next page »
 
Page view tracker