Windowsエクスプローラーのシェル拡張

開発した製品の利便性を向上させるために、オペレーティングシステムに最大限の機能統合を提供し、ユーザーがアプリケーションの可能性を最大限に活用できるようにします。 この記事では、Windowsオペレーティングシステムのシェルに統合できるコンポーネントであるシェル拡張の開発の理論的および実用的な側面について説明します。 例として、ファイルのコンテキストメニューリストの拡張と、この領域の既存のソリューションの概要を検討します。





一般的に、Windowsオペレーティングシステムシェルのコンポーネントを統合するためのオプションは多数あります。たとえば、コントロールパネルアプレット、スクリーンセーバーなどがありますが、この記事では、Windows Explorerの拡張オプションについて詳しく説明します。機能的な負荷のため。



Windowsエクスプローラーでは、専用のCOMオブジェクトを使用して自分自身を拡張できます。 Windows APIには、そのようなCOMオブジェクトがどのように機能するか、どのメソッドをエクスポートするかを記述するインターフェイスと構造が含まれています。 必要な機能を実装するCOMオブジェクトが開発された後、Windowsレジストリの特定のパスに登録されるため、Windowsエクスプローラーは、登録時に説明した機能を実行するときに、対応するCOMオブジェクトにアクセスします。

そのため、ファイルのコンテキストメニューのリストを展開できるCOMオブジェクトの開発を開始します。

.netフレームワークを使用して開発します。



次のディレクティブをAssembly.csに追加して、アセンブリをCOMオブジェクトとして使用できるようにします。

// Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(true)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("345F4DC2-A9BF-11E2-AA47-CC986188709B")]
      
      





Windows APIのいくつかの機能をインポートする必要があります。

  [DllImport("shell32")] internal static extern uint DragQueryFile(uint hDrop,uint iFile, StringBuilder buffer, int cch); [DllImport("user32")] internal static extern uint CreatePopupMenu(); [DllImport("user32")] internal static extern int InsertMenuItem(uint hmenu, uint uposition, uint uflags, ref MENUITEMINFO mii); [DllImport("user32.dll")] internal static extern bool SetMenuItemBitmaps(IntPtr hMenu, uint uPosition, uint uFlags, IntPtr hBitmapUnchecked, IntPtr hBitmapChecked); [DllImport("Shell32.dll")] internal static extern void SHChangeNotify(int wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2); const int SHCNE_ASSOCCHANGED = 0x08000000; [DllImport("user32.dll", SetLastError = true)] internal static extern bool PostMessage(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
      
      





DragQueryFile関数を使用すると、ディレクトリで選択されたファイルのリスト、SHChangeNotifyを取得して、シェルが変更されたことをオペレーティングシステムに通知できます。



コンテキストメニューを拡張するCOMオブジェクトを開発しているため、IShellExtInitインターフェイスを実装する必要があります。 Initializeメソッドでは、実行中のディレクトリに関する基本情報を取得します。



  [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("000214e8-0000-0000-c000-000000000046")] public interface IShellExtInit { [PreserveSig()] int Initialize (IntPtr pidlFolder, IntPtr lpdobj, uint /*HKEY*/ hKeyProgID); }
      
      





COMインターフェイスIContextMenuを記述して実装することも必要です。 PreserveSigの値がtrueに等しい場合、HRESULT値またはretval値を使用してアンマネージシグネチャの直接変換が開始され、falseによりHRESULT値またはretval値の例外への自動変換が開始されます。 PreserveSigフィールドのデフォルト値はtrueです。

  [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("000214e4-0000-0000-c000-000000000046")] public interface IContextMenu { // IContextMenu methods [PreserveSig()] int QueryContextMenu(uint hmenu, uint iMenu, int idCmdFirst, int idCmdLast, uint uFlags); [PreserveSig()] void InvokeCommand (IntPtr pici); [PreserveSig()] void GetCommandString(int idCmd, uint uFlags, int pwReserved, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] byte[] pszName, uint cchMax); }
      
      





QueryContextMenuメソッドは、コンテキストメニューが呼び出されたときに呼び出されます。メニュー項目を追加するための機能を実装する必要があります。GetCommandStringは、このコマンドの詳細、その説明などを返します。 追加するメニュー項目を選択すると、InvokeCommandが呼び出されます。



COMオブジェクトの場合は、インストールおよびアンインストール機能も実装する必要があります。

 [System.Runtime.InteropServices.ComRegisterFunctionAttribute()] static void RegisterServer(System.Type type) { try { string approved = string.Empty; string contextMenu = string.Empty; RegistryKey root; RegistryKey rk; root = Registry.LocalMachine; rk = root.OpenSubKey(Resources.ApprovedReg, true); rk.SetValue(type.GUID.ToString("B"), Resources.Extension); approved = rk.ToString(); rk.Flush(); rk.Close(); root = Registry.ClassesRoot; rk = root.CreateSubKey(Resources.ExShellReg); rk.Flush(); rk.SetValue(null, type.GUID.ToString("B")); contextMenu = rk.ToString(); rk.Flush(); rk.Close(); EventLog.WriteEntry("Application", "Example ShellExt Registration Complete.\r\n" + approved + "\r\n" + contextMenu, EventLogEntryType.Information); RestartExplorer(); } catch(Exception e) { EventLog.WriteEntry("Application", "Example ShellExt Registration error.\r\n" + e.ToString(), EventLogEntryType.Error); } }
      
      





この関数では、コンテキストメニューの機能を拡張するコンポーネントがあるため、レジストリにコンポーネントを登録します。ContextMenuHandlersセクション(* \\ shellex \\ ContextMenuHandlers \\ ExShell)に登録します。 登録後、explorer.exeプロセスを再起動して、変更をすぐに有効にします。

 [System.Runtime.InteropServices.ComUnregisterFunctionAttribute()] static void UnregisterServer(System.Type type) { try { string approved = string.Empty; string contextMenu = string.Empty; RegistryKey root; RegistryKey rk; // Remove ShellExtenstions registration root = Registry.LocalMachine; rk = root.OpenSubKey(Resources.ApprovedReg, true); approved = rk.ToString(); rk.DeleteValue(type.GUID.ToString("B")); rk.Close(); // Delete regkey root = Registry.ClassesRoot; contextMenu = Resources.ExShellReg; root.DeleteSubKey(Resources.ExShellReg); EventLog.WriteEntry("Application", "Example ShellExt Unregister Complete.\r\n" + approved + "\r\n" + contextMenu, EventLogEntryType.Information); Helpers.SHChangeNotify(0x08000000, 0, IntPtr.Zero, IntPtr.Zero); } catch(Exception e) { EventLog.WriteEntry("Application", "Example ShellExt Unregister error.\r\n" + e.ToString(), EventLogEntryType.Error); } }
      
      





コンポーネント削除機能により、以前に作成されたキーのレジストリがクリアされます。

次に、インターフェイス関数の実装プロセスに進み、インターフェイスIShellExtInit、IContextMenuを実装します。 このクラスのコード全体を詳細に説明するのではなく、これらのインターフェイスの機能の実装に焦点を当てます。



  int IShellExtInit.Initialize (IntPtr pidlFolder, IntPtr lpdobj, uint hKeyProgID) { try { if (lpdobj != (IntPtr)0) { // Get info about the directory IDataObject dataObject = (IDataObject)Marshal.GetObjectForIUnknown(lpdobj); FORMATETC fmt = new FORMATETC(); fmt.cfFormat = CLIPFORMAT.CF_HDROP; fmt.ptd = 0; fmt.dwAspect = DVASPECT.DVASPECT_CONTENT; fmt.lindex = -1; fmt.tymed = TYMED.TYMED_HGLOBAL; STGMEDIUM medium = new STGMEDIUM(); dataObject.GetData(ref fmt, ref medium); m_hDrop = medium.hGlobal; } } catch(Exception) { } return 0; }
      
      





コンポーネントの初期化関数は、コンテキストメニューがあるコンテキストでディレクトリまたはその他のオブジェクトが開かれると起動されます。 IDataObjectインターフェイスを使用して、現在のオブジェクトに関するデータを取得します。特に、hGlobalに関心があります。 このハンドルは、実行が行われる現在のオブジェクトを識別します。



次に、コンテキストメニューがポップアップしたときに呼び出される関数について考えます。



 int IContextMenu.QueryContextMenu(uint hMenu, uint iMenu, int idCmdFirst, int idCmdLast, uint uFlags) { if ( (uFlags & 0xf) == 0 || (uFlags & (uint)CMF.CMF_EXPLORE) != 0) { uint nselected = Helpers.DragQueryFile(m_hDrop, 0xffffffff, null, 0); if (nselected > 0) { for (uint i = 0; i < nselected; i++) { StringBuilder sb = new StringBuilder(1024); Helpers.DragQueryFile(m_hDrop, i, sb, sb.Capacity + 1); fileNames.Add(sb.ToString()); } } else return 0;
      
      





コードのこのセクションでは、正しいコンテキストで実行されていることを確認し、ディレクトリで選択されたファイルの数を要求し、これらのファイルのリストを保存します。 また、iFile = 0xffffffffをDragQueryFile関数に渡すと、ディレクトリ内のファイルの数が返されることに注意してください。



 // Add the popup to the context menu MENUITEMINFO mii = new MENUITEMINFO(); mii.cbSize = 48; mii.fMask = (uint) MIIM.ID | (uint)MIIM.TYPE | (uint) MIIM.STATE; mii.wID = idCmdFirst; mii.fType = (uint) MF.STRING; mii.dwTypeData = Resources.MenuItem; mii.fState = (uint) MF.ENABLED; Helpers.InsertMenuItem(hMenu, (uint)iMenu, (uint)MF.BYPOSITION | (uint)MF.STRING, ref mii); commands.Add(idCmdFirst); System.Reflection.Assembly myAssembly = System.Reflection.Assembly.GetExecutingAssembly(); Stream myStream = myAssembly.GetManifestResourceStream(Resources.BitmapName); Bitmap image = new Bitmap(myStream); Color backColor = image.GetPixel(1, 1); image.MakeTransparent(backColor); Helpers.SetMenuItemBitmaps((IntPtr)hMenu, (uint)iMenu, (uint)MF.BYPOSITION, image.GetHbitmap(), image.GetHbitmap()); // Add a separator MENUITEMINFO sep = new MENUITEMINFO(); sep.cbSize = 48; sep.fMask = (uint )MIIM.TYPE; sep.fType = (uint) MF.SEPARATOR; Helpers.InsertMenuItem(hMenu, iMenu + 1, 1, ref sep); } return 1; }
      
      





ここでは、InsertMenuItem関数を呼び出して新しいメニュー項目を追加し、次にこのメニュー項目にアイコンを準備して追加します。また、美的な美しさのための境界線も追加します。 MENUITEMINFOの構造は、メニュー項目、つまりそのタイプ(ftype)、含まれるデータ(dwTypeData)、ステータス(fState)、メニュー項目識別子(wID)を記述します。 hMenu変数は現在のドロップダウンメニューを識別し、iMenuは追加される位置を識別します。 より完全な情報を取得するには、MSDNに連絡してください。



次に、GetCommandString関数について考えます。

  void IContextMenu.GetCommandString(int idCmd, uint uFlags, int pwReserved, byte[] pszName, uint cchMax) { string commandString = String.Empty; switch(uFlags) { case (uint)GCS.VERB: commandString = "test"; break; case (uint)GCS.HELPTEXTW: commandString = "test"; break; } var buf = Encoding.Unicode.GetBytes(commandString); int cch = Math.Min(buf.Length, pszName.Length - 1); if (cch > 0) { Array.Copy(buf, 0, pszName, 0, cch); } else { // null terminate the buffer pszName[0] = 0; } }
      
      





この関数は、言語に依存しないコマンドの説明と、ヘルプテキストの形式の短いヒントをそれぞれ返します。



さて、メニュー項目を選択するときに呼び出される最後の関数:

  void IContextMenu.InvokeCommand (IntPtr pici) { try { System.Windows.Forms.MessageBox.Show("Test code"); } catch(Exception exe) { EventLog.WriteEntry("Application", exe.ToString()); } }
      
      





ここではすべてが透明です。



COMオブジェクトはWindowsエクスプローラーのコンテキストで実行されるため、デバッグするには、explorer.exeプロセスに接続する必要があります。 コンポーネントを登録および削除するには、この記事のソースコードで提供されるbatファイルを使用できます。 登録には、COMクライアントが.netクラスをCOMのように使用できるようにするRegAsm.exeユーティリティと、GACにアセンブリを配置するGacUtilを使用します。 登録後、プロセスexplorer.exeが再起動されます。

また、システムにインストールされているすべてのWindowsエクスプローラー拡張機能を表示し、必要に応じて編集できるユーティリティにも注目します。 このユーティリティはShellExViewと呼ばれ、 Nirsoftの製造元のWebサイトまたは記事の付録からダウンロードできます。



これは、ShellExViewでのコンポーネントの外観です。





これは、コンテキストメニューが開いているときの表示です。





そこで、Windowsエクスプローラーを拡張するためのコンポーネントを開発する例を見てみましたが、このタイプの拡張機能は、変更できる唯一のものであり、影響を与えることができるものとはほど遠いものです。

これらの種類のコンポーネントがどのように機能するかを理解すれば、コミュニティによって既に開発されたものを見ることができ、そのような操作の実装を促進するために使用できます。

たとえば、 SharpShellライブラリを使用すると、 CodeProject.NET Shell Extensionsの記事シリーズで詳しく説明されているように、かなりの量の変更を実行できます。 WindowsシェルフレームワークライブラリまたはATL + C ++ ミニシェル拡張フレームワークライブラリをアナログとして使用することもできます。



また、このような拡張機能の開発に関するMicrosoftの警告にも注意を向けます:「.NET言語でシェル拡張機能を記述することはお勧めできません。1つのプロセスで使用されるCLRランタイムは1つだけなので、異なるバージョンのCLRを使用する2つのシェル拡張機能で競合が発生する可能性があります。 ただし、.Net Framework 4は.Net Framework 2.0、3.0、3.5のサイドバイサイドテクノロジーをサポートしており、古いCLR 2と新しいCLR 4の両方を同じプロセスで使用できます。

シェル拡張を開発するときに.netを使用する場合の制限について詳しくは、 インプロセス拡張を実装するためのガイダンスをご覧ください。



別の外観:

Cでのエクスプローラー列ハンドラーシェル拡張

シェル拡張ハンドラーの作成





ソースとユーティリティ: ExampleShell.rar



PS Windows 8では拡張機能はテストされていませんでした。レビューにより、レジストリが正しく機能するには、HKEY_CLASSES_ROOT \ CLSID \ {guid component} \ InprocServer32セクションで次のThreadingModel = Apartmentを設定する必要があります。



ご清聴ありがとうございました!




All Articles