Asynchronous Pluggable Protocol Implementation with ATL


Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame


Like most people who contribute, I think it's a part of wanting to contribute something back into the system. However, I've always felt that perhaps I'm not worthy being a mere social programmer rather than by profession. Anyhow, a caveat, I'm self taught and I don't profess to be an expert, however I do like to find out how things work, and this article is related to some of my findings regarding Asynchronous Pluggable Protocols (APPs). I came up with an idea for a piece of code whereby I could create some functionality that would sit between Internet Explorer and the HTTP protocol such that I could monitor and audit the traffic. After much research I discovered the Asynchronous Pluggable Protocol technology that allows one to create a protocol scheme (eg, much like http:, file:, etc) and that the architecture was extensible and flexible enough to introduce new schemes. To gain some exposure to the architecture I decided to create a simple protocol scheme (app:) that would retrieve an HTTP page. The code samples you will see merely demonstrate a simple request using WinInet (not MFC) and using callbacks for asynchronous operation, the program will not stand on it's own and do much useful. There is hardly any error handling. </exit excuse mode>

The Registry

If you look under HKEY_CLASSES_ROOT/PROTOCOLS/Handler you'll see a very familiar list of protocol schemes, eg http:, file:, local:, mailto:. Under each of these keys is a STRING value with the name of CLSID. For HKEY_CLASSES_ROOT/PROTOCOLS/Handler/http/CLSID this is {79eac9e2-baf9-11ce-8c82-00aa004ba90b}. So, right now you're probably thinking is it that easy to create new schemes, well, fundamentally, the answer is yes. Create a new COM object (more on it's interfaces soon), create a new key under the Handler hierarchy and create CLSID STRING value and off you go.

The Interfaces

So, what of interfaces, well, the COM object needs to support IInternetProtocol, this is mandatory. Others are optional and I'll talk about those later. IInternetProtocol is inherited from IInternetProtocolRoot and together these interfaces have a number of methods that must be implemented in the Pluggable protocol. They are:

// IInternetProtocolRoot

Start( LPCWSTR szUrl, IInternetProtocolSink *pOIProtSink, IInternetBindInfo *pOIBindInfo, DWORD grfPI, DWORD dwReserved);
Continue( PROTOCOLDATA *pProtocolData);
Abort( HRESULT hrReason, DWORD dwOptions);
Terminate(DWORD dwOptions);
Suspend( void);
Resume( void);

// IInternetProtocol based on IInternetProtocolRoot

Read( void *pv, ULONG cb, ULONG *pcbRead);
Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition);
LockRequest(DWORD dwOptions);
UnlockRequest( void);

For the purposes of this article, the only real important ones are IInternetProtocolRoot::Start () and IInternetProtocol::Read. Both Suspend () and Resume () are not implemented and should return E_NOTIMPL. One of the optional interfaces is IInternetProtocolInfo, this is provided should you wish to process URL strings and break them up. I have not provided this, thus Internet Explorer will provide it's own processing.

Interface Definition - IDL

The IDL is relatively simple, no need to expose any dual interfaces, only IUnknown is required, then the client (Internet Explorer) can go querying for other mandatory/option interfaces.

import "oaidl.idl";
import "ocidl.idl";
	helpstring("Pluggable 1.0 Type Library")
library PLUGGABLELib

		helpstring("HttpFilter Class")
	coclass HttpFilter
		interface IUnknown;

Requesting the data

Essentially, when Internet Explorer sees the protocol scheme name and an URL following, it will create the object (using the CLSID from the registry) and pass information to the Start () method. The first parameter is simple, this is the URL, including the scheme name, eg app:. The second parameter is a pointer to an interface, IInternetProtocolSink. This interface is provided by the client of an Asynchronous Pluggable Protocol such that the handler can notify the client of any events, eg Data Available. The rest of the parameters are trivial and not important for this particular example. So, we have the URL and need to start an HTTP transfer. First thing that must be done is to save the ProtocolSink pointer:

// Save pointers, we'll need them later
m_pProtocolSink = pOIProtSink;

As we'll be using the WinInet functions, we need to first get a handle such that we can perform some I/O functions, we do this by using the InternetOpen () function. Note that we specify the INTERNET_FLAG_ASYNC option, because we do not want operations to go blocked and hold up Internet Explorer.

// Get handle for internet I/O
m_hInternetSession = InternetOpen (AgentName, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, INTERNET_FLAG_ASYNC);
if (!m_hInternetSession) {
	ATLTRACE(_T("InternetOpen () failed\n"));
	return S_FALSE;
	ATLTRACE(_T("InternetOpen () succeeded, handle is 0x%lX\n"), m_hInternetSession);


Now that we have a handle to the 'internet' we can start doing something useful. The first thing you might do is manipulate the URL in some way so that you construct a new one, for the purposes of the example, I'll just use a canned URL, but before we do, we need to establish a callback. This is done thus.

InternetSetStatusCallback (m_hInternetSession, CHttpFilter::StatusCallback);

Note that the StatusCallback () method is a static function as it can't be used in the context of a class/object. However, now we are going to actually start some real work, this takes the form of opening an URL (in asynchronous mode). The very last parameter to this call is passing a user specified "context" DWORD. I've chosen to use our object pointer (this) so that the callback method can access some member variables.

#define CANNED_URL "http://www.corporate.com/default.htm"
InternetOpenUrl(m_hInternetSession, CANNED_URL, NULL, 0, INTERNET_FLAG_RELOAD, (DWORD) this);
return S_OK;

If we weren't operating in an asynchronous environment, we would have expected InternetOpenUrl () to immediately pass back a handle, however, our callback will communicate any handles.

The Callback

That's it, we now need to sit back and wait for those callbacks. As I said previously, the "context" DWORD passed is our object pointer, so now, in the callback:

CHttpFilter::StatusCallback (HINTERNET hSession, DWORD Context, DWORD Status, LPVOID pInformation, DWORD InfoLength)
	CHttpFilter *	pClass = (CHttpFilter *) Context;

we work it back the other way. A lot of the calls to this callback reflect the status so-far, so you get a lot of "resolving hostname", "host contacted", "sending request", type messages. However, some of the messages are (obviously) important and contain some sort of information, this information is communicated through the pInformation and InfoLength parameters. The first one we are interested in is the handle that needs to be returned from the initial InternetOpenUrl () call. By using a switch statement on the Status parameter we catch the appropriate status:

	pClass -> m_hHttpSession = * (HINTERNET *) pInformation;	// Copy to member variable

The handle is stored for future use. We now wait for WinInet to retrieve the file, when it arrives we'll receive the INTERNET_STATUS_REQUEST_COMPLETE status. At that point we then call back in to our client (Internet Explorer) through it's IInternetProtocolSink interface through the ReportData () method. This method allows you to report intermediate progress. The 100, 100 reflects the fact that 100% of the 100% is complete and that the client should request data.

	// Tell sink that we have the data
	pClass -> m_pProtocolSink -> ReportData (BSCF_DATAFULLYAVAILABLE, 100, 100);

We now wait for the IInternetProtocolSink client, Internet Explorer, to call back into us and request the data. This is achieved by implementing the Read () method. Remember that the handle used by InternetRead () is the one that we received in the callback and saved away in a member variable.

CHttpFilter::Read(void *pv, ULONG cb, ULONG *pcbRead)
	BOOL	Status;

	Status = InternetReadFile (m_hHttpSession, pv, cb, pcbRead);

	ATLTRACE(_T("nBuffer is %ld\n"), *pcbRead);

	if (Status == FALSE) {
		DWORD error = GetLastError();
		ATLTRACE(_T("Errno = %d\n"), error);
		if (*pcbRead == cb) {	// more to read ?
			return S_OK;

	return S_FALSE;	

The documentation for IInternetProtocol::Read () states that the client may call the method several times after having read to end of file, in which case you must return S_FALSE. Apart from cleaning up by closing some handles (via InternetCloseHandle ()) that's about it.

Final Note

As a small exercise, this Asynchronous Pluggable Protocol was quite informative, in particular, just watching ATLTRACE statements flashing by is an eye opener. My project took a different turn shortly afterwards.The thinking was that "Heck, Microsoft have done all the hard work in there HTTP Asynchronous Pluggable Protocol, why not use that?". So, I've started down that path. Now Internet Explorer calls my Pluggable protocol, and I call the real HTTP Pluggable protocol pretending to be Internet Explorer (you only have to implement IInternetProtocolSink). However, I soon found that I became unstuck in that Internet Explorer seems to special case the HTTP Asynchronous Pluggable Protocol and made it non-Pluggable. Additionally, I ended up implementing the URL processing interface (IInternetProtocolInfo) and again used the WinInet functions to do the hard work. However, HTTP aside, it does make the mind think, what if I could create a ???? Pluggable Protocol, eg a deciphering one (pgp:), or a database accessing one (sql:). Have fun....

Any feedback gratefully received.

Reference Material

Asynchronous Pluggable Protocols Reference
Asynchronous Pluggable Protocols Overview
EtcProtocol Demonstrates Pluggable Protocol Handler (From KnowledgeBase: never got this sample to work in VC5 or 6)


Download demo project - 11 KB



  • There are no comments yet. Be the first to comment!

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date