vbMHWB ActiveX Control - v1.2.1.6

SourceForge.net Logo

Introduction
Background
Implementation challenges:
    Events
    _ATL_MIN_CRT
   Asynchronous Pluggable Protocols
   RegisterBindStatusCallback and Content-Disposition header
Brief Overview of Classes
Setting up the control
About Demo Applications
Control:
   Properties
   Events
   Methods
Related Documents
History
Downloads
C# Version!
Contact

Introduction

The goal of this project is to replace web browser wrapper control with one that allows developers to create and use a web browser control having total control over GUI, context menus, accelerator keys, downloads, security, ... without using any sub classing, registry or hacks. The result is an ATL control:

Even though, this control was made to be used by VB, but due to the fact that it is a Fully compliant ActiveX control, it can also be used from MFC, .NET, ...

Background

The control is written in VC++ 6.0 using ATL3.0. It is compiled with minimum dependencies (no MFC, std::, CString, ...). It is designed to host multiple WebBrowser controls within one ATL created window. This is contradictory to MSDN recommendation which suggests to use one ATL window per hosted control. The reason for choosing this approach was to remove burden of Webbrowser control management from the hosting client application to the control. Normally, a developer places an instance of a control on a form/dlg, then if needed, an array of the controls is created and maintained by client application. My approach enables the developer to insert one instance of this control on a form/dlg and then use CvbWB::AddBrowser and CvbWB::RemoveBrowser methods to add and remove Webbrowser controls. Each newly created control (through an instance of IWB class) is assigned a unique id (wbUID). This unique id enables the client application to communicate with that specific Webbrowser control instance via it's properties, methods, and to find out which Webbrowser control has fired an event.

Index

Implementation challenges

There are many articles that cover the basics of creating, hosting and sinking events of a Webbrowser control. So rather than going through CoCreateInstance, IOleObject::SetClientSite, and so on. I decided to explain some of the main implementation challenges where you will find very little and often no information about them.

Here is a list of main challenges encountered and resolved during development of this control. I neither claim that these solutions are unique nor the best. Just that they seem to work.

Events

1) The first issue that I encountered was lack of documentation on how to pass byref parameter or objects to a client application such as VB. Attempts to handle any of these wizard generated events was causing GPF. Here is a sample of the none working code for NewWindow3 event taken from CProxy_IvbWBEvents class:

	VOID Fire_NewWindow3(SHORT wbUID, IDispatch * * ppDisp, VARIANT_BOOL * Cancel, LONG lFlags, BSTR sURLContext, BSTR sURL)
	{
		T* pT = static_cast(this);
		int nConnectionIndex;
		CComVariant* pvars = new CComVariant[6];
		int nConnections = m_vec.GetSize();
		
		for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
		{
			pT->Lock();
			CComPtr sp = m_vec.GetAt(nConnectionIndex);
			pT->Unlock();

			IDispatch* pDispatch = reinterpret_cast(sp.p);
			if (pDispatch != NULL)
			{
				pvars[5] = wbUID;
				
				/////////////////////////////////////////////////////////////
				//Next two params need to be passed by ref or they cause GPF
				//
				pvars[4] = ppDisp;
				pvars[3] = Cancel;
				/////////////////////////////////////////////////////////////
				
				pvars[2] = lFlags;
				pvars[1] = sURLContext;
				pvars[0] = sURL;
				
				DISPPARAMS disp = { pvars, NULL, 6, 0 };
				pDispatch->Invoke(0x2d, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
			}
		}
	}

And here is the correction:

				pvars[4].vt = VT_BYREF|VT_DISPATCH;
				pvars[4].byref = ppDisp;
				
				pvars[3].vt = VT_BOOL|VT_BYREF;
				pvars[3].byref = Cancel;

2) The second issue that I encountered was again related to events. This issue showed up when I implemented the protocol handlers. Apparently, the IConnectionPointImpl does not fire events across COM components. So, after some looking around, I came across KB article 280512, ATLCPImplMT encapsulates ATL event firing across COM apartments. Using included class IConnectionPointImplMT (From MS) solved this issue. And here is the completed code for Newwindow3 event:

	VOID Fire_NewWindow3(SHORT wbUID, IDispatch * * ppDisp, VARIANT_BOOL * Cancel, LONG lFlags, BSTR sURLContext, BSTR sURL)
	{
		T* pT = static_cast(this);
		int nConnectionIndex;
		CComVariant* pvars = new CComVariant[6];
		int nConnections = m_vec.GetSize();
		
		for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
		{
			//////////////////////////////////////////////////////////////////////
			//Next three lines need to be replaced
			//pT->Lock();
			//CComPtr sp = m_vec.GetAt(nConnectionIndex);
			//pT->Unlock();
			/////////////////////
			
			//////////////////////////////////////////////////////////////////////
			//Replaced the previous three lines of code with the next two lines
			CComPtr sp;
			sp.Attach (GetInterfaceAt(nConnectionIndex));
			/////////////////////

			IDispatch* pDispatch = reinterpret_cast(sp.p);
			if (pDispatch != NULL)
			{
				pvars[5] = wbUID;

				pvars[4].vt = VT_BYREF|VT_DISPATCH;
				pvars[4].byref = ppDisp;
				
				pvars[3].vt = VT_BOOL|VT_BYREF;
				pvars[3].byref = Cancel;

				pvars[2] = lFlags;
				pvars[1] = sURLContext;
				pvars[0] = sURL;
				
				DISPPARAMS disp = { pvars, NULL, 6, 0 };
				pDispatch->Invoke(0x2d, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
			}
		}
	}

3) The third issue showed up after implementing protocol hanlder. I needed to fire events from the WBPassthruSink (protocol handler sink) class for a specific Webbrowser control to notify the client application via ProtocolHandlerOnBeginTransaction and ProtocolHandlerOnResponse events. Since the instances of the WBPassthruSink class are created by URLMon as needed by PassthroughAPP, I had to find a way to determine which instance of the Webbrowser control is involved so I can fire events for that specific control. My solution was to find the Internet Explorer Server HWND in the implementation of WBPassthruSink::OnStart using IWindowForBindingUI interface Obtained from our protocol handler.

	//Using IWindowForBindingUI interface
	CComPtr objWindowForBindingUI;
	//This is a macro for QueryService
	HRESULT hret = QueryServiceFromClient(&objWindowForBindingUI);
	if( (SUCCEEDED(hret)) && (objWindowForBindingUI) )
	{
		HWND hwndIEServer = NULL;
		//Should return InternetExplorerServer HWND
		objWindowForBindingUI->GetWindow(IID_IHttpSecurity, &hwndIEServer);
		//From here we can find the ATL window hosting this instance of our control
		//and have it fire an event for the form/dlg hosting this instance of our control
		if(hwndIEServer)

Index

_ATL_MIN_CRT

One of the design goals of this control was to be build with minimum dependencies. Standard _ATL_MIN_CRT support does the job for eliminating the CRT overhead well. But unfortunately, it doesn't support the use of global C++ constructs, like the following:

class CTest {
public:
  CTest() { 
    MessageBox(NULL, _T("Hello, I'm intitialized"), 
                     _T("Static object"), MB_SETFOREGROUND | MB_OK);
  }
  ~CTest() { 
    MessageBox(NULL, _T("Bye, I'm done"), _T("Static object"), 
                     MB_SETFOREGROUND | MB_OK);
  }
};

static CTest g_test;
extern CTest *gp_Test;

The above would lead to linker conflicts, because CRT code for constructors/destructors invocation would be referenced. To overcome this, I am using AuxCrt.cpp custom _ATL_MIN_CRT implementation and a replacement for AtlImpl.cpp class. This class is from Andrew Nosenko (andien@geocities.com). http://www.geocities.com/~andien/atlaux.htm. With this class, I was able to use CSimpleArray as a global variable to keep track of instances of Webbrowser controls. Note, for convenience, I placed a copy of AuxCrt.cpp in the ATL\Include\ directory.

//Taken from StdAfx.h

//gCtrlInstances keeps track of each instance of our control
//This global is needed due to the fact that a client may place
//this control on more than one form/dlg or have multiple instances of BW
//hosting in one control. In this case, using one
//global ptr to our control will cause the events to be routed to the
//first control, not the one we want. The control instances (this) is
//added to this array in Constructor and removed in Destructor of CvbWB class.

extern CSimpleArray<void*> gCtrlInstances;

//Flag to track registering and unregistering of HTTP/HTTPS protocols
//Can only be done once per DLL load. Effects all instances of Webbrowser control.
extern BOOL gb_IsHttpRegistered;
extern BOOL gb_IsHttpsRegistered;
//Protocol handling registration
extern CComPtr m_spCFHTTP;
extern CComPtr m_spCFHTTPS;
....

Index

Asynchronous Pluggable Protocols

One of the my main design goals was to be able to act as a pass through between Webbrowser control and URLMon so as to intercept all the requests and responses by using a asynchronous pluggable protocol. At the start, this task seemed pretty straightforward, implement IInternetProtocol, IInternetProtocolInfo, IInternetPriority, IInternetProtocolSink, IInternetBindInfo, IClassFactory interfaces. In the implementation of the IServiceProvider::QueryService, create and pass an instance of my IInternetProtocolImpl to URLMon. Overwrite necessary methods and handle the requests. Unfortunately, this approach had a big flaw, My IInternetProtocol implementation was only being called to handle the main document and not for the rest of the page requests, images, css,...

After doing some searching, I came across an excellent package called PassthroughAPP by Igor Tandetnik. In google groups under microsoft.public.inetsdk.programming.xxx, just search for PassthroughAPP. The package contains 5 files.

PassthroughObject.h A simple COM object. IPassthroughObject
ProtocolCF.h A customized COM class factory. Implements CComClassFactory
ProtocolCF.inl Class factory implementation
ProtocolImpl.h

Protocol handlers header. Implemented interfaces,
IPassthroughObject
IInternetProtocol
IInternetProtocolInfo
IInternetPriority
IInternetThreadSwitch
IWinInetHttpInfo
IInternetProtocolSink
IServiceProvider
IInternetBindInfo

ProtocolImpl.inl Protocol handlers implementation

Except for the five methods that I have overridden in WBPassthruSink class to intercept all the requests and responds, I will not be able to answer any questions regarding PassthroughAPP.  Please direct your questions to the author as my understanding of package's design and implementation is limited.

Index

RegisterBindStatusCallback and Content-Disposition header

One of the design goals of this control was to take full control over file downloads. This was pretty simple to implement at first and all seemed to work without a glitch. But as usual, a strange problem was reported. If a user attempted to download an attachment from Hotmail, Yahoo, ... the default download dialog was being displayed, bypassing my custom download manager. I traced the problem to RegisterBindStatusCallback method which is called in IDownloadManager::Download method, it was returning E_FAIL. This failure seems to occur when a server sends a Content-Disposition header in response to a file download request. Of course, MSDN does not even mention anything about an E_FAIL return or why RegisterBindStatusCallback may fail. After searching for a while to no avail, I decided to attempt to implement a work around.

First step was to somehow force RegisterBindStatusCallback to succeed.

	//Taken from WBDownLoadManager::Download
	//filedl is an instance of my IBindStatusCallback implementation
	//pbc is a BindCtx pointer passed to Download method
	
	IBindStatusCallback *pPrevBSCB = NULL;
	hr = RegisterBindStatusCallback(pbc, 
			reinterpret_cast(filedl), &pPrevBSCB, 0L);

	if( (FAILED(hr)) && (pPrevBSCB) )
	{
		//RevokeObjectParam for current BSCB, so we can register our BSCB
		//_BSCB_Holder_ is the key used to register a callback with a specific BindCtX
		LPOLESTR oParam = L"_BSCB_Holder_";
		hr = pbc->RevokeObjectParam(oParam);
		if(SUCCEEDED(hr))
		{
			//Attempt register again, should succeed now
			hr = RegisterBindStatusCallback(pbc, 
					reinterpret_cast(filedl), 0, 0L);
			if(SUCCEEDED(hr))
			{
				//Need to pass a pointer for BindCtx and previous BSCB to our implementation
				filedl->m_pPrevBSCB = pPrevBSCB;
				filedl->AddRef();
				pPrevBSCB->AddRef();
				filedl->m_pBindCtx = pbc;
				pbc->AddRef();
			}
	//....

Second step was to relay some calls to the previous IBindStatusCallback from our implementation. Otherwise no download will take place. Memory leaks and crashes to be expected. This part was based on trial and error.

	if(m_pPrevBSCB)
	{
		m_pPrevBSCB->OnStopBinding(HTTP_STATUS_OK, NULL);
	}
	if(m_pPrevBSCB)
	{
		//Need to do this otherwise a filedownload dlg will be displayed
		//as we are downloading the file.
		if(ulStatusCode == BINDSTATUS_CONTENTDISPOSITIONATTACH)
			return S_OK;
		m_pPrevBSCB->OnProgress(ulProgress, ulProgressMax, ulStatusCode, szStatusText);
	}
	if( (m_pPrevBSCB) && (m_pBindCtx) )
	{
		//Register PrevBSCB and release our pointers
		LPOLESTR oParam = L"_BSCB_Holder_";
		m_pBindCtx->RegisterObjectParam(oParam, 
					reinterpret_cast(m_pPrevBSCB));
		m_pPrevBSCB->Release();
		m_pPrevBSCB = NULL;
		m_pBindCtx->Release();
		m_pBindCtx = NULL;
		//Decrease our ref count, so when release is called
		//we delete this object
		--m_cRef;
	}

Index

Brief Overview of Classes

Please ensure that you have the latest SDK that works with VC++ 6.0 , February 2003 SDK.

Class Name Implements Description
CvbWB CComObjectRootEx
IDispatchImpl
CComControl
IPersistStreamInitImpl
IOleControlImpl
IOleObjectImpl
IOleInPlaceActiveObjectImpl
IViewObjectExImpl
IOleInPlaceObjectWindowlessImpl
ISupportErrorInfo
IConnectionPointContainerImpl
IPersistStorageImpl
ISpecifyPropertyPagesImpl
IQuickActivateImpl
IDataObjectImpl
IProvideClassInfo2Impl
IPropertyNotifySinkCP
CComCoClass
CProxy_IvbWBEvents
This class was created as a Full ATL control using the wizard. It is responsible to host the control in a client application (VB, C++), fire events, allow access to properties and methods of all WebBrowser controls to the hosting client. This task is achieved by using a simple array of IWB pointers. The pointers are added and removed from array in calls to AddBrowser and RemoveBrowser methods. Each new instance of IWB is given a unique id wbUID. The client application uses this id to access this instance of Webbrowser control's properties and methods. Also, all events come with an extra parammeter, wbUID, which identifies the Webbrowser instance that has fired the event. In addition, This class contains a number of useful methods and properties, get_ActiveDocumentObj get_ActiveElementObj (Returns Active document or element. Accounts for frames), DownloadUrlAsync (Starts a file download, allowing client app to monitor status via OnFileDL_xxx events), ucInternetCrackUrl (To break a given URL into parts including filename and ext. Parts are accessed via Get/Set ucXXX properties),...
IWB IUnknown This class is responsible to create and maintain a single instance of a Webbrowser  control along with all the necessary classes such as WBClientSite (IOleClientSite implementation). Also, all QIs from all the classes are routed through the same IWB instance that created them. In addition, it contains a number of useful methods, IsFrameset, FramesCount , DocHighlightFindText,...
WBClientSite IOleClientSite Required as part of Webbrowser control hosting interfaces. All methods return E_NOTIMPL.
WBInPlaceSite IOleInplaceSite Required as part of Webbrowser control hosting interfaces. All methods return E_NOTIMPL.
WBEventDispatch IDispatch This class is the sink for Webbrowser control events. As the events arrive from Webbrowser control, it fires events to notify the client application.
WBDocHostShowUI IDocHostShowUI I am only handling ::ShowMessage method which is responsible to intercept HTTP messages and notifies the client via Fire_ShowMessage event to determine what to do with the message. A typical message may look like this:
Your current security settings prohibit running ActiveX controls on this page. As a result, the page may not display correctly.
WBOleCommandTarget IOleCommandTarget The purpose of this class is to intercept Script errors via ::Exec method, and based on the m_lScriptError flag, either allow or disallow them.
WBAuthenticate IAuthenticate This class intercepts requests for basic authentication from servers, notifies the client using OnAuthentication event to obtain username and password. Useful for clients wanting to automate the process of logging in using basic authentication schemes.
WBDocHostUIHandler IDocHostUIHandler The purpose of this class is to intercept context menu (::ShowContextMenu), accelerator keys (::TranslateAccelerator), and to set the UI flags (::GetHostInfo).
WBHttpSecurity IHttpSecurity This class intercepts HTTP related security problems, such as ERROR_HTTP_REDIRECT_NEEDS_CONFIRMATION, ERROR_INTERNET_SEC_CERT_CN_INVALID via OnSecurityProblem method. Please note, using ::OnSecurityProblem method or OnHTTPSecurityProblem event incorrectly can compromise the security of your application and potentially leave users of your application exposed to unwanted information disclosure.
WBSecurityManager IInternetSecurityManager This class only implemets ::ProcessUrlAction method. It returns INET_E_DEFAULT_ACTION for the rest of the methods. Please note, using ::ProcessUrlAction method or SecurityManagerProcessUrlAction event incorrectly may result in the incorrect processing of URL actions and possibly leave users susceptible to elevation of privilege attacks.
WBServiceProvider IServiceProvider It is responsible to respond to QueryService calls on our IUknown(IWB) in ::QueryService method.
WBWindowForBindingUI IWindowForBindingUI It returns a hanlde to a window via ::GetWindow method which is used by MSHTML to display information in the client's user interface when necessary. Currently, this method returns a hanlde to the Internet Explorer Server window.
WBBSCBFileDL IBindStatusCallback
IHttpNegotiate
An instance of this class is created and used to receive callbacks and notify client app via OnFileDLxxxx events for all file downloads. By user clicking on a download link or by using ::DownloadUrlAsync method from code.
WBDownLoadManager IDownloadManager It implements ::Download method which in turn creates an instance of our IBindStatusCallback implementation (WBBSCBFileDL), registers our BSCB for callbacks and notifies the client via OnFileDLxxxx events of the progress of the download. Each BSCB is given a Unique ID and a pointer to it is stored in a simple array in the CvbWB class instance. This ID can be used by client app to cancel a download by calling CancelFileDl passing the id.
CTmpBuffer   A simple string buffer class, since CString is not available due to minimum dependency requirement.
CUrlParts   A simple class which uses InternetCrackUrl method of WinInet to break a given URL into it's parts. This includes file name and ext, if available.
WBPassThruSink CInternetProtocolSinkWithSP
IHttpNegotiate
This class is the sink for the protocol handlers.
WBDropTarget IDropTarget To handle custom dragdrop.
WBDocHostUIHandler IDocHostUIHandler2 To handle GetOverrideKeyPath method.
WBStream IStream To handle uploads with progress.

Index

Setting up the control

How to register example, (assuming system dir path is 'C:\windows\system32\') regsvr32.exe C:\windows\system32\vbMHWB.dll.

About Demo Applications

VBDemo and MFCDemo:

Both demo projects are almost identical in terms of GUI and their use of the control. The VBDemo was build using Visual Basic 6.0 and has no dependencies other than this control. The MFCDemo project was build using Visual C++ 6.0 and also has no other dependencies. With MFCDemo, you need to treat BOOL as VARIANT_BOOL (Wizard translates VARIANT_BOOL as BOOL), and make sure that value of an in/out BSTR parameter is released (ClearBSTRPtr method is provided as an example) before assigning a new value. This step is necessary to avoid memory leaks.

VBDemoEX:

Note: this demo is not intended for novice programmers and should be viewed as a learning and experimental tool. Expect bugs!

This project was build using Visual Basic 6.0. It does depend on an external type library WinApiForVb.tlb. This library was put together by me a while back to avoid copying and pasting API declarations in every new VB project. It consists of 950 KB of API declarations (ANSI+UNICODE). The source and the compiled versions of the WinApiForVb.tlb have been provided in WinApiForVb-Source and WinApiForVb_Compiled sub folders.

Build versions have been included in Binaries sub folder for all projects.

Index

Properties

Index

Events

Index

Methods

Note* Wrappers for OleCommandTarget->Exec, using various OLECMDID_xxx IDs. Has the same effect as using IE commands.

Index

Related Document

WebBrowser Customization
Webbrowser Control Reference for C/C++ Developers
Advanced Hosting Reference
How To Get Protocol Headers in a Pluggable Protocol Handler
SAMPLE: ATLCPImplMT encapsulates ATL event firing across COM apartments
How to handle script errors as a WebBrowser control host
About Asynchronous Pluggable Protocols
URL Monikers Interfaces
HTML Control API Interfaces
Implementing a Custom Download Manager
MSHTML Reference
IInternetSecurityManager
Programming with ATL and C Run-Time Code

History

Index

Downloads

C# Version

A limited functionality C# version of this control is available. These limitations are due to issues with COM threading mechanism and interop.

Contact

mehr13@hotmail.com