Posts
57
Comments
89
Trackbacks
0
Writing Windows Shell Extension with .NET 4 - Part 1

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.
 

posted on Thursday, September 16, 2010 10:11 PM Print
Comments
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Artyom
9/30/2010 3:37 AM
Hello,
First of all GREAT thank you for solution of this problem!!!
But I have a question, it works great in example but I changed ".cs" on ".txt" or "*" and nothing. Could you please deal with this?
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Jialiang
10/1/2010 5:30 PM
Artyom's question has been answered offline. It's caused by a doc error that, in 64bit environment, you need to use Visual Studio x64 Win64 Command Prompt (2010), instead of Visual Studio x64 Cross Tool Command Prompt (2010), to register the shell extension.
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Memfis
10/4/2010 3:40 AM
Hello,
Could you help, please? How can I add icon to menu item, like zip or other programs?
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Artyom
10/7/2010 4:09 AM
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:
http://artyomgrigor.wordpress.com/2010/10/06/writing-windows-shell-extension-context-menu-with-icon-in-c-4-0/
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Jialiang
10/7/2010 10:21 AM
Hi Artyom

Thank you for writing this!
I have a small suggestion for the code on http://artyomgrigor.wordpress.com/2010/10/06/writing-windows-shell-extension-context-menu-with-icon-in-c-4-0/:

The code needs to free the hbitmap handle by PInvoking DeleteObject:

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);


private void DemonstrateGetHbitmap()
{
Bitmap bm = new Bitmap("Picture.jpg");
IntPtr hBitmap = bm.GetHbitmap();

// Do something with hBitmap.
DeleteObject(hBitmap);
}

Otherwise, the bitmap handle is leaked.

For example, you can do it by initializing the bitmap handle in the constructor of FileContextMenuEx, and delete the handle in the finalize
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Kida
1/10/2011 5:36 AM
Any way to add submenu items to the context menu?
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
shailesh
1/26/2011 4:16 PM
I need following funtinality.
1) i have created one folder at specified location and now on that folder i need to only context menu so how i can do it with .net 3.5 or 2.0 would you please let me know .... Many thanks in advance..
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
mahmoud mahrous ahmed
9/18/2011 9:29 PM
the menu item doesn't appear when more than one file selected why please help
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
damo
10/2/2011 11:48 AM
app opens when right click on file, working fine.
But problem is , if we want to add one more file, mouse pointer shows spinning control. so u can not right click any other file until unless u close the app manually..
Please suggest me on how to do this

Thanks
damo
Gravatar
# re: Writing Windows Shell Extensin with .NET 4 - Part 1
wind
12/21/2011 6:46 PM
I have been visiting various blogs for my research papers writing assignment. I have found your blog to be quite useful. Keep updating your blog with valuable information... Regards
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Andrey
1/19/2012 2:59 AM
Could not find this code in 1code browser (tried CS/VB/CppShellExtContextMenuHandler and Shell context menu handler). Finally downloaded samples directly: http://1code.codeplex.com/wikipage?title=WinShell#x_x_x_Download
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Gurgen
1/20/2012 9:14 PM
I do everyting as described above. I.e. create CSShellExtContextMenuHandler.dll and run Regasm.exe CSShellExtContextMenuHandler.dll /codebase.
But on .cs file i didn't get new item with "Display File Name...".
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 1
Mario
4/26/2012 5:40 PM
How to debug a project of this type? A standard F5 does nothing? How can we attach to a process? Thanks

Post Comment

Title *
Name *
Email
Comment *  
 
Advertise on this site through Lake Quincy Media
Tag Cloud