About Us
The Microsoft All-In-One Code Framework (http://1code.codeplex.com) is a free, centralized code sample library driven by developers' needs. Our goal is to provide typical code samples for all Microsoft development technologies, and reduce developers' efforts in solving typical programming tasks.
Our team listens to developers’ pains in MSDN forums, social media and various developer communities. We write code samples based on developers’ frequently asked programming tasks, and allow developers to download them with a short code sample publishing cycle. Additionally, our team offers a free code sample request service. This service is a proactive way for our developer community to obtain code samples for certain programming tasks directly from Microsoft.
Introduction
In MSDN forums, lots of developers ask how to write Windows Shell extension with .NET languages (e.g. C#, VB.NET).
Prior to .NET Framework 4, the development of in-process shell extensions using managed code was 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.
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 document explains the theory. How on earth can I write a managed shell extension?
If you search on the internet, you would find that there are 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’ needs, 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 implement certain COM interfaces. For example:
[ClassInterface(ClassInterfaceType.None)]
[Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)]
public class SimpleObject : ISimpleObject
{
...
}
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 hKeyProgID);
}
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214e4-0000-0000-c000-000000000046")]
internal interface IContextMenu
{
[PreserveSig]
int QueryContextMenu(
IntPtr 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
Please visit http://1code.codeplex.com to download the latest code sample.