2

次のメソッドを C++ から C# に変換しようとしています

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

MSDN ドキュメントには次のように書かれています。

pPnPDeviceIDs [入力、出力]
接続されているすべてのデバイスのプラグ アンド プレイ名を保持する、呼び出し元によって割り当てられた文字列ポインターの配列。このパラメータに必要なサイズを知るには、まずこのパラメータを 0 に設定してこのメ​​ソッドを呼び出しNULLpcPnPDeviceIDs次に によって取得された値に従ってバッファを割り当てますpcPnPDeviceIDs

pcPnPDeviceIDs [in, out] 入力時、pPnPDeviceIDs保持できる値の数。出力では、実際に書き込まれたデバイスの数へのポインターpPnPDeviceIDs

これまでのところ、次の定義があります。

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

Marshal.AllocCoTaskMemfor でメモリをpPnPDeviceIDs割り当てようとしましたが、メソッドを呼び出そうとすると 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() への 2 回目の呼び出しで AccessViolationException を取得します。

4

1 に答える 1

0

これを試して:

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、HRESULT には (s) を使用します。これは、ビットごとに解析する必要があるためです (符号なし整数の方が簡単です) 。
また、システム メモリの割り当ては、常に によって保護する必要があります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]);
        }
    }
}

この方法では、メイン配列をアンマネージ メモリに割り当てる必要はありません。

于 2013-08-04T10:21:35.550 に答える