I'm trying to convert the following method from C++ to C#
HRESULT GetDevices(
[in, out] LPWSTR *pPnPDeviceIDs,
[in, out] DWORD *pcPnPDeviceIDs
);
In the MSDN documents it says:
pPnPDeviceIDs [in, out]
A caller-allocated array of string pointers that holds the Plug and Play names of all of the connected devices. To learn the required size for this parameter, first call this method with this parameter set toNULL
andpcPnPDeviceIDs
set to zero, and then allocate a buffer according to the value retrieved bypcPnPDeviceIDs
.pcPnPDeviceIDs [in, out] On input, the number of values that
pPnPDeviceIDs
can hold. On output, a pointer to the number of devices actually written topPnPDeviceIDs
.
So far I have this definition:
[PreserveSig]
int GetDevices(
[In, Out] IntPtr pPnPDeviceIDs,
[In, Out] ref uint pcPnPDeviceIDs
);
I've tried allocating some memory with Marshal.AllocCoTaskMem
for pPnPDeviceIDs
but I get an AccessViolationException when I try to call the method.
Can anyone show me the correct way to convert LPWSTR *
when its an array of string pointers?
EDIT: Here are my definitions:
[ComImport, System.Security.SuppressUnmanagedCodeSecurity,
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("a1567595-4c2f-4574-a6fa-ecef917b9a40")]
public interface IPortableDeviceManager
{
[PreserveSig]
HRESULT GetDeviceDescription(
[In] string pszPnpDeviceID,
[In, Out] ref StringBuilder pDeviceDescription,
[In, Out] ref uint pcchDeviceDescription);
[PreserveSig]
HRESULT GetDeviceFriendlyName(
[In] string pszPnPDeviceID,
[In, Out] ref StringBuilder pDeviceFriendlyName,
[In, Out] ref uint pcchDeviceFriendlyName);
[PreserveSig]
HRESULT GetDeviceManufacturer(
[In] string pszPnPDeviceID,
[In, Out] ref StringBuilder pDeviceManufacturer,
[In, Out] ref uint pcchDeviceManufacturer);
[PreserveSig]
HRESULT GetDeviceProperty(
[In] string pszPnPDeviceID,
[In] string pszDevicePropertyName,
[In, Out] IntPtr pData,
[In, Out] ref uint pcbData,
[In, Out] ref uint pdwType);
[PreserveSig]
HRESULT GetDevices(
[In, Out] IntPtr pPnPDeviceIDs,
[In, Out] ref uint pcPnPDeviceIDs);
[PreserveSig]
HRESULT GetPrivateDevices(
[In, Out] IntPtr pPnPDeviceIDs,
[In, Out] ref uint pcPnPDeviceIDs);
[PreserveSig]
HRESULT RefreshDeviceList();
}
/// <summary>
/// CLSID_PortableDeviceManager
/// </summary>
[ComImport, Guid("0af10cec-2ecd-4b92-9581-34f6ae0637f3")]
public class CLSID_PortableDeviceManager { }
This is my test code:
var devManager = Activator.CreateInstance(
typeof(CLSID_PortableDeviceManager)) as IPortableDeviceManager;
uint pcPnPDeviceIDs = 0;
var res1 = devManager.GetDevices(IntPtr.Zero, ref pcPnPDeviceIDs);
// check for errors in res1
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocCoTaskMem((int)(IntPtr.Size * pcPnPDeviceIDs));
var res2 = devManager.GetDevices(ptr, ref pcPnPDeviceIDs);
// check for errors in res2
IntPtr ptr2 = ptr;
for (uint i = 0; i < pcPnPDeviceIDs; i++)
{
string str = Marshal.PtrToStringUni(ptr2);
ptr2 += IntPtr.Size;
}
}
finally
{
if (ptr != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(ptr);
}
}
Marshal.ReleaseComObject(devManager);
I get the AccessViolationException on the second call to GetDevices()
Try this:
And I use
uint
(s) for HRESULT, because they must be parsed bit by bit (and it's easier with unsigned integers)And allocation of system memory should always be protected by
try/finally
. And remember to free all the strings :-) (as I have done)A variant using an
IntPtr[]
:The definition of GetDevices (note the use of
[MarshalAs(UnmanagedType.LPArray)]
:The remaining code:
In this way you don't have to allocate the main array in unmanaged memory.