はじめに
僕は以前 Delphi で Windows のシェル拡張(Shell Extension)を使ってエクスプローラに機能を追加するアプリを作りました。
[簡単エクスプローラ拡張 EzExpEx]
(http://hp.vector.co.jp/authors/VA029585/EzExpEx/)
これは残念なことに 64 ビット Windows のエクスプローラで動かないんですよね。僕が持っている Delphi では 64 ビット Windows アプリを作れないんです。
これを VB.NET で作り直したいとずっと思っていました。
そのために調べていると次の記事がありました。
[How to Write Windows Shell Extension with .NET Languages - CodeProject]
(http://www.codeproject.com/Articles/174369/How-to-Write-Windows-Shell-Extension-with-NET-Lang)
これを参考にしてプログラムを書いてみました。
Windows シェル拡張でコンテキストメニュー
エクスプローラで右クリックするとコンテキストメニューが表示されます。このメニューに独自の項目を追加します。
プロジェクトを作る
まず、「クラスライブラリ」のプロジェクトを新規作成します。
ここではプロジェクト名を「ContextMenuExtension」にします。
次に、プロジェクトの「参照」設定に以下のアセンブリを追加します。
- System.Windows.Forms
- System.Drawing
IDE が作成したソースファイル「Class1.cs」または「Class1.vb」を「ContextMenuExtension.cs」または「ContextMenuExtension.vb」にリネームします。* 好きなファイル名で構いません。
IShellExtInit と IContextMenu を用意する
using System.Runtime.InteropServices;
using System.Text;
namespace ContextMenuExtension
{
[ComImport(),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("000214e8-0000-0000-c000-000000000046")]
internal interface IShellExtInit
{
void Initialize(IntPtr /*LPCITEMIDLIST*/ pidlFolder, IntPtr /*LPDATAOBJECT*/ pDataObj, IntPtr /*HKEY*/ hKeyProgID);
}
[ComImport(),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("000214e4-0000-0000-c000-000000000046")]
internal interface IContextMenu
{
[PreserveSig]
int QueryContextMenu(IntPtr /*HMENU*/ hMenu, uint iMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
void InvokeCommand(IntPtr pici);
void GetCommandString(UIntPtr idCmd, uint uFlags, IntPtr pReserved, StringBuilder pszName, uint cchMax);
}
Imports System.Runtime.InteropServices
Imports System.Text
<ComImport(),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("000214e8-0000-0000-c000-000000000046")>
Friend Interface IShellExtInit
Sub Initialize(ByVal pidlFolder As IntPtr, ByVal pDataObj As IntPtr, ByVal hKeyProgID As IntPtr)
End Interface
<ComImport(),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("000214e4-0000-0000-c000-000000000046")>
Friend Interface IContextMenu
<PreserveSig()>
Function QueryContextMenu(ByVal hMenu As IntPtr, ByVal iMenu As UInt32, ByVal idCmdFirst As UInt32, ByVal idCmdLast As UInt32, ByVal uFlags As UInt32) As Integer
Sub InvokeCommand(ByVal pici As IntPtr)
Sub GetCommandString(ByVal idCmd As UIntPtr, ByVal uFlags As UInt32, ByVal pReserved As IntPtr, ByVal pszName As StringBuilder, ByVal cchMax As UInt32)
End Interface
IShellExtInit と IContextMenu から派生する
「ContextMenuExtension」クラスを「IShellExtInit」「IContextMenu」から派生するよう書換します。
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
IDE が「インターフェイスの実装」を助言してくるので従って以下のメソッドを実装します。
- Initialize
- QueryContextMenu
- GetComandString
- InvokeCommand
必要な定数やメソッドを用意する
必要な定数やメソッドを用意します。
using System.Runtime.InteropServices.ComTypes;
namespace CsContextMenuExtension
{
internal enum CLIPFORMAT : uint
{
CF_TEXT = 1,
CF_BITMAP = 2,
CF_METAFILEPICT = 3,
CF_SYLK = 4,
CF_DIF = 5,
CF_TIFF = 6,
CF_OEMTEXT = 7,
CF_DIB = 8,
CF_PALETTE = 9,
CF_PENDATA = 10,
CF_RIFF = 11,
CF_WAVE = 12,
CF_UNICODETEXT = 13,
CF_ENHMETAFILE = 14,
CF_HDROP = 15,
CF_LOCALE = 16,
CF_MAX = 17,
CF_OWNERDISPLAY = 0x0080,
CF_DSPTEXT = 0x0081,
CF_DSPBITMAP = 0x0082,
CF_DSPMETAFILEPICT = 0x0083,
CF_DSPENHMETAFILE = 0x008E,
CF_PRIVATEFIRST = 0x0200,
CF_PRIVATELAST = 0x02FF,
CF_GDIOBJFIRST = 0x0300,
CF_GDIOBJLAST = 0x03FF
}
internal static class WinError
{
public const int S_OK = 0x0000;
public const int S_FALSE = 0x0001;
public const int E_FAIL = -2147467259;
public const int E_INVALIDARG = -2147024809;
public const int E_OUTOFMEMORY = -2147024882;
public const int STRSAFE_E_INSUFFICIENT_BUFFER = -2147024774;
public const uint SEVERITY_SUCCESS = 0;
public const uint SEVERITY_ERROR = 1;
public static int MAKE_HRESULT(uint sev, uint fac, uint code)
{
return (int)((sev << 31) | (fac << 16) | code);
}
}
[Flags]
internal enum MIIM : uint
{
MIIM_STATE = 0x00000001,
MIIM_ID = 0x00000002,
MIIM_SUBMENU = 0x00000004,
MIIM_CHECKMARKS = 0x00000008,
MIIM_TYPE = 0x00000010,
MIIM_DATA = 0x00000020,
MIIM_STRING = 0x00000040,
MIIM_BITMAP = 0x00000080,
MIIM_FTYPE = 0x00000100
}
internal enum MFT : uint
{
MFT_STRING = 0x00000000,
MFT_BITMAP = 0x00000004,
MFT_MENUBARBREAK = 0x00000020,
MFT_MENUBREAK = 0x00000040,
MFT_OWNERDRAW = 0x00000100,
MFT_RADIOCHECK = 0x00000200,
MFT_SEPARATOR = 0x00000800,
MFT_RIGHTORDER = 0x00002000,
MFT_RIGHTJUSTIFY = 0x00004000
}
internal enum MFS : uint
{
MFS_ENABLED = 0x00000000,
MFS_UNCHECKED = 0x00000000,
MFS_UNHILITE = 0x00000000,
MFS_GRAYED = 0x00000003,
MFS_DISABLED = 0x00000003,
MFS_CHECKED = 0x00000008,
MFS_HILITE = 0x00000080,
MFS_DEFAULT = 0x00001000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct MENUITEMINFO
{
public uint cbSize;
public MIIM fMask;
public MFT fType;
public MFS fState;
public uint wID;
public IntPtr hSubMenu;
public IntPtr hbmpChecked;
public IntPtr hbmpUnchecked;
public UIntPtr dwItemData;
public string dwTypeData;
public uint cch;
public IntPtr hbmpItem;
}
[Flags]
internal enum CMF : uint
{
CMF_NORMAL = 0x00000000,
CMF_DEFAULTONLY = 0x00000001,
CMF_VERBSONLY = 0x00000002,
CMF_EXPLORE = 0x00000004,
CMF_NOVERBS = 0x00000008,
CMF_CANRENAME = 0x00000010,
CMF_NODEFAULT = 0x00000020,
CMF_INCLUDESTATIC = 0x00000040,
CMF_ITEMMENU = 0x00000080,
CMF_EXTENDEDVERBS = 0x00000100,
CMF_DISABLEDVERBS = 0x00000200,
CMF_ASYNCVERBSTATE = 0x00000400,
CMF_OPTIMIZEFORINVOKE = 0x00000800,
CMF_SYNCCASCADEMENU = 0x00001000,
CMF_DONOTPICKDEFAULT = 0x00002000,
CMF_RESERVED = 0xFFFF0000
}
[Flags]
internal enum CMIC : uint
{
CMIC_MASK_ICON = 0x00000010,
CMIC_MASK_HOTKEY = 0x00000020,
CMIC_MASK_NOASYNC = 0x00000100,
CMIC_MASK_FLAG_NO_UI = 0x00000400,
CMIC_MASK_UNICODE = 0x00004000,
CMIC_MASK_NO_CONSOLE = 0x00008000,
CMIC_MASK_ASYNCOK = 0x00100000,
CMIC_MASK_NOZONECHECKS = 0x00800000,
CMIC_MASK_FLAG_LOG_USAGE = 0x04000000,
CMIC_MASK_SHIFT_DOWN = 0x10000000,
CMIC_MASK_PTINVOKE = 0x20000000,
CMIC_MASK_CONTROL_DOWN = 0x40000000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CMINVOKECOMMANDINFO
{
public uint cbSize;
public CMIC fMask;
public IntPtr hwnd;
public IntPtr lpVerb;
[MarshalAs(UnmanagedType.LPStr)]
public string parameters;
[MarshalAs(UnmanagedType.LPStr)]
public string directory;
public int nShow;
public uint dwHotKey;
public IntPtr hIcon;
}
internal enum GCS : uint
{
GCS_VERBA = 0x00000000,
GCS_HELPTEXTA = 0x00000001,
GCS_VALIDATEA = 0x00000002,
GCS_VERBW = 0x00000004,
GCS_HELPTEXTW = 0x00000005,
GCS_VALIDATEW = 0x00000006,
GCS_VERBICONW = 0x00000014,
GCS_UNICODE = 0x00000004
}
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);
[DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool InsertMenuItem(IntPtr hMenu, uint uItem, [MarshalAs(UnmanagedType.Bool)]bool fByPosition, ref MENUITEMINFO mii);
public static int HighWord(int number)
{
return ((number & 0x80000000) == 0x80000000) ? (number >> 16) : ((number >> 16) & 0xffff);
}
public static int LowWord(int number)
{
return number & 0xffff;
}
}
Imports System.Runtime.InteropServices.ComTypes
Friend Enum CLIPFORMAT As UInt32
CF_TEXT = 1
CF_BITMAP = 2
CF_METAFILEPICT = 3
CF_SYLK = 4
CF_DIF = 5
CF_TIFF = 6
CF_OEMTEXT = 7
CF_DIB = 8
CF_PALETTE = 9
CF_PENDATA = 10
CF_RIFF = 11
CF_WAVE = 12
CF_UNICODETEXT = 13
CF_ENHMETAFILE = 14
CF_HDROP = 15
CF_LOCALE = &H10
CF_MAX = &H11
CF_OWNERDISPLAY = &H80
CF_DSPTEXT = &H81
CF_DSPBITMAP = &H82
CF_DSPMETAFILEPICT = &H83
CF_DSPENHMETAFILE = &H8E
CF_PRIVATEFIRST = &H200
CF_PRIVATELAST = &H2FF
CF_GDIOBJFIRST = &H300
CF_GDIOBJLAST = &H3FF
End Enum
Friend Class WinError
Public Const S_OK As Integer = 0
Public Const S_FALSE As Integer = 1
Public Const E_FAIL As Integer = -2147467259
Public Const E_INVALIDARG As Integer = -2147024809
Public Const E_OUTOFMEMORY As Integer = -2147024882
Public Const STRSAFE_E_INSUFFICIENT_BUFFER As Integer = -2147024774
Public Const SEVERITY_ERROR As UInt32 = 1
Public Const SEVERITY_SUCCESS As UInt32 = 0
Public Shared Function MAKE_HRESULT(ByVal sev As UInt32, ByVal fac As UInt32, ByVal code As UInt32) As Integer
Return CInt((((sev << &H1F) Or (fac << &H10)) Or code))
End Function
End Class
<Flags()>
Friend Enum MIIM As UInt32
MIIM_STATE = 1
MIIM_ID = 2
MIIM_SUBMENU = 4
MIIM_CHECKMARKS = 8
MIIM_TYPE = &H10
MIIM_DATA = &H20
MIIM_STRING = &H40
MIIM_BITMAP = &H80
MIIM_FTYPE = &H100
End Enum
Friend Enum MFT As UInt32
MFT_STRING = 0
MFT_BITMAP = 4
MFT_MENUBARBREAK = &H20
MFT_MENUBREAK = &H40
MFT_OWNERDRAW = &H100
MFT_RADIOCHECK = &H200
MFT_SEPARATOR = &H800
MFT_RIGHTORDER = &H2000
MFT_RIGHTJUSTIFY = &H4000
End Enum
Friend Enum MFS As UInt32
MFS_ENABLED = 0
MFS_UNCHECKED = 0
MFS_UNHILITE = 0
MFS_DISABLED = 3
MFS_GRAYED = 3
MFS_CHECKED = 8
MFS_HILITE = &H80
MFS_DEFAULT = &H1000
End Enum
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Friend Structure MENUITEMINFO
Public cbSize As UInt32
Public fMask As MIIM
Public fType As MFT
Public fState As MFS
Public wID As UInt32
Public hSubMenu As IntPtr
Public hbmpChecked As IntPtr
Public hbmpUnchecked As IntPtr
Public dwItemData As UIntPtr
Public dwTypeData As String
Public cch As UInt32
Public hbmpItem As IntPtr
End Structure
<Flags()>
Friend Enum CMF
CMF_NORMAL = 0
CMF_DEFAULTONLY = 1
CMF_VERBSONLY = 2
CMF_EXPLORE = 4
CMF_NOVERBS = 8
CMF_CANRENAME = &H10
CMF_NODEFAULT = &H20
CMF_INCLUDESTATIC = &H40
CMF_ITEMMENU = &H80
CMF_EXTENDEDVERBS = &H100
CMF_DISABLEDVERBS = &H200
CMF_ASYNCVERBSTATE = &H400
CMF_OPTIMIZEFORINVOKE = &H800
CMF_SYNCCASCADEMENU = &H1000
CMF_DONOTPICKDEFAULT = &H2000
CMF_RESERVED = &HFFFF0000
End Enum
<Flags()>
Friend Enum CMIC As UInt32
CMIC_MASK_ICON = &H10
CMIC_MASK_HOTKEY = &H20
CMIC_MASK_NOASYNC = &H100
CMIC_MASK_FLAG_NO_UI = &H400
CMIC_MASK_UNICODE = &H4000
CMIC_MASK_NO_CONSOLE = &H8000
CMIC_MASK_ASYNCOK = &H100000
CMIC_MASK_NOZONECHECKS = &H800000
CMIC_MASK_FLAG_LOG_USAGE = &H4000000
CMIC_MASK_SHIFT_DOWN = &H10000000
CMIC_MASK_PTINVOKE = &H20000000
CMIC_MASK_CONTROL_DOWN = &H40000000
End Enum
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Public Structure POINT
Public X As Integer
Public Y As Integer
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Friend Structure CMINVOKECOMMANDINFO
Public cbSize As UInt32
Public fMask As CMIC
Public hwnd As IntPtr
Public lpVerb As IntPtr
<MarshalAs(UnmanagedType.LPStr)>
Public parameters As String
<MarshalAs(UnmanagedType.LPStr)>
Public directory As String
Public nShow As Integer
Public dwHotKey As UInt32
Public hIcon As IntPtr
End Structure
Friend Enum GCS As UInt32
GCS_VERBA = 0
GCS_HELPTEXTA = 1
GCS_VALIDATEA = 2
GCS_HELPTEXTW = 5
GCS_UNICODE = 4
GCS_VERBW = 4
GCS_VALIDATEW = 6
GCS_VERBICONW = 20
End Enum
Friend Class WinApi
Public Const MAX_PATH As Integer = 260
<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
<DllImport("user32", CharSet:=CharSet.Unicode, SetLastError:=True)>
Public Shared Function InsertMenuItem(ByVal hMenu As IntPtr, ByVal uItem As UInt32, <MarshalAs(UnmanagedType.Bool)> ByVal fByPosition As Boolean, ByRef mii As MENUITEMINFO) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Public Shared Function HighWord(ByVal number As Integer) As Integer
Return If(((number And &H80000000) = &H80000000), (number >> &H10), ((number >> &H10) And &HFFFF))
End Function
Public Shared Function LowWord(ByVal number As Integer) As Integer
Return (number And &HFFFF)
End Function
End Class
Initialize メソッドを実装する
選択されたファイル/フォルダを取得して変数にセットしておきます。
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
private string selectedFile;
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);
}
}
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
Private SelectedFile As String
Public Sub Initialize(ByVal pidlFolder As IntPtr, ByVal pDataObj As IntPtr, ByVal 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
QueryContextMenu メソッドを実装する
コンテキストメニューに項目を追加します。
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
private uint IDM_SHOW_FILENAME = 0;
private string TXT_SHOW_FILENAME = "ファイル名を表示(&D)";
public int QueryContextMenu(IntPtr hMenu, uint iMenu, uint idCmdFirst, uint idCmdLast, uint uFlags)
{
// メニュー項目を追加
if (((uint)CMF.CMF_DEFAULTONLY & uFlags) != 0)
{
return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0);
}
MENUITEMINFO mii = new MENUITEMINFO();
mii.cbSize = (uint)Marshal.SizeOf(mii);
mii.fMask = MIIM.MIIM_ID | MIIM.MIIM_TYPE | MIIM.MIIM_STATE;
mii.wID = idCmdFirst + IDM_SHOW_FILENAME;
mii.fType = MFT.MFT_STRING;
mii.dwTypeData = TXT_SHOW_FILENAME;
mii.fState = MFS.MFS_ENABLED;
if (!WinApi.InsertMenuItem(hMenu, iMenu, true, ref mii))
{
return Marshal.GetHRForLastWin32Error();
}
// 追加したメニュー項目の数を返す
return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 1);
}
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
Private IDM_SHOW_FILENAME As UInteger = 0
Private TXT_SHOW_FILENAME As String = "ファイル名を表示(&D)"
Public Function QueryContextMenu(ByVal hMenu As IntPtr, ByVal iMenu As UInt32, ByVal idCmdFirst As UInt32, ByVal idCmdLast As UInt32, ByVal uFlags As UInt32) As Integer Implements IContextMenu.QueryContextMenu
'メニュー項目を追加
If ((CMF.CMF_DEFAULTONLY And uFlags) <> 0) Then
Return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0)
End If
Dim mii As New MENUITEMINFO
With mii
.cbSize = Marshal.SizeOf(mii)
.fMask = MIIM.MIIM_TYPE Or MIIM.MIIM_STATE Or MIIM.MIIM_ID
.wID = idCmdFirst + Me.IDM_SHOW_FILENAME
.fType = MFT.MFT_STRING
.dwTypeData = Me.TXT_SHOW_FILENAME
.fState = MFS.MFS_ENABLED
End With
If Not WinApi.InsertMenuItem(hMenu, iMenu, True, mii) Then
Return Marshal.GetHRForLastWin32Error
End If
'追加したメニュー項目の数を返す
Return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 1))
End Function
GetCommandString メソッドを実装する
エクスプローラのステータスバーに表示されるメッセージを設定します。
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
private string MSG_SHOW_FILENAME = "ファイル名を表示します。";
public void GetCommandString(UIntPtr idCmd, uint uFlags, IntPtr pReserved, StringBuilder pszName, uint cchMax)
{
if (idCmd.ToUInt32() != IDM_SHOW_FILENAME)
{
return;
}
if ((GCS)uFlags == GCS.GCS_HELPTEXTW)
{
if (MSG_SHOW_FILENAME.Length > cchMax - 1)
{
Marshal.ThrowExceptionForHR(WinError.STRSAFE_E_INSUFFICIENT_BUFFER);
}
else
{
pszName.Clear();
pszName.Append(MSG_SHOW_FILENAME);
}
}
}
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
Private MSG_SHOW_FILENAME As String = "ファイル名を表示します。"
Public Sub GetCommandString(ByVal idCmd As UIntPtr, ByVal uFlags As UInt32, ByVal pReserved As IntPtr, ByVal pszName As StringBuilder, ByVal cchMax As UInt32) Implements IContextMenu.GetCommandString
If Not (idCmd.ToUInt32 = IDM_SHOW_FILENAME) Then
Exit Sub
End If
If DirectCast(uFlags, GCS) = GCS.GCS_HELPTEXTW Then
If (MSG_SHOW_FILENAME.Length > (cchMax - 1)) Then
Marshal.ThrowExceptionForHR(WinError.STRSAFE_E_INSUFFICIENT_BUFFER)
Else
pszName.Clear()
pszName.Append(MSG_SHOW_FILENAME)
End If
End If
End Sub
InvokeCommand メソッドを実装する
メニュー項目が選択されると呼出されます。
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
public void InvokeCommand(IntPtr pici)
{
CMINVOKECOMMANDINFO ici = (CMINVOKECOMMANDINFO)Marshal.PtrToStructure(pici, typeof(CMINVOKECOMMANDINFO));
// ici.lpVerb の上位ワードが NULL でなければ
// このメソッドは別のアプリケーションによって呼出されたものなので
// そのまま返す
if (WinApi.HighWord(ici.lpVerb.ToInt32()) != 0)
{
return;
}
// そうでなければ
// シェルが呼出したもので
// ici.lpVerb の下位ワードが、ユーザが選択したメニュー項目
if (WinApi.LowWord(ici.lpVerb.ToInt32()) == IDM_SHOW_FILENAME)
{
this.DoShowFileName();
}
else
{
Marshal.ThrowExceptionForHR(WinError.E_FAIL);
}
}
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
Public Sub InvokeCommand(ByVal pici As IntPtr) Implements IContextMenu.InvokeCommand
Dim ici As CMINVOKECOMMANDINFO = Marshal.PtrToStructure(pici, GetType(CMINVOKECOMMANDINFO))
'ici.lpVerb の上位ワードが NULL でなければ
'このメソッドは別のアプリケーションによって呼出されたものなので
'そのまま返す
If (WinApi.HighWord(ici.lpVerb.ToInt32) <> 0) Then
Exit Sub
End If
'そうでなければ
'シェルが呼出したもので
'ici.lpVerb の下位ワードが、ユーザが選択したメニュー項目
If (WinApi.LowWord(ici.lpVerb.ToInt32) = IDM_SHOW_FILENAME) Then
Me.DoShowFileName()
Else
Marshal.ThrowExceptionForHR(WinError.E_FAIL)
End If
End Sub
主な機能は「DoShowFileName」。選択したファイルの名称を表示します。
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
private void DoShowFileName()
{
System.Windows.Forms.MessageBox.Show("選択されたファイル: " + Environment.NewLine + this.selectedFile);
}
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
Private Sub DoShowFileName(hwnd As IntPtr)
System.Windows.Forms.MessageBox.Show("選択されたファイル: " & Environment.NewLine & Me.SelectedFile, My.Application.Info.Title)
End Sub
COM サーバ登録機能を実装する
作成しているクラスライブラリが COM サーバとして登録できるようにします。
まず、アセンブリを COM 参照可能にします。以下の手順で作業します。
プロジェクトの「プロパティ」を開く。
「アプリケーション」タブを選択。
「アセンブリ情報」を押下。
ダイアログボックスで「アセンブリを COM 参照可能にする」にチェック。
次に、アセンブリに署名します。以下の手順で作業します。
プロジェクトの「プロパティ」を開く。
「署名」タブを選択。
「アセンブリに署名する」をチェック。
「厳密な名前のキーファイルを選択」で「新規作成」を選択。
ダイアログボックスで「キーファイル名」に任意のファイル名を指定。
「キーファイルをパスワードで保護する」は必須ではない。
クラスに属性を設定します。
GUID はクラスごとに新規作成して下さい。
[ClassInterface(ClassInterfaceType.None),
Guid("99999999-9999-9999-9999-9999999999"),
ComVisible(true)]
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
<ClassInterface(ClassInterfaceType.None),
Guid("99999999-9999-9999-9999-9999999999"),
ComVisible(True)>
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
登録および解除のためのメソッドを実装します。
ここで対象となるファイルの拡張子を指定します。
using Microsoft.Win32
public class ContextMenuExtension : IShellExtInit, IContextMenu
{
private const string Description = "ContextMenuExtention Class";
private const string TargetFileType = "*";
[ComRegisterFunction()]
public static void Register(Type t)
{
try
{
Guid clsid = t.GUID;
if (clsid == Guid.Empty)
{
throw new ArgumentException("clsid must not be empty");
}
string keyName = string.Format("{0}\\shellex\\ContextMenuHandlers\\{1}", TargetFileType, clsid.ToString("B"));
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(keyName))
{
if (key != null && !string.IsNullOrEmpty(Description))
{
key.SetValue(null, Description);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
[ComUnregisterFunction()]
public static void Unregister(Type t)
{
try
{
Guid clsid = t.GUID;
if (clsid == Guid.Empty)
{
throw new ArgumentException("clsid must not be empty");
}
string keyName = string.Format("{0}\\shellex\\ContextMenuHandlers\\{1}", TargetFileType, clsid.ToString("B"));
Registry.ClassesRoot.DeleteSubKeyTree(keyName, false);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
Imports Microsoft.Win32
Public Class ContextMenuExtension
Implements IShellExtInit, IContextMenu
Private Const Description As String = "ContextMenuExtention Class"
Private Const TargetFileType As String = "*"
<ComRegisterFunction()>
Public Shared Sub Register(ByVal t As Type)
Try
Dim clsid = t.GUID
If clsid = Guid.Empty Then
Throw New ArgumentException("clsid must not be empty")
End If
Dim keyName As String = String.Format("{0}\shellex\ContextMenuHandlers\{1}", TargetFileType, clsid.ToString("B"))
Using key As RegistryKey = Registry.ClassesRoot.CreateSubKey(keyName)
If ((key IsNot Nothing) AndAlso (Not String.IsNullOrEmpty(Description))) Then
key.SetValue(Nothing, Description)
End If
End Using
Catch ex As Exception
Console.WriteLine(ex.Message)
Throw
End Try
End Sub
<ComUnregisterFunction()>
Public Shared Sub Unregister(ByVal t As Type)
Try
Dim clsid = t.GUID
If clsid = Guid.Empty Then
Throw New ArgumentException("clsid must not be empty")
End If
Dim keyName As String = String.Format("{0}\shellex\ContextMenuHandlers\{1}", TargetFileType, clsid.ToString("B"))
Registry.ClassesRoot.DeleteSubKeyTree(keyName, False)
Catch ex As Exception
Console.WriteLine(ex.Message)
Throw
End Try
End Sub
ビルドする
ビルドするとアセンブリファイルが作成されます。これを COM サーバとして Windows に登録します。
COM サーバとして登録する
C# または VB.NET で作成したアセンブリファイルを COM サーバとして登録するには、.NET Framework に同梱されている「regasm」ツールを使います。
regasm ContextMenuExtension.dll /codebase
おわりに
以下の記事も書いています。
[.NET で Windows エクスプローラのコンテキストメニューを拡張 - Qiita] (http://qiita.com/tinymouse/items/eb8aebb39ddd5c103347)
上記のコードの大半をライブラリにした SharpShell を使用しています。