Streaming HTML content into a webbrowser control from VB

From HashVB
Revision as of 21:52, 23 November 2006 by Wakjah (Talk | contribs)

Jump to: navigation, search

One of the many useful tools available to you as a VB developer is the WebBrowser control from Microsoft. This control forms the platform on top of which the entirety of Internet Explorer is built. Clearly, this is a powerful resource, offering you the ability to deliver rich content and a highly satisfying experience for your users. Unfortunately, it is impossible to directly put HTML content into a WebBrowser control from VB. There are many available techniques, such as caching your HTML on disk and using the Navigate() methods to open the page, or setting document.outerHTML from the WebBrowser's DOM. However, these methods are wholly unsatisfactory, and leave much to be desired. There has do be a better way, does there not?

The answer is yes, there does, and there is. As I said, it is impossible to do this from VB. However, if you have a C++ compiler (there are many available free from ... well, everywhere) and a couple of minutes on your hands, it is perfectly simple to create a small DLL that will allow you to stream your HTML content into the WebBrowser via an interface called IPersistStreamInit, that it implemented by the IHTMLDocument interface in order to give users the ability to stream content in and out (huzzah!).

The following code is somewhat outside the scope of this wiki, and so little explanation will be given for the code other than the comments within it. In fact no explanation will be given other than that. So here it is.

This code is from the file 'WebBrowserUtilVB.cpp':

   #include "stdafx.h"
   
   BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                       )
   {
       return TRUE;
   }
   
   bool __stdcall StreamIn(IDispatch *pDisp, const wchar_t *szData, size_t len)
   {
       bool bRet = false;
   
       CoInitialize(NULL);
   
       IPersistStreamInit *pPersistStreamInit;
       // Query for IPersistStreamInit
       HRESULT hr = pDisp->QueryInterface(IID_IPersistStreamInit, (void**)&pPersistStreamInit);
       if (SUCCEEDED(hr))
       {
           // Try to initialize the new document
           hr = pPersistStreamInit->InitNew();
           if (SUCCEEDED(hr))
           {
               // Allocate global memory to copy the string into
               HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, len * sizeof(wchar_t));
               if (hGlob)
               {
                   // Lock the memory
                   void *pData = GlobalLock(hGlob);
   
                   if (pData)
                   {
                       // Copy string into global memory
                       wcsncpy((wchar_t*)pData, szData, len);
   
                       IStream *pStream = NULL;
                       // Create a stream object from the global memory
                       hr = CreateStreamOnHGlobal(hGlob, TRUE, &pStream);
                       if (SUCCEEDED(hr))
                       {
                           // Load the stream into the webbrowser
                           pPersistStreamInit->Load(pStream);
                           pStream->Release();
                           
                           // Succeeded!
                           bRet = true;
                       }
   
                       // Clean up
                       GlobalUnlock(hGlob);
                       GlobalFree(hGlob);
                   }
               }
           }
   
           pPersistStreamInit->Release();
       }
   
       CoUninitialize();
   
       return bRet;
   }

This code is from 'stdafx.h':

   #pragma once
   
   
   #define WIN32_LEAN_AND_MEAN     // Exclude rarely-used stuff from Windows headers
   // Windows Header Files:
   #include <windows.h>
   
   #include <mshtml.h>
   
   
   bool __stdcall StreamIn(IDispatch *pDisp, const wchar_t *szData, size_t len);


This code is from the exports file 'exports.def':

   LIBRARY WebBrowserUtilVB
   
   EXPORTS
   StreamIn

When you compile this with VC7.1 in the release build configuration, it spits out a DLL of around 40kb, that should be included with your VB application.

So how do you use this code from VB? The following example shows you exactly what to do:

   Option Explicit
   
   Private Declare Function WebBrowser_StreamIn Lib "WebBrowserUtilVB.dll" Alias "StreamIn" (ByVal pDisp As Long, ByVal szData As Long, ByVal slen As Long) As Boolean
   
   Dim bStreamIn As Boolean
   
   Private Sub Form_Load()
       bStreamIn = True
       WebBrowser1.Navigate "about:blank"
   End Sub
   
   Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant)
       If (bStreamIn) Then
           Dim sHtml As String
           
           sHtml = "<html><body>test!!<img src=""http://www.google.co.uk/intl/en_uk/images/logo.gif""></body></html>"
           WebBrowser_StreamIn ObjPtr(WebBrowser1.Document), StrPtr(sHtml), Len(sHtml)
           
           bStreamIn = False
       End If
   End Sub

A little explanation: On the form is a WebBrowser control called 'WebBrowser1' (in case you didn't get that...). When the form loads, we tell the browser to navigate to "about:blank". The reasoning for this is that, if, when you try to stream data into the WebBrowser, there is no document already loaded, the program will simply crash. And we don't want that now, do we? Since it's a pain to mess around with trying to pass pointers from VB to C++ other than just using the *Ptr functions (see Pointers and CopyMemory for an explanation of how to use these mystical functions), I have just passed the pointers byval as long. One additional advantage of this is that it enables you to pass a Unicode string to the function rather than just ANSI (once again, see the aforementioned article). The flag 'bStreamIn' is used to make sure the StreamIn function is only called once when we need it to be. Every time you stream data into a document, you will receive another DocumentComplete event. If you then call StreamIn in said event without a flag, you will just create a recursive loop and eventually get a stack overflow (or it could just crash instantly - which is just as bad, or possibly worse).

And that is it. You can now safely and happily put any HTML content in a WebBrowser that you wish. Happy coding.