About/Contact

Steve Trefethen

Steve Trefethen is a Director of Engineering at Reply. Contact me

View my LinkedIn profile


Powered by discountASP.NET
referal ID: sdtref
Why recommend discountASP.NET?
$720 in referrals so far!


Calendar

<<  February 2012  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
2728291234
567891011

View posts in large calendar

Disclaimer

The posts on this weblog are provided AS IS with no warranties, and confer no rights. The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.



Calling Windows Client code from Javascript hosted inside the WebBrowser control

June 14 2007 1:02AM

Well, Allen pretty much stole the thunder on this one so I'll post this article I wrote a few days ago and try to get the code cleaned up and posted ASAP. Though I suppose now I should look to incorporate his code rather than duplicating that effort. Anyhoo...

In a previous post, I talked about using Google maps from within a Windows Client application (in this case a Delphi VCL application). I demonstrated how it's possible to call into the Javascript loaded in a hosted WebBrowser control using IHTMLWindow2::execScript method. Through this technique it's possible to manipulate a things like Google maps rendered in the browser control.

Closing the Loop
In this post, I'll discuss a technique that allows Javascript, on a page rendered in the WebBrowser control, to execute VCL application code using IDocHostUIHandler::GetExternal. If you happened to read this post from Hallvard Vassbotn, a little over a year ago, then you're already aware of the unit which is the secret behind getting this technique to work. The unit is ObjComAuto.pas. It introduces TObjectDispatch which implements IDispatch and provides the plumbing necessary to script VCL objects compiled with the directive {$METHODINFO ON} which  is used to generate extended RTTI for public and published methods.

Up's and Down's
Now, if you're familiar with the VCL you know it's not compiled with METHODINFO ON since the additional RTTI (Runtime Type Information) would dramatically increase the size of the resulting executables. As a result, we need a mechanism to map an instance of a VCL object that does not have this additional RTTI to one that does, using wrapper classes and a class map. The up-side is that we're able to control the amount of additional RTTI that's generated by providing implementations for only those things we really want. The downside of course, is that everything we want to script has to be exposed manually though as you'll see it's not that bad. Another issue is that the code for any classes added to the class map will be pulled into our application regardless of whether or there instantiated.

Base Wrapper Class
The first step is to create a base class with METHODINFO ON that's will be bound to a VCL object and implements a few key methods to get the ball rolling:

  { TObjectWrapper }

{$METHODINFO ON}
  TObjectWrapper = class(TObject)
  private
    function GetClassName: string;
    function GetParentClass: string;
  protected
    FObject: TObject;
  public
    constructor Connect(AObject: TObject); virtual;
    function InheritsFrom(const ClassName: string): Boolean;
  published
    property ParentClass: string read GetParentClass;
{$WARNINGS OFF}
    property ClassName: string read GetClassName;
{$WARNINGS ON}
  end;

From this we'll construct additional classes which mirror VCL classes like TComponentWrapper, TControlWrapper and TWinControlWrapper that expose various key properties and methods we need to build a VCL scripting framework.

A Mapper Class
Next, we construct a list that will allow us to map VCL classes to these wrapper classes which we can do with a TObjectList:

 TObjectWrapperClass = class of TObjectWrapper;

 TClassMap = class(TObjectList)
 public
   procedure AddClass(AClass: TClass; AObjWrapper: TObjectWrapperClass);
   procedure RemoveClass(AClass: TClass; AObjWrapper: TObjectWrapperClass);
   function FindObjectWrapper(AClass: TClass): TObjectWrapperClass;
 end;

Using the Class Map
Then we implement a descendant of TObjectDispatch and override GetObjectDispatch, GetMethodInfo and GetPropInfo to create instances of our wrapper classes from the class map for use with automation:

 TAutoObjectDispatch = class(TObjectDispatch)
 protected
   function GetObjectDispatch(Obj: TObject): TObjectDispatch; override;
   function GetMethodInfo(const AName: ShortString; 
     var AInstance: TObject): PMethodInfoHeader; override;
   function GetPropInfo(const AName: string; var AInstance: TObject; 
     var CompIndex: Integer): PPropInfo; override;
 end;

Connecting with Javascript
Then we implement IDocHostUIHandler::GetExternal and use TAutoObjectDispatch class to create objects that can be used from Javascript:

function TWebBrowser.GetExternal(out ppDispatch: IDispatch): HResult;
var
  W: TAppObjectWrapper;
begin
  W := TAppObjectWrapper.Connect(Forms.Application);
  ppDispatch := TAutoObjectDispatch.Create(W) as IDispatch;
  Result := S_OK;
end;

Putting it all together
Once we have these pieces in place it's possible to build VCL applications that host the WebBrowser control and allow for rich interactions between the web page and the application itself. Of course, this same technique can be utilized in other ways as well though like Hallvard I'll leave that as an exercise for the reader.

Related posts

FacebookDel.icio.usDigg It!

Tags:

Comments (8) -

6/14/2007 6:22:14 AM #

Hello,

Great article.

Can something like this be used on a MAC. i.e. if non-visual Delphi code was compiled with Free Pascal for the MAC, is there something equivalent to GetExternal on the Mac browsers?

David Novo

6/14/2007 6:56:14 AM #

Hi David,
  Boy, I have no idea. The GetExternal call for IE is certainly something that's very specific to it's implementation.

Steve Trefethen

6/14/2007 7:11:00 PM #

Just a prayer of a possible way to create cross platform apps using Delphi. FreePascal can(supposidly) compile non-visual code quite well. If we can rewrite our UI in Javascript, perhaps we can use dlls (or the MAC equivalent)  and have a compile once, run everyone kind of thing.

David Novo

6/16/2007 11:07:26 PM #

Steve,

You and Allen are posting interesting ideas about integrating Web and Win32.  However, for the browser-desktop application integration concept to have mass appeal it needs be cross-browser and cross-platform.   Personally, I think that more and more of the application development market is moving to Web development.  I love Firefox and just about everything Google is doing.  I think that you guys need to examine what Google is doing and then figure out how to allow your customers to create Web and desktop applications like Google Reader (Web) and Google Earth (Windows and Linux) using Object Pascal.   Google does very little development for Win32 only.   You guys need to do the same thing.   Following Microsoft too closely does you little good.  You should give your customers a way to build universal applications (son of CLX?) and extensions to Firefox.

Tom Wilk

6/17/2007 2:40:59 AM #

Tom,
I agree with you. I attended Google Developer Day because of many of the reasons you mentioned.

Steve Trefethen

6/17/2007 7:37:15 PM #

While I do agree that more and more development is taking place in Web space, I do not think that necessarily reduces the importance of Win32.  Looking at my PC yeilds numerous apps that are not going to be replaced by web based versions anytime soon.  My point is that Web development is very important but I do not think the importance of Win32 has diminished in the least.

Ryan McGinty

4/3/2008 9:07:49 PM #

Hi...

How I get the content of a textfield from the Browser???

Thanks
Jair

Jair

4/3/2008 10:34:10 PM #

Jair,
  One way is to use Javascript to locate the DOM element and simply access its innertext property.

Something like this:
  document.getElementById("InputIDValue").innerText

Steve Trefethen

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading