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:
- Disassemble the PortableDeviceApi interop using the command -
ildasm Interop.PortableDeviceApiLib.dll /out:pdapi.il - Open the IL in Notepad and search for the following string
instance void GetDevices([in][out] string& marshal( lpwstr) pPnPDeviceIDs, - Replace all instances of the string above with the following string
instance void GetDevices([in][out] string[] marshal([]) pPnPDeviceIDs, - 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
Anonymous
January 29, 2007
How can I get FriendlyName for my portable device in C#? Counld you mind to tell me the steps?Anonymous
February 14, 2007
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.Anonymous
February 25, 2007
The comment has been removedAnonymous
April 30, 2007
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 ?Anonymous
April 30, 2007
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.Anonymous
May 01, 2007
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 ?Anonymous
May 02, 2007
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; }Anonymous
September 06, 2007
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
Anonymous
September 06, 2007
The comment has been removedAnonymous
October 03, 2007
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!Anonymous
October 03, 2007
jtackabury: Just digging out the contants and helper classes I use. I'll mail them to you shortly.Anonymous
August 20, 2008
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();Anonymous
August 20, 2008
Thanks Chris, I'm glad there is a cleaner solution! I'll pass that on to the guys who maintain my device code now. TreeAnonymous
January 03, 2009
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 ? ThanksAnonymous
December 14, 2012
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 ^^Anonymous
July 09, 2013
Can I get portable device(WPD) and MTP device memory detail in this?Anonymous
November 13, 2013
Thanks, Mathias Muenzner. I suspected something like this. You solved the puzzle!Anonymous
February 12, 2014
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 2012Anonymous
October 12, 2014
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 patternsAnonymous
April 03, 2015
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"Anonymous
April 03, 2015
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"