Implementing IDocHostUIHandler in a C++ WTL/ATL project

I recently implemented IDocHostUIHandler in one of my WTL projects hosting the web browser. You need to implement this interface if you want to do things like control the context menu of the web browser.

I had to go spelunking in various forums and documentation to piece together how to do this, and I never found the complete story online, so I thought I'd save any future searchers some trouble. I only did this in Visual Studio 2008, but I've done similar things in VS 2005, so it should work there also.

You should already have a WTL project with an .idl file that implements a COM server. If not, create a new WTL project with the wizard named "MyProject", and choose the option to make it a COM server.

Add an ATL Simple Object

Next, add a new class to your project, and select the type "ATL Simple Object."

Here's where you might hit your first snag. You might see the following error dialog:

Error, only MFC projects please

ATL classes can only be added to MFC EXE and MFC Regular DLL projects or projects with full ATL support

"I'm using the ATL, you idiot!" you yell vainly at your computer screen. But alas, the compiler gods did not consider that you might want to use something as esoteric as the WTL for your GUI project, when you have the obviously superior choice of MFC.

To convince Visual Studio that yes, you do deserve to add an ATL Simple Object to your project, you've got to modify the file "VS Root/VC/VCWizards/1033/common.js". Specifically, you've got to have the function IsATLProject return true.

On the Internet, you'll see all sorts of fixes to this function, but I just cut to the chase:

function IsATLProject(oProj)
{
    // I promise that I will use this only for good and not evil.
    return true ;
    try
    {
            …

Try adding the object again; this time, you should be successful. Name your class something descriptive, like "MyHandler" ( 🙂 ), and accept all the defaults.

Modify the .idl file

Now go into your .idl file, and make some edits. First, import "atliface.idl", and #include "olectl.h". The top of your file should now look like this:

import "oaidl.idl";
import "ocidl.idl";
import "atliface.idl";

#include "olectl.h"

Now, go to your IMyHandler interface, and change the base class from IDispatch to IDocHostUIHandlerDispatch, like so:

[
    object,
    uuid(12345678-1234-1234-1234-123456789ABC),
    dual,
    nonextensible,
    helpstring("IMyHandler Interface"),
    pointer_default(unique)
]
interface IMyHandler : IDocHostUIHandlerDispatch{
};

Modify your MyHandler.h file

Go into your CMyHandler class file, and #include "Atliface.h". Next, add an entry to your COM map for IDocHostUIHandlerDispatch. The top of your file should now look something like this:

#pragma once
#include "resource.h"       // main symbols
#include "Atliface.h"
#include "MyProject.h"

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Blah blah blah"
#endif

// CMyHandler

class ATL_NO_VTABLE CMyHandler :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CMyHandler, &CLSID_MyHandler>,
    public IDispatchImpl<IMyHandler, &IID_MyHandler, &LIBID_MyProject, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:

    CMyHandler()
    {
    }

DECLARE_REGISTRY_RESOURCEID(IDR_MYHANDLER)

BEGIN_COM_MAP(CMyHandler)
    COM_INTERFACE_ENTRY(IMyHandler)
    COM_INTERFACE_ENTRY(IDocHostUIHandlerDispatch)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

Add skeletons for the IDocHostUIHandler methods

Add skeletons for all the methods implemented by IDocHostUIHandler. You can simply copy and paste the method signatures from IDocHostUIHandlerDispatch in "Atliface.h". Now implement the method bodies so that they all return E_NOTIMPL.

Compile your code

Now compile, and make sure that everything's working.

Here's where you might hit another snag: by failing to show proper respect to the compiler gods above, you could see an obscure MIDL error when you try to compile. Even if you don't, I recommend compiling your .idl file manually just to be sure. Go to All Programs >> Visual Studio 200X >> Visual Studio Tools >> Command Prompt (this loads the command prompt with all your VS symbols defined).

Next, navigate to your source directory, and compile your .idl file:

midl MyProject.idl

Implement methods of interest

Now, you can implement the methods you're interested in. For example, if you want to suppress the context menu, or show your own menu in its place:

HRESULT STDMETHODCALLTYPE ShowContextMenu(DWORD dwID, DWORD x, DWORD y, IUnknown *pcmdtReserved, IDispatch *pdispReserved, HRESULT *dwRetVal )
{
    *dwRetVal = S_OK;
    // Show your own context menu here if you want.
    return S_OK;
}

Set the handler in your web browser

In your OnCreate handler, set your handler in the web browser.

    CComObject<CMyHandler> *pUIH = NULL;
    HRESULT hr = CComObject<CMyHandler>::CreateInstance (&pUIH);
    if (SUCCEEDED(hr))
    {
        // Make our custom DocHostUIHandler the window.external handler
        CComQIPtr<IDocHostUIHandlerDispatch> pIUIH = pUIH;
        hr = m_view.SetExternalUIHandler(pIUIH) ;
    }
    ATLASSERT(SUCCEEDED(hr)) ;

That's it. Compile and run.

6 comments to Implementing IDocHostUIHandler in a C++ WTL/ATL project

  • //You need to implement this interface if you want to do things like control the context menu of the web browser.//

    That’s fine. But at the risk of appearing stupid, I would like to know:
    1. What is meant by a context menu of the web browser?
    2. Why should it be controlled?

    Regards,
    Dondu N. Raghavan

  • @Dondu

    The “context menu” is what appears when you right click on a web page, or when you hit the “menu” key on your keyboard while the page has focus.

    There are many reasons why you might want to control the context menu.

    * Maybe you’re implementing special functionality, and the normal browser commands aren’t relevant

    * Maybe you want to add your own relevant commands. For example, if you’re using the web browser to display music albums, you might provide the command “play this album” in the context menu. Since I write a lot of software for translators, maybe a “translate this sentence” command so that users can translate a web page live and visually.

    * You also might want to disable certain commands like “show source” or “copy link location.”

    I’ll also add that this isn’t the only reason to implement IDocHostUIHandler. You can also use it to show images dynamically in response to window resizing; allow more than one file to be dropped into the page at once; supply your own home page and other default settings; override or provide your own keyboard shortcuts; process URLs (e.g. to take the user’s Local App Data directory as the base for relative URLs); and so on. The possibilities are pretty broad.

  • Hello Ryan,

    I followed your tutorial but I get error C2259: ‘ATL::CComObject’ : cannot instantiate abstract class, with a list of every single method in IDocHostUIHandler, even though I implemented them.

    Have you encountered that problem ? Can you post a zip file with your minimal, working example ?

    TIA,


    Guillaume

  • @Guillaume

    I’ll try to post a working example, but could you post some of your failing code? For example, the class declaration of your derived handler class.

  • Thank you Ryan for offering your time to look at my code, it actually helped me find the problem ! I had a IDocHostUIHandler implementation (the custom interface, not disp) from another project that I wanted to reuse. The copy-paste kind of reuse.

    Turns out that I didn’t pay attention to the interface. Methods of IDocHostUIHandlerDispatch have different parameters than IDocHostUIHandler, wich explains while the compiler wasn’t finding them.

    I should have followed your suggestion and copy the function prototypes from atliface.h, listening to the saying “If you don’t know where you are going, don’t take a shortcut”…

    Thanks for your time !

  • Oh, and one little thing that got me…

    The code snippet that calls SetExternalUIHandler must be put after the call to the base class OnCreate (that creates your window). Else SetExternalUIHandler will fail in a call to AtlAxGetHost.

    Everything is working now, thanks a million !

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>