Welcome to MSDN Blogs Sign in | Join | Help

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.

Published Tuesday, December 05, 2006 5:06 PM by dimeby8

Comments

# re: Enumerating WPD devices in C#

Tuesday, January 30, 2007 1:45 AM by sinchun

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

# 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.

# re: Enumerating WPD devices in C#

Monday, February 26, 2007 6:01 AM by Tree

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.

# re: Enumerating WPD devices in C#

Monday, April 30, 2007 9:06 PM by jdawes

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 ?

# re: Enumerating WPD devices in C#

Tuesday, May 01, 2007 4:12 AM by Tree

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.

# re: Enumerating WPD devices in C#

Wednesday, May 02, 2007 7:33 AM by jdawes

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 ?

# re: Enumerating WPD devices in C#

Wednesday, May 02, 2007 9:36 AM by Tree

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 \0 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;

}

# re: Enumerating WPD devices in C#

Thursday, September 06, 2007 2:18 PM by Ociter

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

# re: Enumerating WPD devices in C#

Friday, September 07, 2007 3:27 AM by Tree

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 Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\ildasm" input\Interop.PortableDeviceApiLib.dll /out:pdapi2_0.il

3. Make interop tweaks

4. Reassemly the interop with: "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ilasm" pdapi2_0.il /dll /output=output\Interop.PortableDeviceApiLib.dll

# re: Enumerating WPD devices in C#

Wednesday, October 03, 2007 9:44 AM by jtackabury

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!

# re: Enumerating WPD devices in C#

Wednesday, October 03, 2007 10:24 AM by Tree

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

# re: Enumerating WPD devices in C#

Wednesday, August 20, 2008 3:27 PM by Ociter

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();

# re: Enumerating WPD devices in C#

Thursday, August 21, 2008 5:42 AM by Tree

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

# re: Enumerating WPD devices in C#

Sunday, January 04, 2009 7:27 AM by viewon01

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

Anonymous comments are disabled
 
Page view tracker