CanaimaSoft
f90SQL
Search
Contents
f90ADO
Links
f90VB
 
 
 
 

Handling Automation events from Fortran (Example 6.5)

 

In this example, we build an automation controller that keeps a log of all the pages you visit while surfing the net with Internet Explorer. The controller works this way; it first checks if there is an instance of Internet Explorer running, in which case it requests a reference to the IE application object. If there isn't an instance of IE running, it creates a new one. The controller then requests the default event interface of IE and registers a subroutine (OnNavigationComplete) that handles the IE event NavigationComplete2. Fortran subroutine OnNavigationComplete only prints the name of the downloaded URL into the main application window but it can be easily modified to store the output into a log file. The program keeps logging the navigated pages until the user quits Internet Explorer. This is detected through the event OnQuit, which is fired by IE explorer when the user closes the application. Below is the program in its version for Compaq Visual Fortran. You can find specific versions for Lahey Fortran 95 and Absoft Pro Fortran in the Examples directory of the f90VB CD.


program AutoEventsExample

!Demonstrates event handling using
!the EventSink facility in f90VBAutomation
!Copyright (C) 1999-2000, Canaima Software, Inc.
!All rights reserved

use f90VBDefs
use f90VBVariants
use f90VBAutomation
use EventHandler
use dfwinty, only: T_MSG
use user32, only: GetMessage, DispatchMessage
implicit none

!Variants containing main objects
type(VARIANT)::IE
!Variants used to store temporal objects and collections
type(VARIANT)::VarTmp,URL
!Event sink handle
integer(HRESULT_KIND)::iRet
type(VARIANT)::IsVisible
type(T_MSG)::mesg

!Initialize Ole
iRet = OleInitialize()

!Get an instance of Internet Explorer
IE = GetActiveOleObject('InternetExplorer.Application', iRet)
if (iRet.ne.S_OK) then
    !no instances of IE are running, create one
    IE = CreateOleObject('InternetExplorer.Application', iRet)
    if (iRet.ne.S_OK) then
        print *,'You need to have Internet Explorer installed'
        print *,'for this program to work'
        goto 1000
    endif
    !Makes the IE object visible
    call PropertyPut(IE,'Visible',VariantCreate(VT_BOOL,.true.))
endif

!Create an f90VBAutomation Event Sink
EventSinkHndl = EventSinkCreate(iRet)
if (iRet.ne.S_OK) then
    print *,'There was an error creating the Event Sink'
    goto 1000
endif

!Connect the Event Sink to the default IE events interface
call EventSinkConnect(EventSinkHndl, IE, NullGUID(),iRet)
if (iRet.ne.S_OK) then
    print *,'Cannot connect to IE events interface'
    goto 1000
endif

print *,'Log started...'

!Register Fortran functions for the events we want to handle
call EventSinkRegEvnt(EventSinkHndl, 'NavigateComplete2', &
                      loc(OnNavigationComplete), iRet)
call EventSinkRegEvnt(EventSinkHndl, 'OnQuit', loc(OnQuit), &
                      iRet)

!This loop keep the program running until IE exits
do while( GetMessage (mesg, 0, 0, 0) )
    if (IEUnloaded) exit
    iret  = DispatchMessage( mesg )
enddo

1000 continue

!The next three statements are not really necessary as IE has
!already closed when we get here. We have no way to stop it
!from doing this
call EventSinkUnregEvnt(EventSinkHndl, 'NavigateComplete2', iRet)
call EventSinkUnregEvnt(EventSinkHndl, 'OnQuit', iRet)
call EventSinkDisconnect(EventSinkHndl)

!Destroy the Event Sink object and release its memory
call EventSinkDestroy(EventSinkHndl)

!Release the instance of IE
call Release(IE)

!Clean up variants
call VariantClear(URL)
call VariantClear(VarTmp)

!Uninitialize Ole
call OLEUninitialize()

stop
end


We put the two subroutines that handle the events into a Fortran module called EventHandler. Here is the code for the module:



module EventHandler

    use f90VBDefs
    use f90VBBstrings
    use f90VBVariants
    use f90VBAutomation
    implicit none

    logical::IEUnloaded = .false.
    integer(POINTER_KIND)::EventSinkHndl

contains

    function OnNavigationComplete(DispID, nArgs, VarArgList, nNamedArgs, DispIDList)

        !This function is fired by the event NavigationComplete2 of Internet Explorer
        !We don't do much here, just show a message on the main application
        !console indicating that the event was fired and the URL downloaded

        integer(HRESULT_KIND)::OnNavigationComplete
        integer(LONG_KIND),intent(in)::DispID
        integer(LONG_KIND),intent(in)::nArgs
        type(VARIANT),intent(inout)::VarArgList(nArgs)
        integer(LONG_KIND),intent(in)::nNamedArgs
        integer(LONG_KIND),intent(in)::DispIDList(nNamedArgs)

        character(len=1024)::TmpStr
        integer(HRESULT_KIND)::iRet
        integer(BSTRHNDL_KIND)::BStrHndl

        !get the URL of the downloaded page
        BstrHndl= VariantToBString(VarArgList(1))
        !copy the BStr into a Fortran string for printing
        call StrCopy(BstrHndl,TmpStr,iRet)
        if (iRet.ge.0) then
            print *,'Page downloaded:<',trim(TmpStr),'>'
        else
            print *,'Page downloaded:<cannot print URL>'
        endif
        !releases the BStr
        call StrFree(BStrHndl)

        OnNavigationComplete = S_OK

    end function OnNavigationComplete


    function OnQuit(DispID, nArgs, VarArgList, nNamedArgs, DispIDList)

        !This function is fired by the event OnQuit of Internet Explorer
        !We don't do much here, just show a message on the main application
        !console indicating that the event was fired

        integer(HRESULT_KIND)::OnQuit
        integer(LONG_KIND),intent(in)::DispID
        integer(LONG_KIND),intent(in)::nArgs
        type(VARIANT),intent(inout)::VarArgList(nArgs)
        integer(LONG_KIND),intent(in)::nNamedArgs
        integer(LONG_KIND),intent(in)::DispIDList(nNamedArgs)

        integer(HRESULT_KIND)::iRet

        print *,'Quit Internet Explorer'

        IEUnloaded = .true.

        OnQuit = S_OK

    end function OnQuit

end module

Let’s now take a detailed look at the code in this example. The program starts by initializing OLE and creating a new instance of IE if one doesn’t exist. If the program creates a new instance, then it must set its Visible property to True (traditionally new instances of automation servers are not visible). We have seen this last technique before, in Example 6.3:


!Initialize Ole
iRet = OleInitialize()

!Get an instance of Internet Explorer
IE = GetActiveOleObject('InternetExplorer.Application', iRet)
if (iRet.ne.S_OK) then
    !no instances of IE are running, create one
    IE = CreateOleObject('InternetExplorer.Application', iRet)
    if (iRet.ne.S_OK) then
        print *,'You need to have Internet Explorer installed'
        print *,'for this program to work'
        goto 1000
    endif
    !Makes the IE object visible
    call PropertyPut(IE,'Visible',VariantCreate(VT_BOOL,.true.))
endif

Next we create an event sink object through a call to f90VB subroutine EventSinkCreate:


!Create an f90VBAutomation Event Sink
EventSinkHndl = EventSinkCreate(iRet)
if (iRet.ne.S_OK) then
    print *,'There was an error creating the Event Sink'
    goto 1000
endif

In OLE/COM terminology, an event sink is an object capable of responding to events fired by an ActiveX object (the event source). In languages that understand OLE/COM, like Visual Basic, event sinks are created at compile time and usually the compiler takes care of the details for you. Function EventSinkCreate creates a generic event sink capable of responding to events fired by any ActiveX object. The function returns a handle (i.e. a pointer) that is later used to identify the instance of the event sink object you have just created.

Once you have an event sink, the next step is to connect the object to the automation server:


!Connect the Event Sink to the default IE events interface
call EventSinkConnect(EventSinkHndl, IE, NullGUID(),iRet)
if (iRet.ne.S_OK) then
    print *,'Cannot connect to IE events interface'
    goto 1000
endif

You do this by calling EventSinkConnect. Note that EventSinkConnect has four parameters; the first is the handle of an event sink object (EventSinkHndl in this example), the second is a Variant variable that contains a reference to the automation object (IE) that provides the event source, the third argument is the Interface Identifier (IID) of the automation object’s interface that exposes the events. As we saw before, objects expose their functionality through interfaces, and this also applies to events. Event interfaces are usually called outgoing interfaces, because their member functions are not implemented by the automation server, but must be implemented by the automation controller. In this sense, outgoing interfaces are like a template describing the methods an automation controller must implement in order to support communication and messages sent from the automation server. Automation objects can expose more than one set of events through different interfaces. EventSinkConnect gives you the option of connecting an event sink to any of these interfaces by allowing you to pass the Interface Identifier (remember that IIDs are GUID structures) of the outgoing interface you want to use. If you call EventSinkConnect with a null IID (this is what f90VB function NullGUID returns) then the subroutine just connects the event sink to the first outgoing interface exposed by the object, which is normally the default event interface

EventSinkConnect can poll all the outgoing interfaces exposed by an automation object. Most automation objects only expose one outgoing interface, however, it is possible for an object to expose more than one. When this happens, the only reliable way to detect which of the exposed interfaces is the default interface is by accessing the type library of the automation server, because IProvideClassInfo is not always implemented (this is an optional interface). Because type libraries are not always available, in particular for objects that only present their IDispatch interface, when you call EventSinkConnect with a null IID the subroutine always connects to the first outgoing interface listed by IConnectionPointContainer:EnumConnectionPoints.

The forth argument in EventSinkConnect is used to return an error flag if the function cannot establish a connection to the requested event interface (for example, if the object does not expose events).

At any given time, a single event sink object may be connected to only one outgoing interface. If you need to simultaneously handle the events fired by more than one object (or more than one outgoing interface of the same object), you must create an event sink object for each event source object (or outgoing interface). You can, however, reuse sink objects (i.e. connect the sink object to a different event interface) after you have previously disconnected them from the Automation object.

Once you have the event sink connected to the event interface of the object your Fortran application is controlling, the next step is to register the Fortran functions that will handle specific events fired by the ActiveX server. We do this by calling EventSinkRegEvnt:


!Register Fortran functions for the events we want to handle
call EventSinkRegEvnt(EventSinkHndl, 'NavigateComplete2', &
                      loc(OnNavigationComplete), iRet)
call EventSinkRegEvnt(EventSinkHndl, 'OnQuit', loc(OnQuit), &
                      iRet)

Again, the first argument of EventSinkRegEvnt is the handle of the event sink. The second argument is a character string with the name of the event for which you want to register a Fortran function handler. The third argument is the address of the Fortran function that will handle the event, and the forth argument is the iRet error flag indicator. When you call EventSinkRegEvnt, your are telling the event sink object “each time the ActiveX object you are connected to fires this event, execute the Fortran function located at this memory address”.

The two calls to EventSinkRegEvnt in Example 6.5 register two Fortran functions (OnNavigationComplete and OnQuit) as the functions to be called when events NavigateComplete2 and OnQuit are fired. Internet Explorer fires event NavigateComplete2 after an URL has been loaded, while event OnQuit is fired when IE quits.

If you take a look at the Fortran functions OnNavigationComplete and OnQuit in module EventHandler, you will notice that they receive exactly the same arguments:

  • DispID: This is the Dispatch ID of the fired event that resulted in the calling of the Fortran function.

  • nArgs: This is the number of arguments passed by the event to the Fortran function. Each event in an outgoing interface may pass a different number of arguments.

  • VarArgList: This is a vector containing the arguments of the event as Variant values. The size of this vector is always nArgs.

  • nNamedArgs: This is the number of named arguments for the event.

  • DispIDList: If the event uses named arguments, then this vector contains the DispID of the named arguments.

The forth and fifth arguments are included just for the sake of providing full compatibility. Very few ActiveX objects will fire events passing only some of the event function arguments by name.

Note also that the function return value is a long integer (i.e. of type HRESULT_KIND).

All Fortran functions that are registered to handle events must have this exact same declaration

You don't need to follow the naming we used, either for the function name or its arguments. As long as the function returns a long integer and its arguments are in the same order and of the same type as those shown in the example, the Event Sink Object will be able to call back your function without problems. To access the parameters provided by the event, you use VarArgList, as demonstrated in function OnNavigationComplete:


    function OnNavigationComplete(DispID, nArgs, VarArgList, nNamedArgs, DispIDList)

        !This function is fired by the event NavigationComplete2 of Internet Explorer
        !We don't do much here, just show a message on the main application
        !console indicating that the event was fired and the URL downloaded

        integer(HRESULT_KIND)::OnNavigationComplete
        integer(LONG_KIND),intent(in)::DispID
        integer(LONG_KIND),intent(in)::nArgs
        type(VARIANT),intent(inout)::VarArgList(nArgs)
        integer(LONG_KIND),intent(in)::nNamedArgs
        integer(LONG_KIND),intent(in)::DispIDList(nNamedArgs)

        character(len=1024)::TmpStr
        integer(HRESULT_KIND)::iRet
        integer(BSTRHNDL_KIND)::BStrHndl

        !get the URL of the downloaded page
        BstrHndl= VariantToBString(VarArgList(1))
        !copy the BStr into a Fortran string for printing
        call StrCopy(BstrHndl,TmpStr,iRet)
        if (iRet.ge.0) then
            print *,'Page downloaded:<',trim(TmpStr),'>'
        else
            print *,'Page downloaded:<cannot print URL>'
        endif
        !releases the BStr
        call StrFree(BStrHndl)

        OnNavigationComplete = S_OK

    end function OnNavigationComplete

 

If you check the documentation of IE you will notice that event NavigateComplete2 is defined as follows:

Sub NavigateComplete2(ByVal pDisp As Object, URL As Variant)

The first parameter is a pointer to the IDispatch interface of the IE instance that fired the event. The second parameter is the URL to which the browser has navigated. We are interested in this last parameter, because we can use it to keep a log of pages visited by IE. Note however that in Fortran function OnNavigationComplete the URL is accessed as the first entry in vector VarArgList:

!get the URL of the downloaded page 
BstrHndl= VariantToBString(VarArgList(1))

This is because internally ActiveX arguments are always passed in inverse order

Note that f90VB procedures PropertyGet, PropertyPut, PropertySet and ExecMethod take care of ordering arguments of ActiveX functions so they are called in a natural way from your Fortran applications. We could have done the same for the arguments passed to Fortran event handler functions, but that would have resulted in a noticeable degradation in the performance of your application’s event handling. Some events are fired many times in the course of a normal application, and argument re-ordering would have to be done each time the event was fired.. So for example, to access pDisp you would use VarArgList(2).

Because all Fortran functions registered to handle events use exactly the same definition, rather than having individual functions to handle each event (as we did in Example 6.5), you could also use a single Fortran function to handle all the events. This technique is explained in section Centralizing event handling code of the User Manual.

Going back to the main program in Example 6.5, at this point, the program is ready to start processing events. In fact, a Fortran event-handling function will be called in answer to events fired by the ActiveX object immediately after the function is registered for an event.

The next thing the main program in Example 6.5 does is to enter into a Windows message loop:

!This loop keep the program running until IE exits 
do while(GetMessage (mesg, 0, 0, 0) ) 
   if (IEUnloaded) exit
   iret = DispatchMessage( mesg )
enddo 

The loop exits when the value of variable IEUnloaded is set to True or when function GetMessage returns False.

IEUnloaded is changed by function OnQuit, which, as explained earlier, is fired just before IE quits. You then might be rightfully asking why we are using a message loop, rather than a loop like the one below:

do while(.not. IEUnloaded)
enddo

Our suggestion is that you try it. If you edit Example 6.5 and replace the message loop by the loop above, the program will compile and seem to work fine, except that neither OnNavigationComplete nor OnQuit will ever be called

If you want to get to the bottom of this, the true reason why OnNavigationComplete or OnQuit are never called is that the sink object stops responding. This happens because the main application and the sink object share the same process (the sink object is an in-process server). If the main application does not yield control, the sink object is not able to respond to requests made by the Automation object to which it is connected. This would usually also stop (or at least, slowdown) the Automation object, because the Automation object will fire the event in the sink object and wait for a response from the sink. If the sink is not responding, the answer will never come. Some Automation objects can handle this lack of response from the sink graciously (with, for example, a time-out error) but this is not always the case.. Because OnNavigationComplete is never called, you won’t see the log of URLs loaded by the browser, and because OnQuit is also never called, IEUnloaded value is never set to True. In other words, the program will never terminate. Earlier in this chapter, we hinted about this kind of behavior. Loop statements tend to monopolize the system until terminated, thus preventing other operations in the same thread for the duration of the loop. Because the Fortran functions that handle the events and the main application use the same thread of execution, the loop just wouldn’t give the event-handling functions a chance to execute. We get around this problem easily by using a message loop, because when no messages are available for the current application, function GetMessage pre-empts the loop operation and yields control, which allows the event functions to be executed. We do have to agree that a loop message looks awfully weird is a console-based application, but the examples presented here are all console-based applications because we want to keep them as simple as possible, so the important points about f90VB don’t get confused with the complexities related to creating Windows applications in Fortran. Note that the message loop in Example 6.5 doesn’t do much, it just retrieves messages and dispatches them immediately.

As we explained before, the OnQuit event sets variable IEUnloaded to True, in which case the application exits the message loop. The code executed next is straight forward; first the Fortran functions associated with the event sink are unregistered:

call EventSinkUnregEvnt(EventSinkHndl, 'NavigateComplete2', iRet) 
call EventSinkUnregEvnt(EventSinkHndl, 'OnQuit', iRet) 

Then the event sink is disconnected:

call EventSinkDisconnect(EventSinkHndl)

Un-registering and disconnecting the sink object are standard procedures that must be executed before terminating any application that uses events. In this particular case, by the time these procedures are executed, Internet Explorer is no longer running, so you could have just destroyed the sink object directly. We included the instructions mainly for illustration purposes.

Finally, the event sink created by the application needs to be destroyed, so its memory is released to the operating system:

call EventSinkDestroyEventSinkDestroy(EventSinkHndl)

The rest of the code in Example 6.5 is standard and has been explained before; we call subroutine Release to release the instance of Internet Explorer in variant variable IE, clean up used variants and BStrings, and un-initialize OLE with a call to subroutine OleUninitialize:

call Release(IE)
call VariantClear(URL)
call VariantClear(VarTmp)
call OLEUninitialize()  

 

 
Copyright © 1998-2000 Canaima Software
For questions regarding this site, send an e-mail to
webmaster@canaimasoft.com