Don’t forget to implement canonical names for verbs in your shell context menu extension

A commonly-overlooked implementation detail of shell context menu extensions is the canonical verb, which is returned by the IContext­Menu::Get­Command­String method and which can be passed to IContext­Menu::Invoke­Command. Canonical verbs permit your context menu to be consistently invoked programmatically, since they no longer need to rely on the menu text's display name, which can change based on the user's preferred language, or which can change due to the whim of the shell extension. (For example, the shell extension might decide that instead of a generic Revert to previous version menu item, it'll tailor the text to say something like Revert to March 1, 2017 6:34pm, which may look nicer to the user, but it becomes impossible to identify programmatically.)

Another reason to support canonical verbs is to ensure that Explorer itself invokes your verb consistently.

A large category of Explorer hangs were traced back to hangs in shell context menu extensions. To mitigate this problem, Explorer actually juggles two context menus. The first context menu is shown to the user on the UI thread. If the user picks a command from the context menu, then Explorer hands the request to a background thread. The background thread creates a second context menu¹ and uses it to perform the requested operation. That way, if the operation hangs, it's a background thread that hangs, and that isn't quite so bad as hanging a UI thread.

Now, how does Explorer hand the request to a background thread? It takes the menu item the user selected and tries to obtain its corresponding canonical verb. If successful, then it uses that same canonical verb to invoke the command from the background thread. If the shell extension responsible for the menu item does not produce a canonical verb, then the shell applies various heuristics to the second context menu to try to find the item that appears to resemble the one selected by the user most closely.

If you cough up a canonical verb, then Explorer can reliably invoke your verb from the background thread. If you don't, then you end up being subjected to a heuristic, and heuristics sometimes fail.

¹ Explorer cannot simply use the original context menu from a background thread because the original context menu is a COM object that was created on the UI thread, which is a single-threaded apartment. Using it from a background thread requires marshaling, but if you did that and invoked the verb from the background thread, the marshaler would simply switch back to the UI thread and perform the operation there, because it must respect the threading model of the COM object, and the COM object says, "I can be used only on the UI thread."

Marshaling back to the UI thread to perform the operation means that if the operation hangs, we hang the UI thread, which was exactly the problem we were trying to avoid!

Comments (4)
  1. skSdnW says:

    This seems overcomplicated to me. Why not just create another UI thread with a “invisible” 1×1 window at the menu point and then show the menu and execute in this thread?

    1. Because context menu handlers might do things like query the site chain for other services (e.g., to manipulate the host window).

      1. skSdnW says:

        Does that mean that you cannot interact with the site you got from IObjectWithSite in InvokeCommand?

  2. Joshua A Schaeffer says:

    What if the 1st context menu acts on shell items that needed UI intervention to unlock and deep-enumerate aka IShellFolder::EnumObjects(HWND)? You create the 2nd context menu on the 2nd thread that parses a different instance of the same namespace (which I assume because of STA), what will happen if it doesn’t see the same enumeration state and needs user input?

    I don’t think the design is overcomplicated, I’m just not convinced it’s correct. I must be overlooking something.

Comments are closed.

Skip to main content