WPD property retrieval in C#

WPD property values are retrieved as PROPVARIANTs through the WPD API. Unfortunately there is no native support for PROPVARIANTs in C#. To correctly retrieve the property values (or anything that is in a PROPVARIANT) through the WPD API in C#, we must use marshalling.

PortableDeviceApiLib.tag_inner_PROPVARIANT is the data-type exposed through the WPD Interop layer. This data-type exposes the .vt member, but it does not expose the anonymous union which is the raison d'être for the PROPVARIANT structure.

To get to the union, we need to re-define the PROPVARIANT data-type in C#. We can get by with the following definition:

 using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct PropVariant
{
    [FieldOffset(0)]
    public short variantType;
    [FieldOffset(8)]
    public IntPtr pointerValue;
    [FieldOffset(8)]
    public byte byteValue;
    [FieldOffset(8)]
    public long longValue;
} 

This follows exactly the struct layout of a PROPVARIANT. We haven't included all the PROPVARIANT fields, feel free to add them as and when you need them. For our example, we'll manage with the ones above.

Now that we have a C# specific PROPVARIANT definition, we should be able to convert back and forth to the interop version. This isn't as easy as a simple cast - we'll have to use marshalling to do this.

To convert from the interop tag_inner_PROPVARIANT to our C# PropVariant:

 PortableDeviceApiLib.tag_inner_PROPVARIANT ipValue = 
                     new PortableDeviceApiLib.tag_inner_PROPVARIANT();

IntPtr ptrValue = Marshal.AllocHGlobal(Marshal.SizeOf(ipValue));
Marshal.StructureToPtr(ipValue, ptrValue, false);

PropVariant pv = (PropVariant)Marshal.PtrToStructure(p, typeof(PropVariant));

To convert from our C# PropVariant to the interop tag_inner_PROPVARIANT:

 PropVariant pvValue = new PropVariant();

IntPtr ptrValue = Marshal.AllocHGlobal(Marshal.SizeOf(pvValue));
Marshal.StructureToPtr(pvValue, ptrValue, false);

PortableDeviceApiLib.tag_inner_PROPVARIANT ipValue =
    (PortableDeviceApiLib.tag_inner_PROPVARIANT)
    Marshal.PtrToStructure(ptrValue, typeof(PortableDeviceApiLib.tag_inner_PROPVARIANT));

Essentially what we are doing for both cases is getting a pointer to the original structure and then casting it back (via marshalling) to the second structure.

Now let's see this in action - we'll attempt to retrieve all the properties for the DEVICE object and display the value for all string properties.

 static void DisplayProperties(
    PortableDeviceApiLib.PortableDeviceClass pPortableDevice, 
    string objectID)
{
    //
    // 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);

    //
    // Call the GetValues API, we specify null to indicate we
    // want to retrieve all properties
    //
    PortableDeviceApiLib.IPortableDeviceValues pPropValues;
    pProperties.GetValues(objectID, null, out pPropValues);

    //
    // Get count of properties
    //
    uint cPropValues = 0;
    pPropValues.GetCount(ref cPropValues);
    Console.WriteLine("Received " + cPropValues.ToString() + " properties");

    for (uint i = 0; i < cPropValues; i++)
    {
        //
        // Retrieve the property at index 'i'
        //
        PortableDeviceApiLib._tagpropertykey propKey = 
                        new PortableDeviceApiLib._tagpropertykey();
        PortableDeviceApiLib.tag_inner_PROPVARIANT ipValue = 
                        new PortableDeviceApiLib.tag_inner_PROPVARIANT();
        pPropValues.GetAt(i, ref propKey, ref ipValue);

        //
        // Allocate memory for the intermediate marshalled object
        // and marshal it as a pointer
        //
        IntPtr ptrValue = Marshal.AllocHGlobal(Marshal.SizeOf(ipValue));
        Marshal.StructureToPtr(ipValue, ptrValue, false);

        //
        // Marshal the pointer into our C# object
        //
        PropVariant pvValue = 
            (PropVariant)Marshal.PtrToStructure(ptrValue, typeof(PropVariant));

        //
        // Display the property if it a string (VT_LPWSTR is decimal 31)
        //
        if (pvValue.variantType == 31 /*VT_LPWSTR*/)
        {
            Console.WriteLine("{0}: Value is \"{1}\"", 
                (i + 1).ToString(), Marshal.PtrToStringUni(pvValue.pointerValue));
        }
        else
        {
            Console.WriteLine("{0}: Vartype is {1}", 
                (i + 1).ToString(), pvValue.variantType.ToString());
        }
        //PropVariant pv = new PropVariant(ip);
    }
}

The example assumes that a device connection has already been opened. It also assumes that the PropVariant definition (from above) is present somewhere in the class definition.

We need to start with obtaining the IPortableDeviceProperties interface. Once we have the interface, we call the GetValues API to retrieve the properties. This is in a ValuesCollection object, so we use GetAt to retrieve each property. The retrieved property is of type tag_inner_PROPVARIANT which we convert to our C# PropVariant definition using marshalling. Once we have the data in our PropVariant structure, we can reference the string by marshalling the pointer value in the PropVariant to a string object.

As an exercise, try extending this example to display the value for properties of different vartypes.  In our next post, we'll see how we can set property values.

Comments

  • Anonymous
    December 11, 2006
    Since setting a WPD property requires manipulating a PROPVARIANT structure through interop, we must make

  • Anonymous
    December 12, 2006
    This post is a continuation of the one covering WPD property retrieval in C# . That post explained how

  • Anonymous
    February 13, 2007
    When calling DisplayProperties, what are the possible objectID string values?  Are they string values of enumerated constants?  I've searched the documentation and the net until I'm crosseyed and can't figure it out. Thanks for your help.

  • Anonymous
    February 19, 2007
    The "root" value is the string "DEVICE".  Other than that, the string can be any objectID on the device.  No directory traversal is necessary to refer to an object that may appear to be several conventional directory levels deep.  o90002, for example, used as the objectID when calling DisplayProperties, will return that object's properties no matter where it is located on the device (i.e. an mp3 nested 3 dirs down from root).

  • Anonymous
    July 12, 2010
    I've been trying to implement this code for hours and I keep getting a System.ArgumentException saying "Value does not fall within the expected range" on the pProperties.GetValues() call. I think it's the pPropValues that it doesn't seem to like. Is there anything I can change to fix this, or to confirm the source of the error?

  • Anonymous
    January 04, 2012
    Its a very helpful article as there isn't much help elsewhere. Thank you. Though there is one question: I just wanted to get one property, e.g, WPD_OBJECT_NAME. So i must use IPortableDeviceKeyCollection and then add _tagpropertykey to it. But i cant get an object of  IPortableDeviceKeyCollection. I tried as shown:            PortableDeviceApiLib.IPortableDeviceKeyCollection pKeys;            pProperties.GetSupportedProperties(objectID, out pKeys);            pKeys.Clear();            PortableDeviceApiLib._tagpropertykey pkey = new PortableDeviceApiLib._tagpropertykey();            pkey = PortableDeviceConstants.PortableDevicePKeys.WPD_OBJECT_NAME;            pKeys.Add(pkey);            //            // Call the GetValues API, we specify null to indicate we            // want to retrieve all properties            //            PortableDeviceApiLib.IPortableDeviceValues pPropValues;            pProperties.GetValues(objectID, pKeys, out pPropValues); As you can see it involves getting number of supported properties. Instead can i get the pKeys directly. Kindly help.

  • Anonymous
    January 05, 2012
    Solved it using PortableDeviceApiLib.IPortableDeviceKeyCollection pKeys =                (PortableDeviceApiLib.IPortableDeviceKeyCollection)                new PortableDeviceTypesLib.PortableDeviceKeyCollectionClass();

  • Anonymous
    August 07, 2012
    I do get the same as Blazer and to create a IPortableDeviceKeyCollection first which is used for pProperties.GetValues(..) does not solve the Exception. Why is that? How do you initialize the devices and so on? Could you please post one complete code example?

  • Anonymous
    August 07, 2012
    do not use: pProperties.GetValues(myDeviceIDPathAsString, null, out pPropValues); but use: pProperties.GetValues("DEVICE", null, out pPropValues); instead ! "DEVICE" is the root element of device objects accessible in that way.