Introduction
I am a web developer by day, but a C++/MFC coder by night! While I was doing my day job, our client requested that we provide them a solution to ensure that they had a way to allow their employees to take exams and view course content over the Internet in a more secure fashion than regular IE, so that their employees couldn't cheat or copy content and sell it on eBay very easily. So I figured if I could create my own window that goes full screen, I could hide the task bar, disable keystrokes, and whatever I needed to do to lock down the system while my app was open. However I needed to be able to customize the Internet Explorer Web Browser control pretty heavily (custom context menus, dialogs, windows, c++ calls from JavaScript, message boxes, and override keystrokes within the browser window).
I thought I would try coding it in C# and try some of this .NET stuff out on something more complex than a simple database application, but I quickly realized that video wouldn't even play in the HTML page I was loading through the C# implementation (Big problem for us considering we create interactive media solutions). Not to mention, having to make a function declaration for every raw Win32 API call I wanted to make, was really trying my patience. I even tried the .NET CF library, but it was missing many functions I needed. I ported some C++ code I had written to intercept keystrokes using a system wide hook, over to C# and when I did finally get it all ported over to C#, it crashed randomly and only trapped some of the keys I needed (C# may do garbage collection for you, but that doesn't mean your app won't crash). Needless to say, I just decided to start coding it in C++/MFC (long live C++ 6.0 MFC!).
Research or as I like to say (wandering aimlessly deep within the bowels of MSDN)
I began my journey of researching how to make a custom web browser using the Microsoft WebBrowser
control that would allow me to have custom message boxes, custom modal dialogs, custom windows, custom context menus, call C++ code from JavaScript, and disable specific keystroke combinations. I found a ton of documents on MSDN and other sites telling me that I have to implement the IDocHostShowUI
and IDocHostUIHandler
COM interfaces and IDispatch
something or rather. The only problem was I didn't know what that meant or how to do it. The source code I did find, only did a small part of what I needed to do and used a CHtmlView
while I needed it to work in a dialog based application. The source code also left me in the dark on many of the other tasks I needed to do.
Using a single article that I found in the Microsoft knowledge base (after hours and hours of searching) and then writing and tweaking a little code, I was able to finally get all of the functionality I required in my custom web browser application.
How to create the custom web browser application
Before we start, I know this looks scary because of all the header files and CPP files we are going to have to add as well as the sheer length of this article, but it really isn't nearly as complicated or scary as it may seem. That's important to realize I think, because I know I almost got scared away when I started to read that knowledge base article too :)
I must say a lot of credit for this goes to Microsoft Knowledge Base Article - 236312. It was a big factor in allowing me to create a custom web browser solution that was flexible enough to meet my requirements.
First thing you have to do is create a dialog based application using the MFC AppWizard. Next go to the ResourceView tab in Visual Studio and open your dialog in design mode. Now go to "Projects", "Add to Project", "Components and Controls...". Scroll through the controls in the Registered ActiveX Controls folder until you find Microsoft Web Browser. Now click on that and click the insert button and then close the Components and Controls Gallery dialog window.
Now if you look at the controls toolbar in the design view of your dialog based app, you will see a new item that has a globe for an icon. That is our web browser control. So just click on that item and put it on your dialog box. Now press (Ctrl+W) to bring up the class wizard and add a member variable called m_browser
for IDC_EXPLORER1
.
Now here is where the knowledge base article I mentioned earlier comes into play. I would normally just refer you to the knowledge base article, but we all know how reliable Microsoft's links are. Therefore I will basically copy the instructions from the knowledge base article and paste below.
- Open CustomBrowser.h and add a public member variable as shown below to the
CCustomBrowserApp
class: public:
class CImpIDispatch* m_pDispOM;
- Add a new header file to the project called Custsite.h. Copy and paste the following code into it:
#ifndef __CUSTOMSITEH__
#define __CUSTOMSITEH__
#include "idispimp.h"
#include <MSHTMHST.H>
class CCustomControlSite:public COleControlSite
{
public:
CCustomControlSite(COleControlContainer *pCnt):
COleControlSite(pCnt){}
protected:
DECLARE_INTERFACE_MAP();
BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler)
STDMETHOD(ShowContextMenu)( DWORD dwID,
POINT __RPC_FAR *ppt,
IUnknown __RPC_FAR *pcmdtReserved,
IDispatch __RPC_FAR *pdispReserved);
STDMETHOD(GetHostInfo)(
DOCHOSTUIINFO __RPC_FAR *pInfo);
STDMETHOD(ShowUI)(
DWORD dwID,
IOleInPlaceActiveObject __RPC_FAR *pActiveObject,
IOleCommandTarget __RPC_FAR *pCommandTarget,
IOleInPlaceFrame __RPC_FAR *pFrame,
IOleInPlaceUIWindow __RPC_FAR *pDoc);
STDMETHOD(HideUI)(void);
STDMETHOD(UpdateUI)(void);
STDMETHOD(EnableModeless)( BOOL fEnable);
STDMETHOD(OnDocWindowActivate)( BOOL fEnable);
STDMETHOD(OnFrameWindowActivate)( BOOL fEnable);
STDMETHOD(ResizeBorder)(
LPCRECT prcBorder,
IOleInPlaceUIWindow __RPC_FAR *pUIWindow,
BOOL fRameWindow);
STDMETHOD(TranslateAccelerator)(
LPMSG lpMsg,
const GUID __RPC_FAR *pguidCmdGroup,
DWORD nCmdID);
STDMETHOD(GetOptionKeyPath)(
LPOLESTR __RPC_FAR *pchKey,
DWORD dw);
STDMETHOD(GetDropTarget)(
IDropTarget __RPC_FAR *pDropTarget,
IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget);
STDMETHOD(GetExternal)(
IDispatch __RPC_FAR *__RPC_FAR *ppDispatch);
STDMETHOD(TranslateUrl)(
DWORD dwTranslate,
OLECHAR __RPC_FAR *pchURLIn,
OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut);
STDMETHOD(FilterDataObject)(
IDataObject __RPC_FAR *pDO,
IDataObject __RPC_FAR *__RPC_FAR *ppDORet);
END_INTERFACE_PART(DocHostUIHandler)
};
class CCustomOccManager :public COccManager
{
public:
CCustomOccManager(){}
COleControlSite* CreateSite(COleControlContainer* pCtrlCont)
{
CCustomControlSite *pSite =
new CCustomControlSite(pCtrlCont);
return pSite;
}
};
#endif
- Add a new CPP file called Custsite.cpp and add the code below to it:
#include "stdafx.h"
#undef AFX_DATA
#define AFX_DATA AFX_DATA_IMPORT
#include "CustomBrowser.h"
#include <..\src\occimpl.h>
#undef AFX_DATA
#define AFX_DATA AFX_DATA_EXPORT
#include "custsite.h"
BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite)
INTERFACE_PART(CCustomControlSite,
IID_IDocHostUIHandler, DocHostUIHandler)
END_INTERFACE_MAP()
ULONG FAR EXPORT CCustomControlSite::XDocHostUIHandler::AddRef()
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return pThis->ExternalAddRef();
}
ULONG FAR EXPORT CCustomControlSite::XDocHostUIHandler::Release()
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return pThis->ExternalRelease();
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::QueryInterface(REFIID
riid, void **ppvObj)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
HRESULT hr =
(HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj);
return hr;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::GetHostInfo(
DOCHOSTUIINFO* pInfo )
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER;
pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
return S_OK;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::ShowUI(
DWORD dwID,
IOleInPlaceActiveObject * ,
IOleCommandTarget * pCommandTarget,
IOleInPlaceFrame * ,
IOleInPlaceUIWindow * )
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return S_OK;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::HideUI(void)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return S_OK;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::UpdateUI(void)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
anything here
return S_OK;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::EnableModeless(BOOL
)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::OnDocWindowActivate(BOOL
)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::OnFrameWindowActivate(BOOL
)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::ResizeBorder(
LPCRECT ,
IOleInPlaceUIWindow* ,
BOOL )
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::ShowContextMenu(
DWORD ,
POINT* ,
IUnknown* ,
IDispatch* )
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return S_OK;
to show its own.
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::TranslateAccelerator(LPMSG
lpMsg,
const GUID __RPC_FAR *pguidCmdGroup,
DWORD nCmdID)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return S_FALSE;
}
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::GetOptionKeyPath(BSTR*
pbstrKey, DWORD)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
STDMETHODIMP CCustomControlSite::XDocHostUIHandler::GetDropTarget(
IDropTarget __RPC_FAR *pDropTarget,
IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
STDMETHODIMP CCustomControlSite::XDocHostUIHandler::GetExternal(
IDispatch __RPC_FAR *__RPC_FAR *ppDispatch)
{
IDispatch* pDisp = (IDispatch*)theApp.m_pDispOM;
pDisp->AddRef();
*ppDispatch = pDisp;
return S_OK;
}
STDMETHODIMP CCustomControlSite::XDocHostUIHandler::TranslateUrl(
DWORD dwTranslate,
OLECHAR __RPC_FAR *pchURLIn,
OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
STDMETHODIMP CCustomControlSite::XDocHostUIHandler::FilterDataObject(
IDataObject __RPC_FAR *pDO,
IDataObject __RPC_FAR *__RPC_FAR *ppDORet)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return E_NOTIMPL;
}
- Add a new header file called Idispimp.h and add the following code to it:
#ifndef _IDISPIMP_H_
#define _IDISPIMP_H_
class CImpIDispatch : public IDispatch
{
protected:
ULONG m_cRef;
public:
CImpIDispatch(void);
~CImpIDispatch(void);
STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo);
STDMETHODIMP GetTypeInfo( UINT iTInfo,
LCID lcid,
ITypeInfo** ppTInfo);
STDMETHODIMP GetIDsOfNames(
REFIID riid,
LPOLESTR *rgszNames,
UINT cNames,
LCID lcid,
DISPID *rgDispId);
STDMETHODIMP Invoke(
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pDispParams,
VARIANT *pVarResult,
EXCEPINFO *pExcepInfo,
UINT *puArgErr);
};
#endif
- Add a new CPP file called Idispimp.cpp and add the following code to it:
#include "stdafx.h"
#include "idispimp.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
const WCHAR pszExtend[10]=L"xxyyzz";
#define DISPID_Extend 12345
CImpIDispatch::CImpIDispatch( void )
{
m_cRef = 0;
}
CImpIDispatch::~CImpIDispatch( void )
{
ASSERT( m_cRef == 0 );
}
STDMETHODIMP CImpIDispatch::QueryInterface
( REFIID riid, void **ppv )
{
*ppv = NULL;
if ( IID_IDispatch == riid )
{
*ppv = this;
}
if ( NULL != *ppv )
{
((LPUNKNOWN)*ppv)->AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
{
return --m_cRef;
}
STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT* )
{
return E_NOTIMPL;
}
STDMETHODIMP CImpIDispatch::GetTypeInfo( UINT ,
LCID ,
ITypeInfo** )
{
return E_NOTIMPL;
}
STDMETHODIMP CImpIDispatch::GetIDsOfNames(
REFIID riid,
OLECHAR** rgszNames,
UINT cNames,
LCID lcid,
DISPID* rgDispId)
{
HRESULT hr;
UINT i;
hr = NOERROR;
for ( i=0; i < cNames; i++)
{
if ( 2 == CompareString( lcid,
NORM_IGNOREWIDTH, (char*)pszExtend,
3, (char*)rgszNames[i], 3 ) )
{
rgDispId[i] = DISPID_Extend;
}
else
{
hr = ResultFromScode(DISP_E_UNKNOWNNAME);
rgDispId[i] = DISPID_UNKNOWN;
}
}
return hr;
}
STDMETHODIMP CImpIDispatch::Invoke(
DISPID dispIdMember,
REFIID ,
LCID ,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pVarResult,
EXCEPINFO* ,
UINT* puArgErr)
{
if ( dispIdMember == DISPID_Extend )
{
if ( wFlags & DISPATCH_PROPERTYGET )
{
if ( pVarResult != NULL )
{
WCHAR buff[10]=L"Wibble";
BSTR bstrRet = SysAllocString( buff );
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BSTR;
V_BSTR(pVarResult) = bstrRet;
}
}
}
return S_OK;
}
- Open CustomBrowser.cpp and, in the
InitInstance
of CCustomBrowser
, add the following code. Also comment out the call to AfxEnableControlContainer()
: BOOL
CCustomBrowserApp::InitInstance()
{
CCustomOccManager *pMgr = new CCustomOccManager;
m_pDispOM = new CImpIDispatch;
AfxEnableControlContainer(pMgr);
}
- Also add the following to the list of include files in CustomBrowser.cpp:
#include "afxpriv.h"
#include <..\src\occimpl.h>
#include "CustSite.h"
- Go to CustomBrowser.h and add the following statement at the bottom of the file.
extern CCustomBrowserApp theApp;
Now that we have all of that done, we have a good start, but we still have ways to go. Basically what we have done so far is implement the IDocHostUIHandler
and the IDispatch
interfaces. This will allow us to do things like customize the context menus and call C++ functions from our JavaScript code using window.external
. However, we also want to implement the IDocHostShowUI
interface, so we can provide our own custom message boxes. We also need to write some extra code to make it possible to open modal and modeless dialog boxes using our custom web browser, create C++ functions that our JavaScript can call, and show a custom context menu.
We will start by implementing the IDocHostShowUI
interface.
- Open the Custsite.h file and add the following code under
public
just below CCustomControlSite(COleControlContainer *pCnt):COleControlSite(pCnt){}
: BEGIN_INTERFACE_PART(DocHostShowUI,
IDocHostShowUI)
INIT_INTERFACE_PART(CDocHostSite, DocHostShowUI)
STDMETHOD(ShowHelp)(
HWND hwnd,
LPOLESTR pszHelpFile,
UINT uCommand,
DWORD dwData,
POINT ptMouse,
IDispatch __RPC_FAR *pDispatchObjectHit);
STDMETHOD(ShowMessage)(
HWND hwnd,
LPOLESTR lpstrText,
LPOLESTR lpstrCaption,
DWORD dwType,
LPOLESTR lpstrHelpFile,
DWORD dwHelpContext,
LRESULT __RPC_FAR *plResult);
END_INTERFACE_PART(DocHostShowUI)
- Open the Custsite.cpp file and add the following line of code that is shown in bold.
BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite)
INTERFACE_PART(CCustomControlSite,
IID_IDocHostShowUI, DocHostShowUI)
INTERFACE_PART(CCustomControlSite,
IID_IDocHostUIHandler, DocHostUIHandler)
END_INTERFACE_MAP()
- Add the following code directly below the code shown above:
ULONG
CCustomControlSite::XDocHostShowUI::AddRef()
{
METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI);
return pThis->ExternalAddRef();
}
ULONG CCustomControlSite::XDocHostShowUI::Release()
{
METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI);
return pThis->ExternalRelease();
}
HRESULT
CCustomControlSite::XDocHostShowUI::QueryInterface
(REFIID riid, void ** ppvObj)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI);
return pThis->ExternalQueryInterface( &riid, ppvObj );
}
HRESULT CCustomControlSite::XDocHostShowUI::ShowHelp(
HWND hwnd,
LPOLESTR pszHelpFile,
UINT nCommand,
DWORD dwData,
POINT ptMouse,
IDispatch * pDispatchObjectHit)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI);
return S_OK;
}
HRESULT CCustomControlSite::XDocHostShowUI::ShowMessage(
HWND hwnd,
LPOLESTR lpstrText,
LPOLESTR lpstrCaption,
DWORD dwType,
LPOLESTR lpstrHelpFile,
DWORD dwHelpContext,
LRESULT * plResult)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI);
MessageBox(hwnd, (CString)lpstrText, "Custom Browser", dwType);
return S_OK;
}
Now we have finished implementing all the interfaces that we need in order to customize the browser to the extent we need to. Now all we have to do is write the code to actually do our customizations and we have a framework to use whenever we need to provide a custom web browser solution that requires advanced functionality.
First lets go into the Custsite.cpp file. In this file is where we can customize our message boxes. In other words every time someone calls the function alert('some text here');
from JavaScript they will see our custom message box. Find the function called ShowMessage
and inside that function we will make it display a critical error image instead of the standard warning exclamation icon just for an example:
HRESULT CCustomControlSite::XDocHostShowUI::ShowMessage(HWND hwnd,
LPOLESTR lpstrText,
LPOLESTR lpstrCaption,
DWORD dwType,
LPOLESTR lpstrHelpFile,
DWORD dwHelpContext,
LRESULT * plResult)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI);
MessageBox(hwnd, (CString)lpstrText, "Custom Browser",
MB_ICONERROR);
return S_OK;
}
If you want to, you can also trap common key combinations and cancel them or make them do different things. To disable some common key combinations go into the Custsite.cpp file and find the piece of code shown below:
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::TranslateAccelerator
(LPMSG lpMsg,
const GUID __RPC_FAR *pguidCmdGroup,
DWORD nCmdID)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
return S_FALSE;
}
Replace the code shown above with the following code below:
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::TranslateAccelerator(LPMSG lpMsg,
const GUID __RPC_FAR *pguidCmdGroup,
DWORD nCmdID)
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
if(lpMsg->message == WM_KEYDOWN &&
GetAsyncKeyState(VK_F5) < 0)
return S_OK;
if(GetKeyState(VK_CONTROL) & 0x8000)
{
if(lpMsg->message == WM_KEYDOWN &&
GetAsyncKeyState(0x4F) < 0)
return S_OK;
if(lpMsg->message == WM_KEYDOWN &&
GetAsyncKeyState(0x50) < 0)
return S_OK;
if(lpMsg->message == WM_KEYDOWN &&
GetAsyncKeyState(0x4E) < 0)
return S_OK;
}
if(lpMsg->wParam == VK_BACK)
return S_OK;
return S_FALSE;
}
You can also supply your own custom context menu instead of Internet Explorer's by putting code in the ShowContextMenu
handler as shown below:
HRESULT FAR EXPORT
CCustomControlSite::XDocHostUIHandler::ShowContextMenu(
DWORD ,
POINT* pptPosition,
IUnknown* ,
IDispatch* )
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler)
CMenu menu;
menu.LoadMenu(IDR_CUSTOM_POPUP);
CMenu* pSubMenu = menu.GetSubMenu(0);
pSubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,
pptPosition->x, pptPosition->y,
theApp.m_pMainWnd);
return S_OK;
}
Now the cool part. Lets go into the file Idispimp.cpp. This is where we can write code to allows us to call our C++ functions from JavaScript on the HTML pages, that get loaded into our custom browser window.
First we must add the following code to the top of our Idispimp.cpp file just below the includes - just below #include "idispimp.h"
. We need this so we can access functions in our main dialog:
#include "CustomBrowser.h"
#include "CustomBrowserDlg.h"
Go to the top of the file until you see the code shown below:
const WCHAR pszExtend[10]=L"xxyyzz";
#define DISPID_Extend 12345
Replace the block of code shown above with the code below:
CString cszCB_IsOurCustomBrowser = "CB_IsOurCustomBrowser";
CString cszCB_Close = "CB_Close";
CString cszCB_CustomFunction = "CB_CustomFunction";
CString cszCB_CustomFunctionWithParams = "CB_CustomFunctionWithParams";
CString cszCB_OpenWindow = "CB_OpenWindow";
CString cszCB_ShowModalDialog = "CB_ShowModalDialog";
CString cszCB_ShowModelessDialog = "CB_ShowModelessDialog";
#define DISPID_CB_IsOurCustomBrowser 1
#define DISPID_CB_Close 2
#define DISPID_CB_CustomFunction 3
#define DISPID_CB_CustomFunctionWithParams 4
#define DISPID_CB_OpenWindow 5
#define DISPID_CB_ShowModalDialog 6
#define DISPID_CB_ShowModelessDialog 7
Now we have to find the function GetIDsOfNames
and write some code so it can pass the proper ID to the invoke function which will allow us to determine which C++ call to make. So find the code shown below:
STDMETHODIMP CImpIDispatch::GetIDsOfNames(
REFIID riid,
OLECHAR** rgszNames,
UINT cNames,
LCID lcid,
DISPID* rgDispId)
{
HRESULT hr;
UINT i;
hr = NOERROR;
for ( i=0; i < cNames; i++)
{
if ( 2 == CompareString( lcid, NORM_IGNOREWIDTH, (char*)pszExtend,
3, (char*)rgszNames[i], 3 ) )
{
rgDispId[i] = DISPID_Extend;
}
else
{
hr = ResultFromScode(DISP_E_UNKNOWNNAME);
rgDispId[i] = DISPID_UNKNOWN;
}
}
return hr;
}
Replace the code above with this code shown below:
STDMETHODIMP CImpIDispatch::GetIDsOfNames(
REFIID riid,
OLECHAR** rgszNames,
UINT cNames,
LCID lcid,
DISPID* rgDispId)
{
HRESULT hr;
UINT i;
hr = NOERROR;
for ( i=0; i < cNames; i++) {
CString cszName = rgszNames[i];
if(cszName == cszCB_IsOurCustomBrowser)
{
rgDispId[i] = DISPID_CB_IsOurCustomBrowser;
}
else if(cszName == cszCB_Close)
{
rgDispId[i] = DISPID_CB_Close;
}
else if(cszName == cszCB_CustomFunction)
{
rgDispId[i] = DISPID_CB_CustomFunction;
}
else if(cszName == cszCB_CustomFunctionWithParams)
{
rgDispId[i] = DISPID_CB_CustomFunctionWithParams;
}
else if(cszName == cszCB_OpenWindow)
{
rgDispId[i] = DISPID_CB_OpenWindow;
}
else if(cszName == cszCB_ShowModalDialog)
{
rgDispId[i] = DISPID_CB_ShowModalDialog;
}
else if(cszName == cszCB_ShowModelessDialog)
{
rgDispId[i] = DISPID_CB_ShowModelessDialog;
}
else {
hr = ResultFromScode(DISP_E_UNKNOWNNAME);
rgDispId[i] = DISPID_UNKNOWN;
}
}
return hr;
}
Now lastly find the Invoke
function shown below:
STDMETHODIMP CImpIDispatch::Invoke(
DISPID dispIdMember,
REFIID ,
LCID ,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pVarResult,
EXCEPINFO* ,
UINT* puArgErr)
{
if ( dispIdMember == DISPID_Extend )
{
if ( wFlags & DISPATCH_PROPERTYGET )
{
if ( pVarResult != NULL )
{
WCHAR buff[10]=L"Wibble";
BSTR bstrRet = SysAllocString( buff );
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BSTR;
V_BSTR(pVarResult) = bstrRet;
}
}
}
return S_OK;
}
Replace the code above with this code shown below:
STDMETHODIMP CImpIDispatch::Invoke(
DISPID dispIdMember,
REFIID ,
LCID ,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pVarResult,
EXCEPINFO* ,
UINT* puArgErr)
{
CCustomBrowserDlg* pDlg = (CCustomBrowserDlg*) AfxGetMainWnd();
if(dispIdMember == DISPID_CB_IsOurCustomBrowser)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )
{
bool bResult = pDlg->CB_IsOurCustomBrowser();
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = bResult;
}
}
if(dispIdMember == DISPID_CB_Close)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )
{
pDlg->CB_Close();
}
}
if(dispIdMember == DISPID_CB_CustomFunction)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )
{
pDlg->CB_CustomFunction();
}
}
if(dispIdMember == DISPID_CB_CustomFunctionWithParams)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )
{
CString cszArg1= pDispParams->rgvarg[1].bstrVal;
int nArg2= pDispParams->rgvarg[0].intVal;
pDlg->CB_CustomFunctionWithParams(cszArg1, nArg2);
}
}
if(dispIdMember == DISPID_CB_OpenWindow)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )
{
CString cszArg1= pDispParams->rgvarg[5].bstrVal;
int nArg2= pDispParams->rgvarg[4].intVal;
int nArg3= pDispParams->rgvarg[3].intVal;
int nArg4= pDispParams->rgvarg[2].intVal;
int nArg5= pDispParams->rgvarg[1].intVal;
int nArg6 = pDispParams->rgvarg[0].intVal;
pDlg->CB_OpenWindow(cszArg1, nArg2,
nArg3, nArg4, nArg5, nArg6);
}
}
if(dispIdMember == DISPID_CB_ShowModelessDialog)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )
{
CString cszArg1= pDispParams->rgvarg[4].bstrVal;
int nArg2= pDispParams->rgvarg[3].intVal;
int nArg3= pDispParams->rgvarg[2].intVal;
int nArg4= pDispParams->rgvarg[1].intVal;
int nArg5= pDispParams->rgvarg[0].intVal;
pDlg->CB_ShowModelessDialog(cszArg1, nArg2,
nArg3, nArg4, nArg5);
}
}
if(dispIdMember == DISPID_CB_ShowModalDialog)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
}
if ( wFlags & DISPATCH_METHOD )
{
CString cszArg1= pDispParams->rgvarg[4].bstrVal;
int nArg2= pDispParams->rgvarg[3].intVal;
int nArg3= pDispParams->rgvarg[2].intVal;
int nArg4= pDispParams->rgvarg[1].intVal;
int nArg5= pDispParams->rgvarg[0].intVal;
pDlg->CB_ShowModalDialog(cszArg1, nArg2,
nArg3, nArg4, nArg5);
}
}
return S_OK;
}
Now we just need to add all these functions to our CCustomBrowserDlg
class. So go into the CustomBrowserDlg.h file and add the following public function declarations as shown below:
void CB_ShowModelessDialog(CString cszURL,
int nLeft, int nTop, int nWidth, int nHeight);
void CB_ShowModalDialog(CString cszURL,
int nLeft, int nTop, int nWidth, int nHeight);
void CB_OpenWindow(CString cszURL,
int nLeft, int nTop, int nWidth, int nHeight, int nResizable);
void CB_CustomFunctionWithParams(CString cszString, int nNumber);
void CB_CustomFunction();
void CB_Close();
BOOL CB_IsOurCustomBrowser();
Now go into the CustomBrowserDlg.cpp file and add the following functions as shown below:
Note: The functions shown below just show a message box with all the parameters passed in just for simplicity sake. In the demo project, there is actually code to open a window, modal dialog, and modeless dialog. I just didn't want to explain how to create windows, modal, and modeless dialog boxes, as it is out of scope for this article. Of course you can see how all this code actually works in the demo project.
void CCustomBrowserDlg::CB_Close()
{
AfxMessageBox("Close the browser here or the current window");
}
void CCustomBrowserDlg::CB_CustomFunction()
{
AfxMessageBox("Do whatever you like here!");
}
void CCustomBrowserDlg::CB_CustomFunctionWithParams
(CString cszString, int nNumber)
{
CString cszParameters;
cszParameters.Format
("parameter 1: %s\nparameter 2: %d", cszString, nNumber);
AfxMessageBox(cszParameters);
}
void CCustomBrowserDlg::CB_OpenWindow(CString cszURL,
int nLeft, int nTop, int
nWidth, int nHeight, int nResizable)
{
CString cszParameters;
cszParameters.Format("URL=%s LEFT=%d TOP=%d
WIDTH=%d HEIGHT=%d RESIZABLE=%d",
cszURL, nLeft, nTop, nWidth, nHeight, nResizable);
AfxMessageBox(cszParameters);
}
void CCustomBrowserDlg::CB_ShowModalDialog(CString cszURL,
int nLeft, int nTop,
int nWidth, int nHeight)
{
CString cszParameters;
cszParameters.Format("URL=%s LEFT=%d TOP=%d
WIDTH=%d HEIGHT=%d RESIZABLE=%d",
cszURL, nLeft, nTop, nWidth, nHeight);
AfxMessageBox(cszParameters);
}
void CCustomBrowserDlg::CB_ShowModelessDialog
(CString cszURL, int nLeft, int nTop,
int nWidth, int nHeight)
{
CString cszParameters;
cszParameters.Format("URL=%s LEFT=%d TOP=%d
WIDTH=%d HEIGHT=%d RESIZABLE=%d",
cszURL, nLeft, nTop, nWidth, nHeight);
AfxMessageBox(cszParameters);
}
All of these functions that we have added to our CCustomBrowserDlg
class are now accessible from JavaScript! as shown below. The code shown below is from the CustomTest.html file that is included in the sample project.
<script langauge="javascript">
function fnIsOurCustomBrowser()
{
if(window.external.CB_IsOurCustomBrowser!=null)
return true;
else
return false;
}
bIsCustomBrowser = fnIsOurCustomBrowser();
if(!bIsCustomBrowser)
{
alert('You must be using Custom Browser to
view this page properly.');
}
function fnCallFunction()
{
if(bIsCustomBrowser)
window.external.CB_CustomFunction();
}
function fnCallFunctionWithParams(strString, nNumber)
{
if(bIsCustomBrowser)
window.external.CB_CustomFunctionWithParams
(strString, nNumber);
}
function fnOpenWindow(strURL, nLeft, nTop,
nWidth, nHeight, nResizable)
{
if(bIsCustomBrowser)
window.external.CB_OpenWindow(strURL, nLeft,
nTop, nWidth, nHeight, nResizable);
}
function fnShowModalDialog(strURL, nLeft,
nTop, nWidth, nHeight)
{
if(bIsCustomBrowser)
window.external.CB_ShowModalDialog(strURL, nLeft,
nTop, nWidth, nHeight);
}
function fnShowModelessDialog(strURL,
nLeft, nTop, nWidth, nHeight)
{
if(bIsCustomBrowser)
window.external.CB_ShowModelessDialog(strURL, nLeft,
nTop, nWidth, nHeight);
}
</script>
Points of interest
- Creating a custom web browser that exhibits all of the functionality shown in this article is not as straight forward as one would think.
- Once you have implemented the interfaces for your web browser control in your dialog based application, you have a pretty good framework to use for any custom browser solutions you need to provide.
- I had to research for quite a few hours before I even found out what was necessary in order to provide the ability to call a C++ function from JavaScript.
Known problems
- If you are having problems compiling the source code, then you will have to install the Internet Development SDK portion of the Platform SDK from Microsoft. Even if you already think you have installed the latest platform SDK, just install the Internet Development SDK again. This should fix the problem.
Credits