Writing Windows Shell Extension with .NET Framework 4 (C#, VB.NET) - Part 1

Writing Windows Shell Extension with .NET Framework 4 (C#, VB.NET) - Part 1

Rate This
  • Comments 11

Sample download

In MSDN forums, lots of developers ask how to write Windows Shell extension with .NET languages (e.g. C#, VB.NET).

http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ceb44d5-dce8-4197-ac55-f0f4fb59eeb4/
http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ce0c480-59e3-4732-a608-1974a908e44a/
http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/1428326d-7950-42b4-ad94-8e962124043e
http://social.msdn.microsoft.com/Forums/en-US/clr/thread/63d04f72-5c71-40a9-aea3-519c9e9591a6

Prior to .NET Framework 4, the development of in-process shell extensions using managed code is not officially supported because of the CLR limitation allowing only one .NET runtime per process. Jesse Kaplan, one of the CLR program managers, explains it in this MSDN forum thread: http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/1428326d-7950-42b4-ad94-8e962124043e.

In .NET 4, with the ability to have multiple runtimes in process with any other runtime, Microsoft can now offer general support for writing managed shell extensions—even those that run in-process with arbitrary applications on the machine. This article introduces the in-process side-by-side feature in detail. However, please note that you still cannot write shell extensions using any version earlier than .NET Framework 4 because those versions of the runtime do not load in-process with one another and will cause failures in many cases.

The documents explains the theory. How can I on earth write a managed shell extension?

If you search on the internet, you would find that there’s almost zero .NET 4 shell extension samples. The few .NET 2 shell extension samples (not supported because of the above reason) have more or less some defects, e.g. not being able to load in x64 environment. In order to meet customers’ want, we, All-In-One Code Framework project group, would like to fill in the blank. The project group has planned a series of .NET 4 managed Shell extension code samples for Context Menu Handler, Property Sheet Handler, Icon handler, Data handler, Drop handler, Drag-and-drop handler, Thumbnail Handler, Icon Handler, Icon Overlay Handler, and so on. This article introduces the first sample: Context Menu Handler.

CSShellExtContextMenuHandler:   Shell context menu handler (C#) 
VBShellExtContextMenuHandler:   Shell context menu handler (VB.NET)
CppShellExtContextMenuHandler: Shell context menu handler (C++)

Demo

Here is a quick demo of the context menu handler code sample.  After you successfully build the sample project CSShellExtContextMenuHandler in Visual Studio 2010, you will get a DLL: CSShellExtContextMenuHandler.dll. Run 'Visual Studio Command Prompt (2010)' (or 'Visual Studio x64 Win64 Command Prompt (2010)' if you are on a x64 operating system) in the Microsoft Visual Studio
2010 \ Visual Studio Tools menu as administrator. Navigate to the folder that contains the build result CSShellExtContextMenuHandler.dll and enter the command:

       Regasm.exe CSShellExtContextMenuHandler.dll /codebase

to register the context menu handler.

Find a .cs file in the Windows Explorer (e.g. FileContextMenuExt.cs in the sample folder), and right click it. You would see the "Display File Name (C#)" menu item in the context menu and a menu seperator below it. Clicking the menu item brings up a message box that displays the full path of the .cs file.

Implementation Details

A. Creating and configuring the project

In Visual Studio 2010, create a Visual C# / Windows / Class Library project named "CSShellExtContextMenuHandler". Open the project properties, and in the Signing page, sign the assembly with a strong name key file.

-----------------------------------------------------------------------------

B. Implementing a basic Component Object Model (COM) DLL

Shell extension handlers are all in-process COM objects implemented as DLLs. Making a basic .NET COM component is very straightforward. You just need to define a 'public' class with ComVisible(true), use the Guid attribute to specify its CLSID, and explicitly implements certain COM interfaces. For example,

    [ClassInterface(ClassInterfaceType.None)]
    [Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)]
    public class SimpleObject : ISimpleObject
    {
        ... // Implements the interface
    }

You even do not need to implement IUnknown and class factory by yourself because .NET Framework handles them for you.

-----------------------------------------------------------------------------

C. Implementing the context menu handler and registering it for a certain file class

-----------
Implementing the context menu handler:

The FileContextMenuExt.cs file defines a context menu handler. The context menu handler must implement the IShellExtInit and IContextMenu interfaces. The interfaces are imported using the COMImport attribute in ShellExtLib.cs.

    [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("000214e8-0000-0000-c000-000000000046")]
    internal interface IShellExtInit
    {
        void Initialize(
            IntPtr pidlFolder,
            IntPtr 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);
    }

    [ClassInterface(ClassInterfaceType.None)]
    [Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)]
    public class FileContextMenuExt : IShellExtInit, IContextMenu
    {
        public void Initialize(IntPtr pidlFolder, IntPtr pDataObj, IntPtr hKeyProgID)
        {
            ...
        }
 
        public int QueryContextMenu(
            IntPtr hMenu,
            uint iMenu,
            uint idCmdFirst,
            uint idCmdLast,
            uint uFlags)
        {
            ...
        }

        public void InvokeCommand(IntPtr pici)
        {
            ...
        }

        public void GetCommandString(
            UIntPtr idCmd,
            uint uFlags,
            IntPtr pReserved,
            StringBuilder pszName,
            uint cchMax)
        {
            ...
        }
    }

The PreserveSig attribute indicates that the HRESULT or retval signature transformation that takes place during COM interop calls should be suppressed. When you do not apply PreserveSigAttribute (e.g. the GetCommandString method of IContextMenu), the failure HRESULT of the method needs to be thrown as a .NET exception. For example, Marshal.ThrowExceptionForHR(WinError.E_FAIL); When you apply the PreserveSigAttribute to a managed method signature, the managed and unmanaged signatures of the attributed method are identical (e.g. the QueryContextMenu method of IContextMenu). Preserving the original method signature is necessary if the member returns more than one success HRESULT value and you want to detect the different values.

A context menu extension is instantiated when the user displays the context menu for an object of a class for which the context menu handler has been registered.

  1 Implementing IShellExtInit

  After the context menu extension COM object is instantiated, the  IShellExtInit.Initialize method is called. IShellExtInit.Initialize supplies the context menu extension with an IDataObject object that holds one or more file names in CF_HDROP format. You can enumerate the selected files and folders through the IDataObject object. If a failure HRESULT is returned (thrown) from IShellExtInit.Initialize, the context menu extension will not be used.

  In the code sample, the FileContextMenuExt.Initialize method enumerates the selected files and folders. If only one file is selected, the method stores the file name for later use. If more than one file or no file are selected, the method throws an exception with the E_FAIL HRESULT to not use the context menu extension.

  2. Implementing IContextMenu

  After IShellExtInit.Initialize returns successfully, the IContextMenu.QueryContextMenu method is called to obtain the menu item or items that the context menu extension will add. The QueryContextMenu implementation is fairly straightforward. The context menu extension adds its menu items using the InsertMenuItem or similar function. The menu command identifiers must be greater than or equal to idCmdFirst and must be less than idCmdLast. QueryContextMenu must return the greatest numeric identifier added to the menu plus one. The best way to assign menu command identifiers is to start at zero and work up in sequence. If the context menu extension does not need to add any items to the menu, it should simply return from QueryContextMenu.

  In this code sample, we insert the menu item "Display File Name (C#)", and add a menu seperator below it.

  IContextMenu.GetCommandString is called to retrieve textual data for the menu item, such as help text to be displayed for the menu item. If a user highlights one of the items added by the context menu handler, the handler's IContextMenu.GetCommandString method is called to request a Help text string that will be displayed on the Windows Explorer status bar. This method can also be called to request the verb string that is assigned to a command. Either ANSI or Unicode verb strings can be requested. This example only implements support for the Unicode values of uFlags, because only those have been used in Windows Explorer since Windows 2000.

  IContextMenu.InvokeCommand is called when one of the menu items installed by the context menu extension is selected. The context menu performs or initiates the desired actions in response to this method.

-----------
Registering the handler for a certain file class:

Context menu handlers are associated with either a file class or a folder. For file classes, the handler is registered under the following subkey.

    HKEY_CLASSES_ROOT\<File Type>\shellex\ContextMenuHandlers

The registration of the context menu handler is implemented in the Register method of FileContextMenuExt. The ComRegisterFunction attribute attached to the method enables the execution of user-written code other than the basic registration of the COM class. Register calls the ShellExtReg.RegisterShellExtContextMenuHandler method in ShellExtLib.cs to associate the handler with a certain file type. If the file type starts with '.', it tries to read the default value of the HKCR\<File Type> key which may contain the Program ID to which the file type is linked. If the default value is not empty, use the Program ID as the file type to proceed the registration.

For example, this code sample associates the handler with '.cs' files. HKCR\.cs has the default value 'VisualStudio.cs.10.0' by default when Visual Studio 2010 is installed, so we proceed to register the handler under HKCR\VisualStudio.cs.10.0\ instead of under HKCR\.cs. The following keys and values are added in the registration process of the sample handler.

    HKCR
    {
        NoRemove .cs = s 'VisualStudio.cs.10.0'
        NoRemove VisualStudio.cs.10.0
        {
            NoRemove shellex
            {
                NoRemove ContextMenuHandlers
                {
                    {B1F1405D-94A1-4692-B72F-FC8CAF8B8700} =
                        s 'CSShellExtContextMenuHandler.FileContextMenuExt'
                }
            }
        }
    }

The unregistration is implemented in the Unregister method of FileContextMenuExt. Similar to the Register method, the ComUnregisterFunction attribute attached to the method enables the execution of user-written code during the unregistration process. It removes the {<CLSID>} key under HKCR\<File Type>\shellex\ContextMenuHandlers.

Download

http://1code.codeplex.com/releases

And find the CS/VB/CppShellExtContextMenuHandler samples in the Visual Studio 2010 folder.

If you have any feedback about the samples, please email onecode@microsoft.com.

Please stay tuned for Part 2 of the series, which will talk about writing Infotip Handler in managed languages.
 

Leave a Comment
  • Please add 1 and 3 and type the answer here:
  • Post
  • Heya!

    I have done all the steps to try the demo and when i run:

    "Regasm.exe CSShellExtContextMenuHandler.dll /codebase"

    i get somthing like this:

    C:\Users\Bruno\Downloads\All-In-One Code Framework(2)\Visual Studio 2010\CSShell

    ExtContextMenuHandler\obj\Release>Regasm.exe CSShellExtContextMenuHandler.dll /c

    odebase

    Microsoft (R) .NET Framework Assembly Registration Utility 4.0.30319.1

    Copyright (C) Microsoft Corporation 1998-2004.  All rights reserved.

    Types registered successfully

    C:\Users\Bruno\Downloads\All-In-One Code Framework(2)\Visual Studio 2010\CSShell

    ExtContextMenuHandler\obj\Release>

    But when i right click a .cs file the option "Display filename c#" doesnt apear...

    btw im using windows 7

    can someone please help?

  • is there any way to "debug" to find the problem? since everthing works as excepted but i don't get the menu on the right click...

  • Hey. I notice that you are using the dll under All-In-One Code Framework(2)\Visual Studio 2010\CSShellExtContextMenuHandler\obj\Release. You should actually use the one in the All-In-One Code Framework(2)\Visual Studio 2010\Release folder

  • Im also was unable to see the item in the contect menu using the vb version on windows 7 x64.

    I used visual studio 2010 to compile the dll and ran "Visual Studio x64 Cross Tools Command Prompt (2010)" as administrator to register the dll. Any idea why this could be. thanks

  • Hello Joe

    It is caused by a mistake in the documentation. You should actually use "Visual Studio x64 Win64 Command Prompt (2010)" instead of "Visual Studio x64 Cross Tools Command Prompt (2010)" on x64 OS to register the shell extension. Please try it again and let me know if it works for you now. All the sample packagess have been updated.

  • Hello BruNeX

    Are you running on x64 Win7 OS? If so, please use "Visual Studio x64 Win64 Command Prompt (2010)" instead of "Visual Studio x64 Cross Tools Command Prompt (2010)" to register the shell extension. Sorry for the mistake in the documentation. The fix has been checked in.

  • That was it. Using Win64 solved the problem. Many thanks!

  • Hello,

    It's GREAT sample!

    But how can I add icon in context menu?

  • If you want to add icon to context menu you just need to change only

    QueryContextMenu function in CSShellExtContextMenuHandler sample. Please read on my blog:

    artyomgrigor.wordpress.com/.../writing-windows-shell-extension-context-menu-with-icon-in-c-4-0

  • Great work! Thank you for making this freely available.

    Also what is the best way to create sub menu items in the sample above.

  • You should publish this on the CodeProject.com.

    It's the best solution if you want to reach widest possible audience ;)

Page 1 of 1 (11 items)