Simple Web Browser

Home
Introduction
Tips and Techniques
Projects
Libraries
Links

Library

Tested with Functional Developer 2.0.

You can download the simple web browser example here: simple-web-browser.zip (approx. 16KB).

Overview

The DUIM libraries for displaying ActiveX controls in a DUIM pane are not yet ready for prime time use. As a work around here is a way of displaying ActiveX controls in a DUIM pane by using the ATL DLL that Microsoft supplies. This example demonstrates using Internet Explorer as an embedded control:

To use run the example you will need the latest version of the ATL runtime library (113KB) available at the microsoft site. Unzip the file and run the self installing executable to install it.

Here's how it works...

The latest version of the Microsoft Active Template Library (ATL) for MSVC++ 6.0 registers a window class that hosts ActiveX controls - it is a full control container.

It has a class name of 'AtlAxWin' and uses whatever you pass in the window text to decide what control to create. If you pass in a class id as "{class-id-here}" then it will use this class id to create the control. If you pass in an url, it will create the internet explorer control and navigate to that url. I use this in the project.

To register the window class you need to call a function 'AtlAxWinInit' in this DLL. But there is no .lib file available (unless you have VC++ 6.0) so you have to use LoadLibrary/GetProcAddress from Dylan:

define C-function AtlAxWinInit 
  result value :: ; 
  indirect: #t; 
  c-modifiers: "__stdcall"; 
end; 

*atl-module* := LoadLibrary("atl.dll"); 
let function = GetProcAddress(*atl-module*, "AtlAxWinInit"); 
AtlAxWinInit(function); 

With this approach you don't need to link to any additional files. Once that is registered you can create a window of the new class. I create a DUIM gadget for html:

define class <html-gadget> (<value-gadget>) 
end class <html-gadget>; 

With a back end to do the creation of the ATL window:

define class <msie-html-gadget> 
  (<win32-gadget-mixin>, 
   <bordered-gadget-mixin>, 
   <html-gadget>, 
   <basic-value-gadget>, 
   <leaf-pane>) 
  virtual constant slot msie-ole-control; // described later 
end class <msie-html-gadget> 

define method class-for-make-pane
  ( framem :: <win32-frame-manager>, 
    class == <html-gadget>, #key) 
  => (class :: <class>, options :: false-or(<sequence>)) 
  values(<msie-html-gadget>, #f) 
end method class-for-make-pane; 

define method make-gadget-control
  ( gadget :: <msie-html-gadget>, 
    parent :: <hwnd>, 
    options, #key x, y, width, height) 
  => (handle :: <hwnd>) 
  let ext-style = i
    if(gadget.border-type == #"none") 
      0 
    else 
      $WS-EX-CLIENTEDGE 
    end; 
 
  let handle ::  = 
    CreateWindowEx(ext-style, 
      "AtlAxWin", 
      "http://double.nz", 
      %logior(options, 
              $WS-GROUP, 
              $WS-TABSTOP), 
      x, y, width, height, parent, 
      $null-hMenu, 
      application-instance-handle(), 
      $NULL-VOID); 
  handle 
end method make-gadget-control; 

Once these methods are defined you can create the gadget in a frame:

define frame <atl-browser-frame> (<simple-frame>) 
  pane browser-pane (frame) 
    make(<html-gadget>); 

  pane go-button (frame) 
    make(<push-button>, 
      label: "Go", 
      activate-callback: on-go-button); 

  pane url-pane (frame) 
    make(<text-field>, text: "http://double.nz"); 

  layout (frame) 
    vertically() 
      horizontally() 
        frame.url-pane; 
        frame.go-button; 
      end; 
      frame.browser-pane; 
    end; 

  keyword title: = "Simple Web Browser"; 
end frame <atl-browser-frame> 

To prevent a crash when exiting the program, you need to unload the DLL that was loaded previously after program exit:

begin 
  load-atl-library-stuff(); 
  main(); 
  FreeLibrary(*atl-module*); 
end;

Running something like the above is enough to display a frame, with a web browser window displaying the home page at http://double.nz.

You can navigate through the links from there as well. To make the url-pane and go-button work you need to be able to get the underlying web browser control. The COM interface used by the web browser is called IWebBrowser2.

The easiest way of getting the Dylan wrapper for this is to use the COM typelibrary wizard to create dispatch-client wrappers for the 'Microsoft Internet Controls' type library. These are installed with IE 4+. I've included the generated library in the download by the way.

The way of getting the interface pointer from the ATL window is by sending it a windows message, using a special message that ATL has registered:

*get-control-message* := RegisterWindowMessage("WM_ATLGETCONTROL"); 

The implementation of the virtual slot in the gadget does this:

define method msie-ole-control( gadget :: <msie-html-gadget> )
  let interface = 
    c-type-cast(<c-interface>, 
      SendMessage(gadget.window-handle, *get-control-message*, 0, 0)); 
  let (result, com-interface) = 
    QueryInterface(interface, 
      as(<refiid>, "{D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}")); 
  pointer-cast(<iwebbrowser2>, com-interface); 
end method msie-ole-control; 

The {D30C...9e26e} is the GUID for the IWebBrowser2 interface. This method basically asks the ATL window for the interface pointer. This is returned as a DWORD through the SendMessage call - but it is really a pointer to the interface, so I cast it to that. I QueryInterface it for the IWebBrowser2 interface and return it. Note that I don't do any error checking above - an exercise for the reader :-).

Use the slot above to implement the go-button:

define method on-go-button(g) 
  let frame = g.sheet-frame; 
  let browser-gadget = frame.browser-pane; 
  let-com-interface html-control = browser-gadget.msie-ole-control; 
  let-resource url :: <bstr> = as(<bstr>, frame.url-pane.gadget-value); 
  let-resource optional :: <lpvariant> = make(<lpvariant>); 
  IWebBrowser2/Navigate(html-control, 
    url, 
    optional, 
    optional, 
    optional, 
    optional); 
end method on-go-button; 

The above uses a couple of macros to automatically release the reference count for the interface, and to free the <bstr> and <variant>. All these classes contain data allocated by windows - and windows needs to be told when you are finished with it. I was playing around with using 'let-' macros instead of the usual 'with-' style for COM interfaces. This was to avoid heavy source code nesting I was getting when using the MSXML COM libraries. The macros are:

define macro let-com-interface 
{ let-com-interface 
    ?:name :: ?type:expression = ?value:expression; 
  ?:body } 
 => { let ?name :: ?type = ?value; 
      block() 
        ?body 
      cleanup 
        release(?name) 
      end } 
end macro let-com-interface; 

define macro let-resource 
{ let-resource 
    ?:name :: ?type:expression = ?value:expression; 
    ?:body } 
 => { let ?name :: ?type = ?value; 
      block() 
        ?body 
      cleanup 
        destroy(?name) 
      end } 
end macro let-resource; 

The methods that can be called on the web browser control are listed in the automatically generated wrappers to the type library. They are also documented in the windows platform SDK. That's about it to get a very simple browser working in Functional Developer.


Copyright © 2000, Chris ^M Double. All Rights Reserved.
^M All products and brand names are the registered trademarks or trademarks ^M of their respective owners.

^M