IDataObject ポインタから DragQueryFiles する代わりに DataObject.GetFileDropList を使う

Windows のシェル拡張（Shell Extension）のプログラムを書いています。
例えば、[.NET で Windows エクスプローラのコンテキストメニューを拡張]
(http://qiita.com/tinymouse/items/26c1bfa0d2a6a2116cff) するプログラム。
選択されているファイルの一覧を取得するのに、こんなコードを書いています。（一部省略して引用しています。）

```csharp:ContextMenuExtension.cs
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
    public void Initialize(IntPtr pidlFolder, IntPtr pDataObj, IntPtr hKeyProgID)
    {
        // 選択されているファイル/ディレクトリを取得
        if (pDataObj == IntPtr.Zero)
        {
            throw new ArgumentException();
        }
        FORMATETC fe = new FORMATETC();
        fe.cfFormat = (short)CLIPFORMAT.CF_HDROP;
        fe.ptd = IntPtr.Zero;
        fe.dwAspect = DVASPECT.DVASPECT_CONTENT;
        fe.lindex = -1;
        fe.tymed = TYMED.TYMED_HGLOBAL;
        STGMEDIUM stm = new STGMEDIUM();
        IDataObject dataObject = (IDataObject)Marshal.GetObjectForIUnknown(pDataObj);
        dataObject.GetData(ref fe, out stm);
        try
        {
            IntPtr hDrop = stm.unionmember;
            if (hDrop == IntPtr.Zero)
            {
                throw new ArgumentException();
            }
            uint nFiles = WinApi.DragQueryFile(hDrop, UInt32.MaxValue, null, 0);
            if (nFiles == 1)
            {
                StringBuilder fileName = new StringBuilder(WinApi.MAX_PATH);
                if (0 == WinApi.DragQueryFile(hDrop, 0, fileName, fileName.Capacity))
                {
                    Marshal.ThrowExceptionForHR(WinError.E_FAIL);
                }
                this.selectedFile = fileName.ToString();
            }
            else
            {
                Marshal.ThrowExceptionForHR(WinError.E_FAIL);
            }
        }
        finally
        {
            WinApi.ReleaseStgMedium(ref stm);
        }
    }

internal class WinApi
{
    public const int MAX_PATH = 260;

    [DllImport("shell32", CharSet = CharSet.Unicode)]
    public static extern uint DragQueryFile(IntPtr hDrop, uint iFile, StringBuilder pszFile, int cch);

    [DllImport("ole32.dll", CharSet = CharSet.Unicode)]
    public static extern void ReleaseStgMedium(ref STGMEDIUM pmedium);
```

```vbnet:ContextMenuExtension.vb
Public Class ContextMenuExtension
    Implements IShellExtInit, IContextMenu

    Public Sub Initialize(pidlFolder As IntPtr, pDataObj As IntPtr, hKeyProgID As IntPtr) Implements IShellExtInit.Initialize

        '選択されているファイル/ディレクトリを取得
        If (pDataObj = IntPtr.Zero) Then
            Throw New ArgumentException
        End If
        Dim fe As New FORMATETC
        With fe
            .cfFormat = CLIPFORMAT.CF_HDROP
            .ptd = IntPtr.Zero
            .dwAspect = DVASPECT.DVASPECT_CONTENT
            .lindex = -1
            .tymed = TYMED.TYMED_HGLOBAL
        End With
        Dim stm As New STGMEDIUM
        Dim dataObject As IDataObject = Marshal.GetObjectForIUnknown(pDataObj)
        dataObject.GetData(fe, stm)
        Try
            Dim hDrop As IntPtr = stm.unionmember
            If (hDrop = IntPtr.Zero) Then
                Throw New ArgumentException
            End If
            Dim nFiles As UInteger = WinApi.DragQueryFile(hDrop, UInt32.MaxValue, Nothing, 0)
            If (nFiles = 1) Then
                Dim fileName As New StringBuilder(WinApi.MAX_PATH)
                If (0 = WinApi.DragQueryFile(hDrop, 0, fileName, fileName.Capacity)) Then
                    Marshal.ThrowExceptionForHR(WinError.E_FAIL)
                End If
                Me.SelectedFile = fileName.ToString
            Else
                Marshal.ThrowExceptionForHR(WinError.E_FAIL)
            End If
        Finally
            WinApi.ReleaseStgMedium((stm))
        End Try
    End Sub

Friend Class WinApi
    <DllImport("shell32", CharSet:=CharSet.Unicode)>
    Public Shared Function DragQueryFile(ByVal hDrop As IntPtr, ByVal iFile As UInt32, ByVal pszFile As StringBuilder, ByVal cch As Integer) As UInt32
    End Function

    <DllImport("ole32.dll", CharSet:=CharSet.Unicode)>
    Public Shared Sub ReleaseStgMedium(ByRef pmedium As STGMEDIUM)
    End Sub
```

長いですね。
ここで使われている ```IDataObject``` は ```System.Runtime.InteropServices.ComTypes``` で定義されているものです。こちらは元々の COM のインターフェイス。[IDataObject インターフェイス (System.Runtime.InteropServices.ComTypes)]
(https://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.comtypes.idataobject(v=vs.110).aspx)
```IDataObject``` はもう一つ ```System.Windows.Forms``` で定義されています。こちらは .NET Framework で用意されたインターフェイス。[IDataObject インターフェイス (System.Windows.Forms)]
(https://msdn.microsoft.com/ja-jp/library/system.windows.forms.idataobject(v=vs.110).aspx)
同じ ```IDataObject``` だけれど別のものです。紛らわしいですね。

調べていくと、こんなクラスがありました。
[DataObject クラス (System.Windows.Forms)]
(https://msdn.microsoft.com/ja-jp/library/system.windows.forms.dataobject(v=vs.110).aspx)

```csharp:C#
[ClassInterfaceAttribute(ClassInterfaceType.None)]
public class DataObject : IDataObject, IDataObject
```

```vbnet:VB<ClassInterfaceAttribute(ClassInterfaceType.None)>
Public Class DataObject
	Implements IDataObject, IDataObject
```

便利そうなメソッドが用意されています。
これを使って、前述のコードを書換します。

```csharp:ContextMenuExtension.cs
    public void Initialize(IntPtr pidlFolder, IntPtr pDataObj, IntPtr hKeyProgID)
    {
        // 選択されているファイル/ディレクトリを取得
        if (pDataObj == IntPtr.Zero)
        {
            throw new ArgumentException();
        }
        DataObject dataObject = new DataObject(Marshal.GetObjectForIUnknown(pDataObj));
        if (dataObject.ContainsFileDropList())
        {
            StringCollection files = dataObject.GetFileDropList();
        }
```

```vbnet:ContextMenuExtension.vb
    Public Sub Initialize(pidlFolder As IntPtr, pDataObj As IntPtr, hKeyProgID As IntPtr) Implements IShellExtInit.Initialize

        '選択されているファイル/ディレクトリを取得
        If (pDataObj = IntPtr.Zero) Then
            Throw New ArgumentException
        End If
        Dim dataObject As New DataObject(Marshal.GetObjectForIUnknown(pDataObj))
        If dataObject.ContainsFileDropList Then
            Dim files As StringCollection = dataObject.GetFileDropList
        End If
```

短くなりました。いいですね。