LoginSignup
0

More than 5 years have passed since last update.

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

Posted at

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

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

長いですね。
ここで使われている IDataObjectSystem.Runtime.InteropServices.ComTypes で定義されているものです。こちらは元々の COM のインターフェイス。IDataObject インターフェイス (System.Runtime.InteropServices.ComTypes)
IDataObject はもう一つ System.Windows.Forms で定義されています。こちらは .NET Framework で用意されたインターフェイス。IDataObject インターフェイス (System.Windows.Forms)
同じ IDataObject だけれど別のものです。紛らわしいですね。

調べていくと、こんなクラスがありました。
DataObject クラス (System.Windows.Forms)

C#
[ClassInterfaceAttribute(ClassInterfaceType.None)]
public class DataObject : IDataObject, IDataObject
VB
Public Class DataObject
    Implements IDataObject, IDataObject

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

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0