Implementation

[Previous] [Next]

Visual Basic offers three different language features to facilitate the creation of an event service: AddressOf, Class events, and interface-based programming.

AddressOf

Registering a function to handle a specific event is a feature that has always existed in low-level languages such as C and C++. This feature is commonly referred to as a callback function. Typically, the program registering the callback function doesn't invoke it directly; a callback function is registered with a run-time service that invokes it when a particular event occurs. The address in memory of the callback function is registered with the run-time service, which gives the service the ability to locate and call the function. An ideal example of a run-time service is the Windows operating system. The Windows API contains a set of DLLs that in turn contain functions. Some of these functions enable you to subscribe to run-time service events via a callback registration.

Registering callback functions in Microsoft Visual Basic is possible, but not without limitations. Using the Visual Basic AddressOf unary operator followed by the function name will return the address of a Visual Basic function that can be passed to a Windows API function for callback registration. The Visual Basic callback function can be defined only in a standard module. Furthermore, Visual Basic doesn't support function pointers; therefore, it can't be the run-time service invoking the callback by means of the AddressOf operator. But despite these drawbacks, AddressOf is an incredibly useful language feature that permits Visual Basic programmers to tap into some sophisticated run-time services such as those provided by Windows. For example, in the following code extract, a call is made to the Windows API CreateThread function by passing the address of the CallMe Visual Basic user-defined function. The Windows operating system run-time service will hand this address to the newly created thread to execute the function at that address.

 hThread = CreateThread(0, 0, AddressOf CallMe, 0, 0, m_lThreadId) ' Do some work.  ' The CallMe function is defined in a standard module. Public Sub CallMe() ' Do some work.  End Sub 

The CreateThread function executes asynchronously, so the code following it will immediately continue to execute. When the spawning of the new thread event occurs, the run-time service will invoke the CallMe function. Visual Basic doesn't have native language support for spawning threads in a process, but because it provides the AddressOf operator you can still take advantage of this most critical operating-system feature. Callbacks might have different semantics than the Event Service design pattern described in this chapter, but the concept is the same. The callback function signature is equivalent to the ISubscriber interface. CallMe is equivalent to a concrete class implementation of ISubscriber. A conceptual publisher and event channel exist inside the run-time service. Callbacks are therefore a valid Event Service design pattern implementation.

Class Events

Visual Basic allows you to declare, raise (publish), and handle (subscribe) events within Class and Form class modules. These abilities are all that's required to implement the constituents of the Event Service design pattern, which include the event channel, publisher, and subscriber. Events are declared and raised within the same class. By definition, this makes a class with declared events an event channel. Depending on your design, the publisher can be implicit or explicit. An implicit publisher exists when there is no explicit publisher interface but rather events are raised in the class methods wherever the programmer decides it is necessary. This is a good approach when you need to provide hooks for extending specific components within a system. The following code extract defines a TravelAgent class module that declares OnBookReservation and OnPurchaseTicket events. These events are raised during the booking and ticket purchasing processes.

 ' TravelAgent.cls Option Explicit Public Event OnBookReservation(ByVal Res As Reservation) Public Event OnPurchaseTicket(ByVal Tick As Ticket) Public Function BookReservation() As Reservation Dim theRes As Reservation ' The booking code... RaiseEvent OnBookReservation(theRes) BookReservation = theRes End Function Public Function PurchaseTicket() As Ticket Dim theTicket As Ticket ' The purchase ticket code... RaiseEvent OnPurchaseTicket(theTicket) PurchaseTicket = theTicket End Function 

Events are declared Public and the declaration includes the keyword Event followed by a method prototype similar to a subroutine. Events are raised within class methods using the keyword RaiseEvent followed by one of the declared events. In Visual Basic and COM-speak, an object such as TravelAgent that raises events is called an event source. Refer to the Visual Basic documentation for a complete explanation of how to implement an event source class.

Let's assume that TravelAgent objects are created within an electronic travel reservation system exclusively to facilitate the booking and purchasing of airline tickets. However, you want to make it convenient for the customer to book hotel accommodations and car rental reservations along with air travel. Because the TravelAgent object raises events, you don't have to make enhancements to the existing code base. New class modules compiled in a different project (DLL or EXE) can plug into the system by subscribing to the events raised by the TravelAgent object. Event subscribers are class modules that declare module-level object variables of an event source object's class using the keyword WithEvents. The following code extract reflects the definitions you would expect to find in a HotelAgent class and a CarAgent class that are subscribing to (or handling) events raised by a TravelAgent object.

 Option Explicit Private WithEvents aTravelAgent As TravelAgent Public Sub Attach(ta As TravelAgent) Set aTravelAgent = ta End Sub Public Sub Detach() Set aTravelAgent = Nothing End Sub Private Sub aTravelAgent_OnBookReservation(ByVal Res As Reservation) ' Add event handler code here. End sub Private Sub aTravelAgent_OnPurchaseTicket(ByVal Tick As Ticket) ' Add event handler code here. End Sub 

Notice that the object variable aTravelAgent is declared as a type of TravelAgent event source class using the keyword WithEvents. Consequently in the Visual Basic class module editor, aTravelAgent will appear in the Object drop-down list box. Selecting it will result in the OnBookReservation and OnPurchaseTicket events appearing in the Procedure drop-down list box. To add an event handler for a given event, simply select the event from the Procedure drop-down list box. Event handlers are defined as private subroutines. They are named based on the name of the event prefixed with the name of the object variable that was declared WithEvents and an underscore (aTravelAgent_OnBookReservation). To receive events from a particular TravelAgent object, a reference to the object must be maintained. The Attach and Detach methods facilitate the acquisition and release of a TravelAgent object reference. Declaring an event source object variable using WithEvents, defining event handler subroutines, and obtaining a reference to an associated event source object, together allow you to subscribe to events raised by a referenced event source object.

Interface-Based Programming

Interface-based programming is by far the most effective means of implementing the Event Service design pattern. Visual Basic includes the language features required to fully support this concept. In interface-based programming, clients program to an interface and server objects expose and support interfaces expected by clients. (Refer to Chapter 2 for in-depth coverage of interfaces.) For maximum effectiveness, an event service should be publicly accessible to publishers and subscribers in a different space (DLLs and EXEs running local or remote). To achieve this effectiveness, you are required to create an ActiveX type of project.

In an ActiveX project, define a class module for each interface in the Event Service design pattern. Interfaces have no implementation and can't be instantiated. Therefore these class modules should only have public properties and methods defined with no implementation. Also the class Instancing property value should be set to PublicNotCreatable to prohibit instantiation from a client. The following code extract illustrates plausible partial IEventChannel and ISubscriber interface definitions.

 ' IEventChannel.cls Option Explicit Public Function Advise(Context As String, _ Subscriber As ISubscriber) As Long End Sub Public Sub Unadvise(Context As String, Cookie As Long) End Sub  ' ISubscriber.cls Option Explicit Public Sub Push(ByVal Context As String, ByVal vData As Variant) End Sub  

Within the same project you would typically define a concrete class that implements the IEventChannel interface, similar to the following partial code extract.

 ' EventChannelImpl.cls Option Explicit Implements IEventChannel Private m_ctxList As Scripting.Dictionary Private m_nCookie As Long Private Function IEventChannel_Advise(Context As String, _ Subscriber As ISubscriber) As Long Dim ctx As Scripting.Dictionary ' Obtain Dictionary object for Context. ' If one doesn't exist, create it. If m_ctxList.Exists(Context) Then Set ctx = m_ctxList(Context) Else Set ctx = New Scripting.Dictionary Call m_ctxList.Add(Context, ctx) End If ' Create new cookie. m_nCookie = m_nCookie + 1 ' Add Subscriber to context object with new associated cookie. Call ctx.Add(CStr(m_nCookie), Subscriber) ' Return cookie to client. ' Client must use cookie to unadvise. IEventChannel_Advise = m_nCookie End Function 

In general, subscriber implementations are defined in a separate ActiveX project so that they can be registered for events at run time. One possible alternative could be to create an ActiveX DLL project that defines subscriber classes. To define a subscriber class, perform the following steps.

  1. Add a project reference to the ISubscriber interface data type (via the References option of the Project menu in the Visual Basic IDE). The location of this information will be in a COM type library. Depending on the packaging, the type library can be a stand-alone .tlb file or it can be embedded in an ActiveX server binary (DLL or EXE).
  2. Add a new class module to the project.
  3. Implement the ISubscriber interface using the keyword Implements. A subscriber implementation should look similar to the following code extract:
  4.  ' SubscriberImpl.cls Option Explicit Implements ISubscriber Private Sub ISubscriber_Push(ByVal Context As String, _ ByVal vData As Variant) ' Add subscriber-specific implementation code here. End Sub 

  5. The final step involves code that resembles the following code extract to register subscriber objects on an event channel.
  6.  ' Client stitch-in code for subscribing to "someevent" on ' an event channel referenced by m_EventChannel m_nCookie = m_EventChannel.Advise("someevent", _ New SubscriberImpl) 

As you saw in the EventChannelImpl class code extract, subscribers register for notification on a specified context that can be generated on the fly. It's important to recognize that the event channel determines the behavior of the event service infrastructure. The event channel in this case expects to register objects at run time under a context. The client provides both the object and the context at run time, as shown in the previous client code extract. In another scenario, the event channel could just as easily create subscriber instances and register to a context based on subscriber class information stored in a relational database.



Microsoft Visual Basic Design Patterns
Microsoft Visual Basic Design Patterns (Microsoft Professional Series)
ISBN: B00006L567
EAN: N/A
Year: 2000
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net