Windows のシェル拡張(Shell Extension)のプログラムを書いています。
例えば、[.NET で Windows エクスプローラのコンテキストメニューを拡張]
(http://qiita.com/tinymouse/items/26c1bfa0d2a6a2116cff) するプログラム。
選択されているファイルの一覧を取得するのに、こんなコードを書いています。(一部省略して引用しています。)
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);
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)
[ClassInterfaceAttribute(ClassInterfaceType.None)]
public class DataObject : IDataObject, IDataObject
Public Class DataObject
Implements IDataObject, IDataObject
便利そうなメソッドが用意されています。
これを使って、前述のコードを書換します。
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();
}
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
短くなりました。いいですね。