简体   繁体   中英

Convert LPWSTR * array of string pointers to C#

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 to NULL and pcPnPDeviceIDs set to zero, and then allocate a buffer according to the value retrieved by pcPnPDeviceIDs .

pcPnPDeviceIDs [in, out] On input, the number of values that pPnPDeviceIDs can hold. On output, a pointer to the number of devices actually written to pPnPDeviceIDs .

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:

uint pcPnPDeviceIDs = 0;
int res1 = GetDevices(IntPtr.Zero, ref pcPnPDeviceIDs);
// check for errors in res1

IntPtr ptr = IntPtr.Zero;

try
{
    ptr = Marshal.AllocCoTaskMem((int)(IntPtr.Size * pcPnPDeviceIDs));
    int res2 = GetDevices(ptr, ref pcPnPDeviceIDs);
    // check for errors in res2

    IntPtr ptr2 = ptr;

    for (uint i = 0; i < pcPnPDeviceIDs; i++)
    {
        string str = Marshal.PtrToStringUni(Marshal.ReadIntPtr(ptr2));
        ptr2 += IntPtr.Size;
    }
}
finally
{
    if (ptr != IntPtr.Zero)
    {
        IntPtr ptr2 = ptr;

        for (uint i = 0; i < pcPnPDeviceIDs; i++)
        {
            Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(ptr2));
            ptr2 += IntPtr.Size;
        }

        Marshal.FreeCoTaskMem(ptr);
    }
}

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)] :

[PreserveSig]
HRESULT GetDevices(
    [In][MarshalAs(UnmanagedType.LPArray)] IntPtr[] pPnPDeviceIDs,
    [In, Out] ref uint pcPnPDeviceIDs);

The remaining code:

var devManager = Activator.CreateInstance(typeof(CLSID_PortableDeviceManager)) as IPortableDeviceManager;
uint pcPnPDeviceIDs = 0;
HRESULT res1 = devManager.GetDevices(null, ref pcPnPDeviceIDs);
// check for errors in res1

IntPtr[] ptr = null;

try
{
    ptr = new IntPtr[pcPnPDeviceIDs];

    HRESULT res2 = devManager.GetDevices(ptr, ref pcPnPDeviceIDs);
    // check for errors in res2

    for (uint i = 0; i < pcPnPDeviceIDs; i++)
    {
        string str = Marshal.PtrToStringUni(ptr[i]);
    }
}
finally
{
    if (ptr != null)
    {
        for (uint i = 0; i < pcPnPDeviceIDs; i++)
        {
            Marshal.FreeCoTaskMem(ptr[i]);
        }
    }
}

In this way you don't have to allocate the main array in unmanaged memory.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM