【问题标题】:Convert LPWSTR * array of string pointers to C#将 LPWSTR * 字符串指针数组转换为 C#
【发布时间】:2013-08-04 10:08:48
【问题描述】:

我正在尝试将以下方法从 C++ 转换为 C#

HRESULT GetDevices(
  [in, out]  LPWSTR *pPnPDeviceIDs,
  [in, out]  DWORD *pcPnPDeviceIDs
);

在 MSDN 文档中它说:

pPnPDeviceIDs [输入、输出]
调用者分配的字符串指针数组,其中包含所有连接设备的即插即用名称。要了解此参数所需的大小,首先调用此方法,并将此参数设置为NULL,并将pcPnPDeviceIDs设置为零,然后根据pcPnPDeviceIDs检索到的值分配缓冲区。

pcPnPDeviceIDs [输入,输出] 在输入时,pPnPDeviceIDs 可以保存的值的数量。输出时,指向实际写入pPnPDeviceIDs 的设备数量的指针。

到目前为止我有这个定义:

[PreserveSig]        
int GetDevices(    
    [In, Out] IntPtr pPnPDeviceIDs,
    [In, Out] ref uint pcPnPDeviceIDs
);

我尝试使用Marshal.AllocCoTaskMempPnPDeviceIDs 分配一些内存,但是当我尝试调用该方法时出现 AccessViolationException。

LPWSTR * 是一个字符串指针数组时,谁能告诉我转换的正确方法?

编辑: 这是我的定义:

[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 { }

这是我的测试代码:

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

第二次调用 GetDevices() 时出现 AccessViolationException

【问题讨论】:

  • 您的问题出在界面上...如果您尝试对 devManager.RefreshDeviceList();例如一切崩溃
  • 发现问题...你的界面有错误的方法顺序...你是怎么生成的?将 GetDevices 作为第一种方法。
  • 接口方法的正确顺序是:GetDevices、RefreshDeviceList、GetDeviceFriendlyName、GetDeviceDescription、GetDeviceManufacturer、GetDeviceProperty、GetPrivateDevices

标签: c# interop pinvoke arrays


【解决方案1】:

试试这个:

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

我将uint(s) 用于 HRESULT,因为它们必须逐位解析(使用无符号整数更容易)
系统内存的分配应始终受到try/finally 的保护。并记住释放所有字符串:-)(就像我所做的那样)

使用IntPtr[] 的变体:

GetDevices的定义(注意[MarshalAs(UnmanagedType.LPArray)]的使用:

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

剩余代码:

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]);
        }
    }
}

这样您就不必在非托管内存中分配主数组。

【讨论】:

  • 我仍然收到 AccessViolationException。我将编辑我的帖子以显示我的所有代码,以便您重现问题。谢谢
  • @user2648561 在哪里?第一次通话还是第二次通话?还是在各种PtrToStringUni?
  • @user2648561 如果您通过将 GetDevices 放在首位来修复接口,然后修改 Marshal.PtrToStringUni 它可以工作(我添加了 Marshal.ReadIntPtr)。
  • @user2648561 甚至修复了字符串的释放问题。
  • 我没有意识到您必须按照与原始头文件相同的顺序定义方法!你每天学习新的东西!谢谢。
猜你喜欢
  • 1970-01-01
  • 2012-09-09
  • 1970-01-01
  • 1970-01-01
  • 2013-01-11
  • 2010-11-14
  • 2014-07-31
  • 1970-01-01
  • 2013-03-06
相关资源
最近更新 更多