WPD Collection Interfaces (Part 1)

The WPD collection interfaces are documented here in MSDN. These interfaces are used extensively as part of the WPD APIs. WPD clients use them to pass information to the driver, and the driver uses them to return information back to the client. We'll try understanding them in tandem with some API usage. Since we are really concentrating on the collection interfaces, I won't elaborate on the APIs in the examples.

There are four collection interfaces - three of which can be thought of as a plain list-style collection (the ones ending with *Collection), and the fourth can be thought of as a map-style collection.

IPortableDeviceKeyCollection

This holds a collection of PROPERTYKEYs. Here is the WPD view of a PROPERTYKEY and it is a re-use of the Shell PROPERTYKEY structure. To simplify things, PROPERTYKEYs can be thought of as GUIDs. PortableDevice.h already has a bunch of pre-defined PROPERTYKEYs which you can refer to by name.

PROPERTYKEYs are primarily used to identify properties (or give each of them a name). Examples are WPD_OBJECT_ID (most heavily used), WPD_OBJECT_NAME, WPD_OBJECT_FORMAT, etc.

Consequently they are most commonly used in the property retrieval APIs to specify the properties the client is interested in. The example below shows how to retrieve some properties of the DEVICE object (the top-level object in the WPD hierarchy).

 // We'll need the IPortableDeviceProperties interface to
// use the property API set and that is retrieved via Device>Content
CComPtr<IPortableDeviceProperties> spProperties;
CComPtr<IPortableDeviceContent> spContent;
hr = spDevice->Content(&spContent);
hr = spContent->Properties(&spProperties);

// We'll need an IPortableDeviceKeyCollection instance 
CComPtr<IPortableDeviceKeyCollection> spKeys;
hr = CoCreateInstance(CLSID_PortableDeviceKeyCollection,
                      NULL,
                      CLSCTX_INPROC_SERVER,
                      IID_IPortableDeviceKeyCollection,
                      (VOID**)spKeys);

// Specify the properties that we need to retrieve
hr = spKeys->Add(WPD_DEVICE_FRIENDLY_NAME);
hr = spKeys->Add(WPD_DEVICE_POWER_LEVEL);
hr = spKeys->Add(WPD_DEVICE_DATETIME);

// Use the IPortableDeviceProperties::GetValues method to retrieve
// the properties for the DEVICE object
CComPtr<IPortableDeviceValues> spValues;
hr = spProperties->GetValues(L"DEVICE", spKeys, &spValues);

 At this point, (if hr == S_OK) spValues contains the values for the properties that we requested. Since spValues is of type IPortableDeviceValues, we'll take a look at that interface next.

IPortableDeviceValues

I've mentioned before that an IPortableDeviceValues collection can be thought of as a map - PROPERTYKEYs are the keys and PROPVARIANTs are the values. To retrieve a value, we'll need to know the associated PROPERTYKEY. Alternatively, you can also iterate through the collection using an index and retrieve both the PROPERTYKEY and the PROPVARIANT at each index. We'll illustrate the former approach since we explicitly specified the keys that we were interested in. This will also let us check out the type-specific methods of IPortableDeviceValues.

 // The friendly name property is spec'd out to be a string (VT_LPWSTR)
LPWSTR pwszName = NULL;
hr = spValues->GetStringValue(WPD_DEVICE_FRIENDLY_NAME, &pwszName);

// Power level is spec'd to be an integer (VT_UI4)
ULONG ulPowerLevel = 0;
hr = spValues->GetUnsignedIntegerValue(WPD_DEVICE_POWER_LEVEL, &ulPowerLevel);

// DATETIME is spec'd to be VT_DATE - now there isn't a date-compatible
// method for IPortableDeviceValues, so we have to get the PROPVARIANT directly
PROPVARIANT pvDateTime = {0};
hr = spValues->GetValue(WPD_DEVICE_DATETIME, &pvDateTime);

// Process the retrieved values
:

// Cleanup (required!)
CoTaskMemFree(pwszName);
PropVariantClear(&pvDateTime);

 Most of the time, you (as an application writer) will know the type of the property being returned and the type-specific functions can be life-savers (versus having to use plain GetValue() and worrying about the type before doing something useful with the PROPVARIANT). The type-specific functions will also attempt to change the type for you in case you called the wrong method for the wrong type. So GetStringValue for WPD_DEVICE_DATETIME will actually return a well-formatted date-time string! Of course, if the PROPVARIANT contained an error (vt=VT_ERROR), the type-conversion will not be performed and you will get the actual error code back (pv.scode) as the return HRESULT for the Get*Value method.

The Get*Value methods also have their corresponding Set*Value methods which can be used to add (or modify) pairs into the collection. You will use these while attemping to set a property on the device and we'll take a look at this when we touch the IPortableDeviceProperties::SetValues method.