WPD Collection Interfaces (Part 2)

In the previous part, we took a look at IPortableDeviceKeyCollection and IPortableDeviceValues. In this post, we'll examine the IPortableDevicePropVariantCollection (quite a mouthful :) collection.

IPortableDevicePropVariantCollection

The documentation for this provides a pretty good idea of what it does. In a nutshell, this kind of collection is used to hold PROPVARIANTs with the restriction that each instance can only contain PROPVARIANTs of the same VARTYPE. The VARTYPE of the collection is determined by the very first PROPVARIANT that is added to it.

IPortableDevicePropVariantCollection interfaces are most commonly seen in the device capability interrogation APIs. For starters though, we'll look at an example where we provide it as input to the Delete API and get it back as output as well.

 // Interrogate for the IPortableDeviceContent interface
// since the API (Delete) is a method off it
CComPtr<IPortableDeviceContent> spContent;
hr = spDevice->Content(&spContent);

// Create an IPortableDevicePropVariantCollection instance to
// store object IDs in to be deleted
CComPtr<IPortableDevicePropVariantCollection> spObjectIDs;
hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection,
                      NULL,
                      CLSCTX_INPROC_SERVER,
                      IID_IPortableDevicePropVariantCollection,
                      (VOID**)spObjectIDs);

// Declare a PROPVARIANT variable since we'll need it to add 
// object IDs to the collection
PROPVARIANT pvObjectId = {0};

// Add object IDs that need to be deleted to the collection 
// Documentation for Delete states that input collection must be of
// type VT_LPWSTR
pvObjectId.vt = VT_LPWSTR;
pvObjectId.pwszVal = L"o1";
hr = spObjectIDs->Add(&pvObjectId);

// Delete the objects - results for each object ID are returned
// in a second collection
CComPtr<IPortableDevicePropVariantCollection> spResults;
hr = spContent->Delete(PORTABLE_DEVICE_DELETE_WITH_RECURSION,
                       &spObjectIDs,
                       &spResults);

As you noticed, we had to create a temporary PROPVARIANT structure for use. This is kind of a limitation with the IPortableDevicePropVariantCollection in the sense that, unlike the IPortableDeviceValues collection, it does not have type-specific overloads. The Delete API also returns a second collection which contains the results for the operation. Each PROPVARIANT in the returned collection corresponds (index-wise) to the PROPVARIANT in the input collection but is of type VT_ERROR to indicate the HRESULT success code of the operation.

 // Iterate through the results to get the status of the Delete operation
DWORD cdwElems = 0;
hr = spResults->GetCount(&cdwElems);

for (DWORD i = 0; i < cdwElems; i++)
{
    PROPVARIANT pvResult = {0};
    // GetAt allocates memory for the PROPVARIANT if needed
    hr = spResults->GetAt(i, &pvResult);

    // Reference the .scode member only if the VARTYPE is what we expect
    if (pvResult.vt == VT_ERROR)
    {
        printf("Delete result[%d]=0x%08X\n", pvResult.scode);
    }

    // Clear the returned PROPVARIANT (Note: We don't really
    // need it here since the VT is VT_ERROR and that doesn't
    // require memory allocation)
    PropVariantClear(&pvResult);
}

Iterating through the collection is pretty easy and this is actually the same pattern for the other collection interfaces. Be sure to check that the VARTYPE of the retrieved PROPVARIANT is what you expect, especially when dealing with VT_LPWSTR. You could always get back a VT_ERROR if something went wrong inside the driver and in that case de-referencing pv.pwszVal will not bode well for your application!