Enumerating WPD devices in C#


Let’s take a look at how we can enumerate WPD devices in C#. This post assumes that you already have a project set up using these instructions. (Update: You will also need to follow the disassembly/reassembly instructions below for this to work correctly.)

static void Main(string[] args)
{
    //
    // Get an instance of the device manager
    //
    PortableDeviceApiLib.PortableDeviceManagerClass devMgr 
        = new PortableDeviceApiLib.PortableDeviceManagerClass();

    //
    // Probe for number of devices
    //
    uint cDevices = 1;
    devMgr.GetDevices(null, ref cDevices);

    //
    // Re-allocate if needed
    //
    if (cDevices > 0)
    {
        string[] deviceIDs = new string[cDevices];
        devMgr.GetDevices(deviceIDs, ref cDevices);

        for (int ndxDevices = 0; ndxDevices < cDevices; ndxDevices++)
        {
            Console.WriteLine("Device[{0}]: {1}", 
                    ndxDevices + 1, deviceIDs[ndxDevices]);
        }
    }
    else
    {
        Console.WriteLine("No WPD devices are present!");
    }
}

This code maps almost line-by-line to the C++ enumeration example. We manufacture an instance of the device manager using PortableDeviceApiLib.PortableDeviceManagerClass. We then use the instance to probe how many devices are there. If you remember, the GetDevices API will return back in cDevices the number of devices (actually the size of the string array that should be specified to retrieve all the device IDs).

Once we know the number of devices, we allocate an appropriately sized array and then call GetDevices again. This time, the array will contain all the device IDs. To display them, we simply iterate over the array and print them out.

Gotchas:

  • We could use the string[]  notation since the GetDevices API returned an array of CoTaskMemAlloc’d LPWSTR strings
  • We can’t use the string datatype for API calls that require a buffer allocated by the caller. GetDeviceFriendlyName is an example of this – such API calls require prior allocation of ushort[] and then on success, character-by-character conversion to a C# string.

Update

Mike R. brought to my notice that the above sample only enumerates one device even if more than one are connected. This is a marshalling restriction – we can work around it by manually fixing up the generated Interop assembly. Follow the steps below to edit the assembly:

  1. Disassemble the PortableDeviceApi interop using the command –
    ildasm Interop.PortableDeviceApiLib.dll /out:pdapi.il
  2. Open the IL in Notepad and search for the following string
    instance void  GetDevices([in][out] string&  marshal( lpwstr) pPnPDeviceIDs,
  3. Replace all instances of the string above with the following string
    instance void  GetDevices([in][out] string[]  marshal([]) pPnPDeviceIDs,
  4. Save the IL and reassemble the interop using the command –
    ilasm pdapi.il /dll /output=Interop.PortableDeviceApiLib.dll

You can now rebuild your project. You can now first call GetDevices with a NULL parameter to get the count of devices and then call it again with an array to get the device IDs.


Comments (21)

  1. sinchun says:

    How can I get FriendlyName for my portable device in C#? Counld you mind to tell me the steps?

  2. Tree says:

    I tested the interop mod mentioned in the blog update, and while it works fine with .NET 2.0, a runtime MissingMethodException is thrown when attempting to run code which calls GetDevices using .NET 1.1. I guess this is just a limitation in 1.1 when defining method signatures.

  3. Tree says:

    There are potentially a large number of updates similar to the GetDevices ‘hack’ mentioned here. Pretty much all the direct methods on PortableDeviceManager are missing an array parameter to make them functional in C#. For example GetDeviceFriendlyName has a ‘ref ushort pDeviceFriendlyName’ parameter which really should to be ‘ushort[] pDeviceFriendlyName’, where the array is passed in. To fix this find GetDeviceFriendlyName in the IL file as mentioned in the blog entry update and replace:

    [in][out] uint16& pDeviceFriendlyName

    with:

    [in][out] uint16[] marshal( []) pDeviceFriendlyName

    If you really want to use this, then the condensed (i.e. exception handlers are removed) usage is like this:

     //Get friendly name length

     uint nameLength = 0;

     pdManager.GetDeviceFriendlyName(device.WPDID, null, ref nameLength);

     //Get name as a ushort buffer with known namelength

     ushort[] nameBuffer = new ushort[nameLength];

     pdManager.GetDeviceFriendlyName(deviceID, nameBuffer, ref nameLength);

     //convert to string

     string friendlyName = "";

     foreach (ushort letter in nameBuffer)

       if (letter != 0) friendlyName += (char)letter;

     Console.WriteLine("FriendlyName is " + friendlyName);

    The same is true for pretty much all the device manager methods. You can completely ignore these methods though and (assuming you have the device id) find friendlyname, manufacturer, etc, on the device properties interface, as descibed in this blog in a later entry.

    Another change made so far in my progress is the IStream and ISequentialStream interfaces in both PortableDeviceApiLib and PortableDeviceTypesLib, where RemoteRead and RemoteWrite require their first parameter (the byte buffer) to be made into an array (i.e. change "uint8& pv" to be "uint8[] pv" for these methods (and no marshal here).

    I expect there will be a whole lot more changes before I’ve finished my current development.

  4. jdawes says:

    Hello !

    I have just started creating a c#-library for accessing a portable device.

    Your articles have helped me a lot, but now I am struggeling with the RemoteRead and RemoteWrite functions.

    I have tried fixing the interop, but it looks like the data returned is corrupted.

    I am trying to read the DevIcon.fil on my device. The RemoteRead function returns a buffer with the correct size, but there is a problem using the buffer (write to file or Bitmap.FromStream( MemoryStream )).

    Have you done anything more on your WPD project ?

  5. Tree says:

    Hi there jdawes

    I ran into the problem you mention a while back. And the data you are getting is garbage. I think it’s because the RemoteRead/Write are driver level calls, something like that anyway. I’ve found you can do bad things to some devices using those remote calls. The solution is quite simple: cast the PortableDeviceLibApi.IStream object to a generalised System.Runtime.InteropServices.ComTypes.IStream. Then use the Read and Write methods on this object. It works just fine as a ComTypes.IStream object with everything I tried.

  6. jdawes says:

    Hello Tree !

    I have tried with …ComTypes.IStream, but I get the same result.

    Have you done any changes to the IPortableDeviceResources.GetStream in the interop ?

    Could you please give me an example of how you do this ?

  7. Tree says:

    Here’s a bit of code which I use to read an xml doc. Some of it is specific to our code, but you should get the idea. I have to admit it’s not perfect. The problem exists because the pReadBytes from the read call always returns 0, because the interop can’t do anything else, so you can’t tell if you’ve finished reading without a slow check for a the buffer. I tried fixing the interop a number of ways, but I’m no expert and had no success.

    My mods to the interop are fairly minimal, and not related to streams, although as I mentioned earlier I tweaks the remoteRead and remotewrite, but I don’t use them anymore.

    Here’s the code I use which works:

    XmlDocument GetXMLDocument(string fileId)

    {

    //need a contents object to get the propeties

    IPortableDeviceContent content;

    PortableDevice.Content(out content);

    //get transfer interface to this device

    IPortableDeviceResources resources;

    content.Transfer(out resources);

    IStream wpdStream;

    uint optimalTransferSize = 0;

    resources.GetStream(fileId, ref WPDPropertyKey.WPD_RESOURCE_DEFAULT, 0, ref optimalTransferSize, out wpdStream);

    //convert to a useful stream object

    System.Runtime.InteropServices.ComTypes.IStream sourceStream =

    (System.Runtime.InteropServices.ComTypes.IStream)wpdStream;

    XmlDocument document = new XmlDocument();

    ////////Stream bit

    using (MemoryStream mStream = new MemoryStream(1000))

    {

    int totalBytesRead = 0;

    byte[] streamBuffer = new byte[optimalTransferSize];

    IntPtr pBytesRead = new IntPtr();

    int writeBytes = 0;

    do

    {

    sourceStream.Read(streamBuffer, (int)optimalTransferSize, pBytesRead);

    if (pBytesRead.ToInt32() == 0)

    writeBytes = Array.FindIndex(streamBuffer, CheckEndBuffer);

    else

    writeBytes = pBytesRead.ToInt32();

    totalBytesRead += writeBytes;

    mStream.Write(streamBuffer, 0, writeBytes);

    } while ((writeBytes > 0) && (optimalTransferSize == writeBytes));

    mStream.Flush();

    mStream.Seek(0, SeekOrigin.Begin);

    //load and interpret

    if (sourceStream != null) document.Load(mStream);

    }

    return document;

    }

  8. Ociter says:

    Tree,

    I’m pretty new at .NET and C# and would really appriciate your help.

    Regarding your comment:

    # re: Enumerating WPD devices in C#

    Thursday, February 15, 2007 5:53 AM by Tree

    I tested the interop mod mentioned in the blog update, and while it works fine with .NET 2.0, a runtime MissingMethodException is thrown when attempting to run code which calls GetDevices using .NET 1.1. I guess this is just a limitation in 1.1 when defining method signatures.

    I am getting the MissingMethodException when calling GetDevices. I modified the il as specified by dimeby8 and thus it compiles. I have .NET 2.0 as well as .NET 1.1 installed on my machine. How do I specify .NET2.0?

    Thanks a bunch,

    Chris

  9. Tree says:

    The way I rebuild the interop for .net 2 is simply to use the .NET 2.0 ildasm disassembler and ilasm assembler, You’ll find them in different places. This is literally what I do:

    1. Generate the initial interop in VS 2005 (or on the command line I guess)

    2. Run "C:Program FilesMicrosoft Visual Studio 8SDKv2.0Binildasm" inputInterop.PortableDeviceApiLib.dll /out:pdapi2_0.il

    3. Make interop tweaks

    4. Reassemly the interop with: "C:WINDOWSMicrosoft.NETFrameworkv2.0.50727ilasm" pdapi2_0.il /dll /output=outputInterop.PortableDeviceApiLib.dll

  10. Tree: Would you be able to send me your WPDPropertyKey class?  I seem to be having a hard time find the required header files and creating this class.  If you can, please send it to jtackabury (AT) binaryfortress.com.

    Thanks!

  11. Tree says:

    jtackabury: Just digging out the contants and helper classes I use. I’ll mail them to you shortly.

  12. Ociter says:

    Tree,

    I figured out how to get at the returned pReadBytes for the sourceStream.Read function in case you are interested.

    Chris

    GCHandle hBytesRead = GCHandle.Alloc(new ulong(), GCHandleType.Pinned);

    do

    {

    sourceStream.Read(streamBuffer, (int)optimalBufferSize, hBytesRead.AddrOfPinnedObject());

    totalBytesRead += (ulong)hBytesRead.Target;

    } while ((ulong)hBytesRead.Target > 0);

    hBytesRead.Free();

  13. Tree says:

    Thanks Chris, I’m glad there is a cleaner solution! I’ll pass that on to the guys who maintain my device code now.

    Tree

  14. viewon01 says:

    Hi,

    I try to use the "GetStream" method but I got an exception "E_ACCESSDENIED".

    It is on a "Zune" !

    Here is the code :

    uint optimalTransferSize = 0;

    PortableDeviceApiLib.IStream wpdStream;

    deviceResources.GetStream((string)music.Tag,

    ref PropertyKeys.WPD_RESOURCE_DEFAULT,0, ref optimalTransferSize, out wpdStream);

    Can you help me, I have no idea to solve this problem ?

    Thanks

  15. Matthias Muenzner says:

    I just wanted to use that disassemble -> change -> assemble trick and it worked fine but

    if you want to marshal long strings it will not work.

    The compiler throws a DirectiveException (Marshaler restriction: Excessively long string)

    So i changed the lines:

     instance void  GetDevices([in][out] string[]  marshal([]) pPnPDeviceIDs,

                                       [in][out] uint32& pcPnPDeviceIDs) runtime managed internalcall

    to

     instance void  GetDevices([in][out] string[]  marshal( lpwstr[]) pPnPDeviceIDs,

                                       [in][out] uint32& pcPnPDeviceIDs) runtime managed internalcall

    the changed c# code is:

    void GetDevices([In][Out][MarshalAsAttribute(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] pPnPDeviceIDs, [In, Out] ref uint pcPnPDeviceIDs);

    Greetings from a fellow explorer ^^

  16. Mukesh says:

    Can I get portable device(WPD) and MTP device memory detail in this?

  17. Rudi says:

    Thanks, Mathias Muenzner. I suspected something like this. You solved the puzzle!

  18. adam says:

    I tried to get device list by your instructions, but when I first call

    manager.GetDevices(null, ref count);

    "count" is always zero.

    Does any one get this problem before?

    Environment:

    OS: Windows 7 32-bit version

    Compiler: Visual Studio 2012

  19. preguntoncojonero+blogs at gmail says:

    Any final _solution_ with **full source code** sample application ?

    _IMHO, better samples for minimize learning curve are real applications with full source code and good patterns_

  20. JMMROBLES says:

    Thanks a lot to Darene Lewis.

    dimeby8 is the best source of info about the WPD Subject.

    To the friend having an issue with the Framework 1.1 I solved just with the use of a string with a null content by reference.

    The source code: (tested on Framework 1.1 and 2.0)

    //

       // Probe for number of devices

       //

       uint cDevices = 1;

       string PNPNames = null;

       devMgr.GetDevices(ref PNPNames, ref cDevices);

    Is not common to have to work with a such older framework but, sometimes things happens. hehe

    I hope somebody finds it useful.

    Best Regards

    José Manuel

    "It's my time to give something back"

  21. JMMROBLES says:

    Thanks a lot to Darene Lewis.

    dimeby8 is the best source of info about the WPD Subject.

    To the friend having an issue with the Framework 1.1 I solved just with the use of a string with a null content by reference.

    The source code: (tested on Framework 1.1 and 2.0)

    //

       // Probe for number of devices

       //

       uint cDevices = 1;

       string PNPNames = null;

       devMgr.GetDevices(ref PNPNames, ref cDevices);

    Is not common to have to work with a such older framework but, sometimes things happens. hehe

    I hope somebody finds it useful.

    Best Regards

    José Manuel

    "It's my time to give something back"