Outbound Interfaces

The typical relationship between a client and an object is based on a simple connection. The client holds an interface reference to the object and uses this reference to access methods and properties. In this type of relationship, only the client can initiate communication. The object can return information to the client in response to a method call using either an output parameter or a return value, but it can't initiate a call back to the client.

Sometimes, it's beneficial to establish bidirectional communication between an object and a client. To support bidirectional communication, an object must provide an outbound interface. After creating the initial connection to an object, the client can establish a second connection by passing a reference to itself, as shown in Figure 6-6. The object uses this interface reference to invoke methods in the client. You can see that each side in this relationship acts as both a client and an object.

Figure 6-6. Objects that expose an outbound interface can establish bidirectional communication. An outbound interface allows the object to treat the client as another object. The client provides an implementation for the methods defined in an outbound interface.

You can establish bidirectional communication using either Visual Basic events or a custom callback interface. Both do the same thing conceptually; they allow the client to pass a reference to the object to set up the second connection. To demonstrate the use of an outbound interface, in the next section we'll look at a simple example in which you create an application with a dog object. The client that creates the dog object is the dog owner. The class behind the dog object provides a RollOver method that allows the owner to initiate a command on the dog. However, if the dog's owner requests that the dog roll over an unreasonable number of times, the dog will initiate a command back to the owner. In essence, the dog (object) will call back the owner (client) by invoking the OnBite method through an outbound interface.

Using Visual Basic Events

We'll start by implementing the design using Visual Basic events. Events can be hard to use at first because they add a whole new dimension to an object-oriented design. However, once you decide where to use them, they are fairly easy to set up. You define the outbound interface for an object by declaring events in the declaration section of a creatable class module. The other important thing you need to do in the class is to raise your events with the RaiseEvent statement in one or more methods. Examine the following code:

 ' CBeagle.cls Event OnBite() Sub RollOver(ByVal Rolls As Long)     If Rolls > 20 Then         RaiseEvent OnBite     Else         ' Be obedient and roll over.     End If End Sub 

The outbound interface for CBeagle is defined with a single event, OnBite. The RollOver method also contains logic for raising the OnBite event if the number of requested rolls is greater than 20. As you can see, it's pretty easy to define and use an outbound interface with events. The Visual Basic class defines the events and determines when they are raised. Events can be defined with parameters, just as standard methods are. When you define an event, you define an outbound method signature. However, the actual implementation behind these outbound methods must be defined by the client.

Consider what the client has to do to make everything work properly. It must establish the second connection and supply a response to each event raised by the object. Some tricky stuff goes on behind the scenes to hook up the second connection, but the required Visual Basic code is fairly simple. When the client creates a reference to an event-capable object using the WithEvents keyword, Visual Basic automatically establishes the second connection. Here's what the client looks like:

 Private WithEvents Beagle As CBeagle Private Sub Form_Load()     Set Beagle = New CBeagle End Sub Private Sub cmdRollOver_Click()     Beagle.RollOver txtRolls End Sub Private Sub Beagle_OnBite()     ' React to dog bite.     MsgBox "Ouch!" End Sub 

All you really need to do after you use the WithEvents keyword is to supply an implementation for the methods in the object's outbound interface. In the code above, the variable declared with the WithEvents keyword is named Beagle. You create event handlers by following the convention Beagle_OnBite. Visual Basic makes sure that this code is executed whenever the object raises the event. You can even use the wizard bar to quickly generate the skeletons for these handlers after you declare the variable using the WithEvents keyword.

When you use the WithEvents keyword, you create an event sink in the client. This event sink catches event notifications as they're raised by the object. An object that defines events is called an event source. So how does Visual Basic hook up the event sink with the event source? The client and the object must negotiate to establish the second connection. Fortunately for Visual Basic programmers, all the work is done behind the scenes. The following paragraph describes the details, but you don't have to care about them. That's one of the best things about using a high-level productivity tool such as Visual Basic.

Event sources are bound to event sinks through the COM interfaces IConnectionPoint and IConnectionPointContainer. When you use the WithEvents keyword, the Visual Basic run-time library silently uses these interfaces to negotiate on your behalf to hook up the client's event sink to the object's outbound interface. An object that defines events publishes a source interface in the type library. Take a look at the following IDL for the CBeagle class:

 interface _CBeagle : IDispatch {     [id(0x60030000)]     HRESULT RollOver([in] long Rolls); }; dispinterface __CBeagle {     methods:         [id(0x00000001)]         void OnBite(); }; coclass CBeagle {     [default] interface _CBeagle;     [default, source] dispinterface __CBeagle; }; 

You can see that an outbound interface is defined only in terms of automation. (The dispinterface keyword in IDL means IDispatch only.) You can also see that the outbound interface is marked as the [default, source] interface for the CBeagle class. A client using the WithEvents keyword can use only the default source interface. The only way to create a default source interface in Visual Basic is to define events in a creatable class module. This means that you can't create a user-defined interface with events and implement it in a Visual Basic class module. The way that Visual Basic hooks up events is simple but inflexible. You can't reuse an outbound interface definition across multiple classes, nor can a Visual Basic client hook up to anything but the default source interface.

These events come with a few other significant limitations. First, bidirectional communication requires that the client be a valid COM object. You must use an object created from a Visual Basic form or from a user-defined class to set up an event sink. Thus, you can use the WithEvents keyword only in the declaration section of a class module or form module. You can't use it in a local procedure or in a BAS module because there is no object to implement the event sink. Furthermore, when you declare variables using the WithEvents keyword, you must use the name of the class in which the events are defined. This means you can't use the WithEvents keyword when you declare variables of type Variant, Object, or Collection.

The last thing to consider is that events are implemented in Visual Basic using the IDispatch interface. This means that raising an event takes significantly longer than a pure vTable-bound method call. Fortunately, the object already has the DispID for every event, so there's no need for a call to GetIDsOfNames; events use early binding as opposed to late binding. An object can raise an event to a client in a single round-trip. However, the call still goes through Invoke on the client side, which makes it considerably slower than vTable binding.

Using a Custom Callback Interface

Instead of relying on events, you can establish a custom callback mechanism to get the same effect. You do this by defining a custom callback interface that defines a set of methods that an object can invoke on the client. This approach is more involved than using events, but it offers more flexibility and better performance.

Let's look at how to create an outbound interface for another dog class, CBoxer, using a custom callback interface. You start by defining a user-defined interface. We'll use a user-defined interface named IDogOwner with a single method, OnBite.

You should define this method in a Visual Basic class module with the instancing property set to PublicNotCreateable (or better yet, define it using IDL). After you define the outbound interface, you can use it in the CBoxer class, like this:

 Private Owner As IDogOwner Sub Register(ByVal Callback As IDogOwner)     Set Owner = Callback End Sub Sub Unregister()      Set Owner = Nothing End Sub Sub RollOver(ByVal Rolls As Long)     If Rolls > 20 Then         Owner.OnBite     Else         ' Be obedient and roll over.     End If End Sub 

The CBoxer class does a couple of interesting things. First, it declares an IDogOwner variable to hold a reference back to the client. Second, it allows the client to establish the second connection by providing a complementary pair of Register/Unregister methods. After the client creates the object, it must call Register to pass an IDogOwner-compatible reference to the object. Once the client passes a reference to itself or to some other IDogOwner-compatible object, the outbound interface has been hooked up. Here's what the code looks like on the client side:

 Implements IDogOwner Private Boxer As CBoxer Private Sub Form_Load()     Set Boxer = New CBoxer     Boxer.Register Me End Sub Private Sub cmdRollOver_Click()     Boxer.RollOver txtRolls End Sub Private Sub IDogOwner_OnBite()     ' React to dog bite. End Sub Private Sub Form_Unload(Cancel As Integer)     Boxer.Unregister End Sub 

In this example, the client is a form module that implements the IDogOwner interface. The client creates a CBoxer object in Form_Load and immediately calls Register by passing a reference to itself. After the form object registers itself with the CBoxer object, either one can invoke a method on the other. You should also note that the form calls Unregister in Form_Unload. It's important that you break the circular reference when you've finished. When you do this, you ensure that the object doesn't continue to hold a reference to a client that has been unloaded.

Using a custom callback interface is somewhat more complicated than using events because you have to worry about establishing the second connection yourself. It becomes more complicated when you want to connect many clients to a single object. The Register method in the CBoxer class is written to handle a single client. If you want to connect many clients at once, the CBoxer class must be modified to manage a collection of IDogOwner references. The Register method will also be required to add new client connections to the collection. If you want to make an outbound method call to every connected client, you can create a For Each loop to enumerate through the collection.

Custom callback interfaces do offer quite a few advantages over events. One key advantage is that custom callback interfaces allow an object to determine how many clients are listening. When you use events, the object has no idea how many clients are connected. Every event that is raised is broadcast to each client that has hooked up an event sink. You can't prioritize or filter how events are raised on a client-by-client basis. When you raise an event, the object synchronously broadcasts the outbound calls to all clients, one a time. Writing the code to maintain a collection of callback references isn't a trivial undertaking, but it does give you the flexibility to decide which clients get called first and which clients don't get called at all.

The second advantage of custom callback interfaces is flexibility and speed. Take a look at the IDL from the previous example:

 interface _IDogOwner : IDispatch {     HRESULT OnBite(); }; interface _CBoxer : IDispatch {     HRESULT Register([in] _IDogOwner* Callback);     HRESULT Unregister();     HRESULT RollOver([in] long Rolls); }; 

As you can see, custom callback interfaces defined with Visual Basic class modules use dual interfaces as opposed to events, which use IDispatch only. This means that custom callback interfaces use vTable binding; events use early binding. Interfaces based on vTable binding provide better performance and more flexible parameter passing than do events. The methods in custom callback interfaces can use optional parameters and parameter arrays that aren't available to events. You can always elect to define your callback interfaces in IDL instead of Visual Basic class modules.

You should also note that one callback interface can be used by many different clients and objects. This makes callback interfaces far more reusable than events. An outbound interface created using events can be used only by the class module in which the events are defined. If you're using IDL, you can define all your outbound interfaces in the same type library as all your inbound interfaces.

This chapter has described the how of creating an outbound interface, but it hasn't really addressed the when and the why of designing with bidirectional communication. The use of outbound interfaces in the previous examples was limited to making a callback to the client during the execution of a simple method. Outbound interfaces can be used in more sophisticated designs. For instance, if several clients are connected to a single object, you can use an event to inform clients that data in the object has changed. When one client changes some data, the change can be propagated to all the other clients. You can also use events or a custom callback interface to notify a client that an asynchronous method call has been completed. Chapter 7 revisits the idea of creating an asynchronous method.

Designing Distributed Applications

This chapter began by describing ways to design and manage your interfaces in a large application. Each approach has trade-offs in ease of use, extensibility, and scalability. You can design with user-defined interfaces or with simple MultiUse classes. You can create type libraries to publish your interfaces using IDL, or you can simply let Visual Basic take care of the details for you. What really matters is that the entire development team agrees on which road to take at the beginning of the project.

We also explored problems with remote interface design and some possible solutions. The overhead of the proxy/stub layer has a significant impact on the way you design your interfaces. You must try to complete every logical operation in a single round-trip. The challenges you face when creating efficient interfaces are compounded by the fact that you can't marshal objects by value. Objects always remain in the process in which they were created.

Most COM interface designs that have been optimized for remote access bear little resemblance to a design that you would see in a book about traditional OOP. Techniques that employ public properties and collections simply can't scale when they are taken out of process. The need to complete every logical operation in a single round-trip adds a necessary and painful layer of complexity to your interface designs. COM programmers learn to live with this. Once you know how the universal marshaler works, you can find the best way to get the data where it needs to be. On the bright side, you can hide complexity from other programmers by creating smart proxy objects.

This chapter also described two techniques that you can use to create an outbound interface. Events are easier to set up and use, but a custom callback interface can provide more flexibility and better performance. It's not hard to learn the mechanics of setting up an outbound interface, but knowing when to use one in your designs is tougher. Mastering outbound interfaces from the design perspective can take months or years.



Programming Distributed Applications With Com & Microsoft Visual Basic 6.0
Programming Distributed Applications with Com and Microsoft Visual Basic 6.0 (Programming/Visual Basic)
ISBN: 1572319615
EAN: 2147483647
Year: 1998
Pages: 72
Authors: Ted Pattison

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