Back ] Up ] Next ]

Chapter 7

Building COM Components

Building COM Components *

Certification Objectives *

Introduction to Building COM Components *

Creating COM Components *

ActiveX Component Project Types *

Types of ActiveX Components *

In-process (DLL) vs. Out-of-Process (EXE) Components *

Selecting the Type of Component *

Instancing *

Private Instancing *

PublicNotCreatable Instancing *

MultiUse Instancing *

SingleUse Instancing *

GlobalMultiUse/GlobalSingleUse Instancing *

Versioning *

Setting Version Compatibility Options *

When to Use Version Compatibility Options *

From the Classroom *

Implementing COM with Visual Basic *

Making Your Component User-Friendly *

Custom Enumerated Types *

Setting Interface Properties and Descriptions (Procedure Attributes Dialog Box) *

Creating an Enumerator for Your Collection Objects *

Error Handling in Components *

Stupid COM Tricks *

Asynchronous Processing in Visual Basic *

Asynchronous Notifications Using Call-Back Methods *

Providing an Asynchronous Notification Event *

Bundling Data to Improve Performance in Out-of-Process Components *

Designing an Object Model *

Root Objects *

Getting References to Dependent Objects *

Dealing with Circular References *

Debugging COM Components *

Debugging an In-Process Component *

Debugging an Out-of-Process Component *

Debugging an ActiveX Document Component *

Implementing Business Logic in COM Components *

Creating Business Objects *

Certification Summary *

Two-Minute Drill *

Self Test *

 

Certification Objectives

In Chapter 5 you learned to create classes as templates for objects to be reused within your application. It is important that you feel comfortable with these procedures before continuing with this chapter, because COM components are simply classes compiled into external objects that can be reused between applications. In this chapter I am going to expand on the topic of building classes and discuss how to compile them into ActiveX COM components. For this reason, if you feel a little shaky on classes, please review Chapter 5 before attempting to work through this chapter.

Introduction to Building COM Components

The key to building a COM component that is easy to use in a client application and is able to retain compatibility through multiple versions of the component is planning. Keep in mind that the idea behind COM is reusability. If you design a component with an interface that is confusing or limiting, you (or the developer using the component) may be stuck working with this interface over and over. Worse yet, because of COM versioning rules, you may not be able to fix or update the interface to the component in a very graceful way if you want the component to remain compatible with clients using previous versions.

Creating COM Components

Creating the actual component in most cases is not very different than creating classes as described in the previous chapter. I am assuming that you are already familiar with this process and I will focus on the issues that come up when building classes that are going to be compiled as external components. Let’s start by talking about the types of ActiveX components you can create in Visual Basic.

ActiveX Component Project Types

As soon as you start a new project in VB, you are prompted with the dialog box pictured in Figure 7-1 asking you to select a project type. In this chapter I will be talking about the ActiveX project types (with the exception of ActiveX controls, which are complicated enough to get their own chapter later in this guide).

Figure 1: New Project dialog box (note the ActiveX component project types)

When selecting a component type to build, you need to answer two main questions:

  1. Where will the component be used? Will it be used on a form, from code, or possibly inside an OLE container application like Internet Explorer?
  2. What are the performance, stability, and feature requirements for this component?

The answer to the first question will tell you what major category of control you will need to use, that is, ActiveX control, ActiveX code component, or ActiveX document. The second question will determine whether the DLL or EXE subtype is more in line with the functional requirements of the component you are designing.

Types of ActiveX Components

There are five types of ActiveX components shown in the New Project dialog box. They can be categorized into the three major types:

Figure 2: An example of an ActiveX document loaded in Internet Explorer

In-process (DLL) vs. Out-of-Process (EXE) Components

For ActiveX code components and ActiveX documents you can create the component as either a DLL or an EXE file. The primary distinction between these component subtypes is how they are loaded into memory when used by a client (see Table 7-1). DLL components are created in the client applications process space (in-process) and EXE-type components are created in their own process space (out-of-process). ActiveX controls don’t give you a choice in this matter, they can only be compiled as in-process OCX files.

Component Type

In-Process

Out-of-Process

ActiveX Control

ü

ActiveX DLL Code Component

ü

ActiveX EXE Code Component

ü

ActiveX Document DLL

ü

ActiveX Document EXE

ü

Table 1: A sure Sign that a Component Runs Out-of-Process is the .EXE Extension

When I talk about a process, I am referring to an area in memory allocated by the operating system to a particular application. In the Windows implementation of multitasking, each application is compartmentalized into processes. Effectively, each process serves as a virtual computer created for a particular application. In this model, each application can act as if it has the entire machine to itself. Behind the scenes, Windows intercepts all of the requests for memory, hardware devices, and processor time from each process, prioritizes them, and passes them on to the actual hardware.

When I talk about a component running in-process I mean that it is loaded into the same virtual computer space as the client that is using it. By contrast, an out-of-process component gets its own virtual computer and might as well be operating on a separate machine (and sometimes is) as far as the client is concerned. Because of this, out-of-process servers must talk to the client application across process boundaries using a process called marshalling. For this reason, out-of-process components are sometimes referred to as cross-process components.

Now that you understand some of the underlying technical details of where a component runs and what it means to run in one process or another, why should you care? You should care because there are costs and benefits to both models that can make a real difference in the ways your component can be used and how it performs. Here are the key points:

While the EXE/DLL decision is commonly based on whether the in-process or out-of-process loading method is more appropriate, there is a situation in which you would want to select the EXE component type when using ActiveX code components. This is when you want the component to act as an application server. An application server is a component with a dual identity. It can be run like any standard EXE-type application, but can also be used by a client as an OLE server. DLL-type components cannot be used in this fashion.

Exam Watch: Be sure you know which types of components are created in-process and which are created out-of-process. The VB exams always have questions about this in one form or another. Remember: EXE = out-of-process; DLL and OCX = in-process.

Selecting the Type of Component

Here are some sample scenarios with regard to selecting an ActiveX Project Type:

Begin Q&A

I want a component that will run inside of Microsoft Word. ActiveX documents are the logical choice when your component is going to run inside an OLE container application.
I am developing a component to display a graphs of mathematical functions for use in VB clients. An ActiveX control is the best choice when you want interface-centric functionality that can be used as a tool to drop onto a form.
I need an application that can run standalone, but also exposes objects programmatically to VB and C++ clients. ActiveX EXE code components are the only types of components that can be designed to run as application servers.
I want to encapsulate advanced math functions into a component, there will be a lot of information passed between the component and the client, and speed is essential. ActiveX DLL Code Components run in-process and therefore can pass data back and forth to the application much more quickly than out-of-process components.
I am creating a component that will be used in an Active Server Page Script. The component is bound to be crash prone, and I don’t want it to bring down my Web server with it. ActiveX EXE components run in another process that can be terminated by the operative system if necessary without affecting the client.

Instancing

The Instancing property is used to designate classes as available to the client (public) or for internal use only (private). For public classes, the instancing property is also used to control how objects are created when requested by a client. The instancing options for a class are set on the property sheet when the class is selected in the Project Explorer window, as shown in Figure 7-3. Not all instancing options are available to all of the ActiveX component project types, see Table 7-2 for the instancing options available for each project type.

Figure 3: Instancing options for each class are set in the Properties dialog box.

Instancing Setting ActiveX Control ActiveX DLL ActiveX EXE
Private

ü

ü

ü

PublicNotCreatable

ü

ü

ü

MultiUse

ü

ü

ü

GlobalMultiUse

ü

ü

ü

SingleUse

ü

GlobalSingleUse

ü

Table 2: Instancing options available vary depending on the type of project

Private Instancing

The private instancing option is used to designate a class for internal use in the component only. These classes are not made available to the client.

PublicNotCreatable Instancing

Classes defined this way cannot be created from the client application, but can be used by the client if they are created from within the component and then passed to the client. Objects created from this type of class are called dependent objects. A common way to give references to these types of objects back to the client is to provide an externally creatable collection class that exposes an Add method that creates a new instance of the dependent object and returns a reference to the object to the client.

MultiUse Instancing

This setting is used for externally creatable classes. Externally creatable classes are classes that can be created from outside of the component using the CreateObject method or the New keyword. The "Multi" in MultiUse refers to the fact that a single instance of your component can create multiple instances of classes defined with this instancing option. This type of instancing is makes much more efficient use of memory than the SingleUse method because it only needs one instance of the component for all of the objects created from its classes.

SingleUse Instancing

This setting also allows for the creation of externally creatable classes. The only difference between this option and MultiUse is that each instance of the component can only create one instance of the class. Each additional instance of the class that is requested by the client will automatically create another instance of the component. A major benefit of this behavior is that each instance of the class runs on a separate thread of execution that can be independently preemptively multitasked. This behavior also allows objects containing high-risk code to run in an isolated state that will not affect other objects created from the class if it happens to bomb. The downside of using this method is that this type of instancing uses a lot more overhead due to the fact that it must start a separate instance of the component and all of the resources associated with that component for each object that the client requests. This setting is only available for out-of-process (EXE) components and is disabled in other project types.

GlobalMultiUse/GlobalSingleUse Instancing

These settings work just like the MultiUse and SingleUse settings except that you can call the properties and methods of the class as if they were simply global functions in the client. The client does not need to explicitly create an instance of these classes first, because one will automatically be created.

On the Job: The properties and methods of a GlobalMultiUse object are not part of the global name space of the component that provides the object. Within the component itself you must still explicitly create instances of these classes to use the object's properties and methods.

Versioning

The ability of the functionality of a component to evolve without the need to modify the clients that use the component is a fundamental strength of COM. The versioning capabilities of COM fill this need. Versioning is the capability of a COM component to remain compatible with clients using older versions of the component while allowing the developer of the component to further enhance the component. This works because the only link between a client and the component itself is its ClassID. A ClassID is a unique, 128-bit number used to look up the component using the Windows Registry. As long as the new component still supports the same interfaces as the old version of the component and registers itself under the same ClassId, the client will always get the newest version of the component.

Setting Version Compatibility Options

When you are ready to update a component that you have already compiled, it is important to first set the compatibility options so that your component will work successfully with clients using the versions you have previously released. To set the compatibility level for your component, use the Version Compatibility section of the located on the Component tab of the Project Properties dialog box, shown in Figure 7- 4. There are three compatibility options available.

Figure 4: The Component tab of the Properties dialog box is used to set compatibility options for the project

In order to maintain binary compatibility with previous versions of the component, you must not remove or change any of the existing interfaces in the component’s classes. The single exception to this rule is that you are allowed to add optional parameters to an existing interface while still maintaining compatibility. If the compiler detects an incompatibility while compiling with this option set, it will alert you with the warning dialog box shown in Figure 7-5. This dialog box gives you the option to break or preserve compatibility for the class interfaces that have not changed. Selecting the Break Compatibility option will cause the project to be compiled as a brand-new component with a new ClassID. This new component version will no longer support clients using previous versions of that component. If you must break compatibility, it is recommended that you change the name of the project so that the new component will not overwrite previous versions of the component already on the user’s hard disk.

Figure 5: By changing the cmd parameter from a string to an integer, binary compatibility has been broken

When to Use Version Compatibility Options

Here are some sample scenarios for when to use each of the compatibility options.

Begin Q&A

While working on a new version I need to make interface changes that will break backward compatibility. Use the No Compatibility option. Remember to change the filename of your component so that the incompatible version won’t overwrite earlier versions on the users’ hard disks.
I am compiling the first version of my component. Consequently, I do not have previous versions of the component to maintain compatibility with. Use Project Compatibility. However, as soon as you have compiled the component, go back and set the compatibility option so you don’t mistakenly compile an incompatible version of the component the next time you update it.
I am updating a component and I want the new version of the component to work seamlessly with the other clients using an older version of the same component already on the user’s machines. Use Binary Compatibility.

From the Classroom

Implementing COM with Visual Basic

Many COM component advantages occur through functionality contracts between the component author and consumer or developer. Known as interfaces, these contracts are groups of functions that encapsulate functionality into objects programmatically accessed through the interfaces that are implemented according to the COM standard.

Now this is all well and good, but what does this mean in layman’s terms? Creating a COM component in VB begins by creating an abstract class. This class is used to define the interfaces of the COM components; it provides no actual implementation code of any kind. Additional classes are created actually implement the defined interfaces, using the Implements keyword in your code. COM components can provide polymorphism by exposing different objects with a function of the same name, hence hiding the fact that the function may generate different behaviors. COM components may also involve developing a hierarchy of object within your component. Why would this be desired? A COM component might contain a single object that consists of so many interfaces and functions that dividing the complexity into separate, interdependent objects can significantly reduce the burden of using the component. This entails creating an object model for your component, which may be saved as a TLB or OLB file. The object model may also be saved internally within your ActiveX EXE or DLL compiled binary file.

Regarding the exam, be sure to be familiar with early and late binding, and how it would be implemented in a COM component. Be familiar with the purpose of iUnknown, QueryInterface, and iDispatch. Finally, be aware of the proper syntax for creating and manipulating objects defined within a COM component.

By Michael Lane Thomas, MCSE+I, MCSD, MCT, A+

Making Your Component User-Friendly

In this section I will describe several ways to make components that make life simpler for developers using your component in their client application. Remember, in most cases that developer using your component is you! Remember that this code is supposed to be reusable. It is worth spending a little time now to save yourself a lot of time later.

Custom Enumerated Types

Enumerated types allow you to use plain text identifiers in place of numbers or complicated parameters in your component. The code below illustrates how to set up the properties in your component to use the IntelliSense technology when using the objects created from your classes. Notice how the list of acceptable values is displayed in the VB IDE when setting the PetType property, as shown in Figure 7-6.

Public Enum PetTypes
Cat = 1
Dog = 2
Fish = 3
Bird = 4
Wumpus = 5
End Enum

Public Property Let PetType(NewPetType as PetTypes)
mPetType = NewPetType
End Property

Figure 6: Using enumerated types is a good way to make using your component easier

This does more than just save the user a few keystrokes when writing the client application. It also makes the code much more readable. In this example, it is much more evident what is going on when the enumerated type is used:

‘*** This will work but, but it requires you to look up
‘*** animal number 5 to figure out what is happening in
‘*** this line of code

MyPet.PetType = 5

‘*** Using the enumerated type clears things up

MyPet.PetType = Wumpus

Setting Interface Properties and Descriptions (Procedure Attributes Dialog Box)

You have already seen how the Procedure Attributes dialog box can be used to set the data binding properties of a class. Now I will revisit this dialog box to show you how the top two-thirds it can be instrumental in making your components easier to use. As you learned in the previous chapter, VB adds type library information to the DLL or EXE file when you compile an ActiveX component. What you may not know is that the Procedure Attributes dialog box (shown in Figure 7-7) allows you to customize the way the class is documented in that type library. This feature of VB allows you to compile documentation right into the components EXE or DLL file. The following options can be set for each member in the classes your component exposes.

Figure 7: Entering a description in the Procedure Attributes dialog box adds a description to your property or method in the Object Browser dialog box. This can be a big help to developers using your components

Figure 8: The description from the Procedure Attributes dialog box now appears in the Object Browser dialog box

'*** Both of the following statements retrieve the name property
MyName = ObjPerson.Name
MyName = ObjPerson

Creating an Enumerator for Your Collection Objects

There is a hidden feature of the ProcedureID field in the Procedure Attributes dialog box described above. You can set this property to allow a specifically designed property to act as a custom enumerator for collection type objects. Adding an enumerator to a collection object allows you to use VB’s For Each…Next statements to iterate through the items in your collection. To enable this functionality, define a property of your class exactly as shown here:

Public Function NewEnum() As Iunknown
Set NewEnum = mcolPeople.[_NewEnum]
End Function

After creating this property of the collection, open the Procedure Attributes dialog box and type -4 into the ProcedureID field on the Advanced tab. The last step is to select the Hide This Member option on the Procedure Attributes dialog box.

Enabling the For Each…Next functionality for your classes makes them work more like the built-in collection objects that the developers using your components will be used to.

On the Job: The For Each…Next construct also has the additional benefit of being somewhat faster in iterating through a collection than looping through a collection using a For…Next loop using the index property of the collection to retrieve member objects.

Error Handling in Components

The preferred method for dealing with an error in a component is to correct the error silently and continue on normally. Of course, this is not always possible. When an error cannot be dealt with internally, a component should pass the error up the chain to the client using the Raise Method of the Err object. You can use enumerated types to assign meaningful names to the error codes your component uses to make things a little easier on the developer using your component. Here is an example of error handling in a component:

Public Enum WeaponType
RIFLE= 1
SPITWAD = 2
MISSLE = 3
End Enum

Public Enum AnimalType
WOMBAT = 1
BABY_SEAL = 2
ELEPHANT = 3
TIGER = 4
End Enum

Public Enum SAFARI_ERROR_TYPE
SAFARI_ERR_SUCCESS = 0
SAFARI_ERR_INVALID_WEAPON = 1
SAFARI_ERR_OUT_OF_AMMO = 2
Enum

Public Sub Hunt(Animal as AnimalType, Weapon as WeaponType)
‘*** Error Handling
Dim ErrMsg as string
If (Ammunition = 0) then
Select Case Weapon
RIFLE: ErrMsg = ‘Out of Bullets.’
SPITWAD: ErrMsg = ‘Out of spit.’
MISSILE: ErrMsg = ‘Out of Missles.’
End Select

‘*** Send the error up to the client
Err.Raise NUMBER:=SAFARI_ERR_OUT_OF_AMMO,, ErrMsg
Exit sub
End if
End Sub

On the Job: In the previous code segment, I used the error values zero through three for my component. In practice it is probably not a good idea to use these values as you tend to trample on error handling inside the client. It is recommended to add the VB constant vbObjectError to whatever error codes your component uses. This gives a good buffer for the error numbers used by VB and/or the client application.

A client application should only receive errors from the components it calls directly. You should not allow an error to be passed up the chain two levels. It is your component’s responsibility to either deal with the error or translate the error message generated by the subcomponent into one that is meaningful in terms of your component. If the error in the subcomponent contains information that might be useful to the client in handling or debugging the error, it should be incorporated into the error generated by your component. Here is an example of this technique:

Public Function Calculate(ByRef V as Long)
On Error Goto Error_Handler
Dim VC as new Velocity.Calculator
Dim Result as long

Result = VC.GetVelocityCalculation(V)

Exit Function

Error_Handler:
Select Case Err.Number
Case vbObjectError + 1000
'*** Error 1000 means the velocity is negative
'*** Get the absolute value and try again
V = abs(V)
Resume
Case vbObjectError To (vbObjectError + 65536)
‘*** Unanticipated errors Velocity Component.
Err.Raise ERR_VEL_UNK + vbObjectError,, _
‘Error Calculating Velocity’
exit function
End Select
End Function

Stupid COM Tricks

This section presents some techniques available to you as a VB ActiveX component author. These items are clever tricks (the title of this section notwithstanding) for improving performance and implementing difficult interactions between clients and servers. You might want to note that these techniques have been explicitly mentioned on the list of topics covered by the Visual Basic 6 Desktop Applications Exam.

Asynchronous Processing in Visual Basic

Normally, only one line of code in a VB program can be executing at a time. This behavior extends to COM objects as well. For example, when you call a method of a COM object, VB waits patiently for the method to finish executing before resuming on the next line of code in the client application. When a component is called in such a way that the component code must run its course before returning control to the client the component code is said to be to blocking or running synchronously.In most cases, this is exactly how you want your program to act. You generally want whatever that method was doing to finish before giving control back to the client because it probably sets the stage for whatever comes after that call in the application. Blocking is caused by the fact that each process can only have one piece of code executing at one time.

When you run an in-process (DLL) component, it is loaded in the process space of the client, thus, the component and the client must share the virtual machine created for the process, and only one piece of code between them can execute at one time. By using out-of-process components you can get around this. As you’ll recall, out-of-process components get their own virtual machine (process) to execute code on, consequently, the component and the client can each receive their own time slices and can effectively run asynchronously. This property of out-of-process components allows the client to make a call to the functionality in a component and go about its business while the component continues to process the request. The component will then notify the client whenever it is done with the task or when a certain state is reached. This asynchronous processing method can be accomplished by one of two methods. These two methods are the call-back method and the event notification method. Both require some coding on both the client and server side of the relationship. In this section I will discuss how to take advantage of this benefit of out-of-process components.

Asynchronous Notifications Using Call-Back Methods

A call-back is just what its name implies. The application makes a call to a function, method, and so on, and goes about its business. When the external function has completed the task, it calls the application back by calling a function in the client designated to answer it. Although Windows programmers have done this for years, this technique is fairly new to VB.

On the component side of the component you must:

  1. Create a new ActiveX EXE-type project.
  2. Define an externally creatable class to manage each task or notification that will be handled by the component.
  3. Create a type library containing the interface (or interfaces) that clients must implement in order to receive notifications. This interface must include all methods the component will call to notify the client. To create an interface, open a new ActiveX EXE or DLL project and add the properties and methods to a class module but don’t add any code to them. This class will serve as a template of the class that will be called back in the client application. Name the class with the interface name you want to assign to the component. The standard method for interface names is a capital I followed by the class name, for example, IFileIONotify. Lastly, compile this component template into a DLL or EXE file. This compiled DLL or EXE file now contains a type library that can be used by an Implements statement in the component so that the component knows about the calling conventions for the objects in the client that it has to call back.
  4. Add the DLL or EXE to the project of the component by adding a reference in the References dialog box.
  5. Add methods to the externally creatable class(es) that activates the code that will call back the client. It should take at least one parameter that is declared as the interface type we created in Steps 2 and 3. This parameter is used to pass a reference to an object in the client’s process that the external component can use to make the call-back as shown in this example:
  6. Public Sub OpenFileAsynch(Byval iCallBackObject as IFileIONotify)

  7. Add the code to the component’s classes to do whatever it is that the component does when called. At the point that the component is supposed to call back the client call the appropriate call-back method of the interface passed into the component as shown in this example:

ICallBackObject.IFileIONotify_FileOpenComplete

On the Job: You can also use the MKTYPLIB utility included in the Tools directory of Visual Studio to create a type library if you are feeling adventurous.

On the client side of the component you must:

  1. Add a reference to the type library created in Steps 2 and 3 above.
  2. Create a class with instancing set to PublicNotCreatable that implements the interface using the Implements keyword as shown in this example:
  3. Implements IFileIONotify

    In this new class re-create all of the properties and methods exactly as defined when creating the type library. This time, however, you actually want to add the code to do whatever these methods are intended to do. These methods and properties will be the ones called when the component calls back, so put code into these routines to do whatever you plan to do when the call-back occurs. Note: You must exactly reproduce all of the members that you put into the interface as defined above. If you don’t need one of the members in this particular application you can always just add the sub or function header with comments inside for the code. Here is an example method that would be called by the callback defined in Step 6 above:

    Private Sub IFileIONotify_FileOpenComplete()
    MsgBox "File Opened Successfully."
    End Sub

  4. Add a reference to the EXE component you created above (not the type library, but the component that you expect to call your client back), and call the method to initiate the code that will call you back. Make sure that you have an instance of the class that receives the call-back available when the call-back happens. A good way to accomplish this is to make the object reference a public variable at the module level.

At this point the call-back is all set up and ready to use. I realize that these steps are a little complicated so I have included Figure 7-9 to make these steps a little easier to comprehend.

Figure 9: The component uses the type library from the template DLL to determine the calling interface for the callback to the client application

Providing an Asynchronous Notification Event

You can also use events in VB to provide notification to the client after the component has completed its task. The benefit of this approach is that events call back the client anonymously. The object just throws the event notification to whichever clients are listening for them. This approach is also much easier to implement. The steps are as follows.

First we set up the component to notify the client of when the requested task is complete or a specific condition arises.

  1. Create a new ActiveX EXE project.
  2. Define an externally creatable class that can be called by the client to initiate the notification process in the component.
  3. Declare one or more events that will be raised when it is time to notify the client of a condition or that a task is complete.
  4. Create a separate function or subprocedure that will raise the event to notify the client when a task is complete or something has happened in the component.

Here is a sample component class module that will start a file copy and return control to the caller, then notify the client via an event when the copy is done.

Public Event FileCopyComplete()

Public Sub CopyFile(FileName as string, NewFileName as string)
'Start File Copy process running asynchronously
StartCopy(FileName, NewFileName)
End Sub

Private Sub CopyFile(FileName as String, NewFileName as string)
'Put code here to actually copy the file

'Notify the client when copy is complete
RaiseEvent FileCopyComplete()
End Sub

Next we will set up the client to tell the component to respond to the events generated by the component.

  1. Add a reference to the ActiveX EXE component in the client project using the References dialog box.
  2. Declare an object reference to the class defined in the component above using the WithEvents keyword.
  3. Write an event handler for the event raised by the component.
  4. Write code to request an instance of the component’s class, and point the object reference declared in Step 2 to the new instance.
  5. Write code to call the methods of the object in the component that initiates the notification.

Here is some sample code to call the component described in the previous example.

Public WithEvents FileCopierObject as clsFileCopy

Public Sub DoFileCopy
'Start File Copy process running asynchronously
Set FileCopierObject = new clsFileCopy
FileCopierObject.CopyFile(FileName,NewFileName)
End Sub

Public Sub FileCopierObject_FileCopyComplete()
MsgBox "Notification: File Copy has been completed."
End Sub

On the Job: Events cannot have named arguments, optional arguments, or ParamArray arguments. If you must use one of these constructs you will have to use the call-back method.

Bundling Data to Improve Performance in Out-of-Process Components

As mentioned earlier in this chapter, making calls to properties and methods can be a fairly slow task when the client and component are not running in the same process. This was one of the factors that would push you away from the out-of-process or EXE-type components when opting for speed in your application. When designing an out-of-process component you can minimize this performance sacrifice with the following technique.

A lot of the overhead required to make calls across process boundaries is not associated with passing the data but with just opening a channel of communications between the two processes. With this in mind, we can minimize the damage by sending the same amount of data in fewer calls to the component’s properties and methods. Take, for instance, the following example:

‘*** Prepare for call to Purchase Ticket
objTicket.Smoking = True
objTicket.Class = TICKET_CLASS_COACH
objTicket.CreditCardNumber = PassengerCardNo$
objTicket.FrequentFlierNo = PassengerFreqFlierNo$

‘*** Purchase the ticket
objTicket.PurchaseTicket

Although this method will certainly work and is perfectly acceptable in terms of good programming practices, notice that the code initiates five calls to the properties/methods of the component. If the Airline component is in a separate process from the client executing the code, each of those calls is very costly. The following code executes the same functionality in a single call to the Ticket object:

‘*** Purchase the ticket
objTicket.PurchaseTicket(Smoking:=True, Class:=TICKETCLASS_COACH, CreditCardNumber:=PassengerCardNo$, FrequentFlierNo:= PassengerFreqFlierNo$)

A side benefit of using this method is that the programmer using a component designed to use the second method can immediately see what information is necessary to execute PurchaseTicketMethod by looking at the method definition. This is one more example where forward-thinking design can make a big impact on the performance of a component.

Designing an Object Model

The object model of a component is the blueprint for how clients will access the objects exposed by your component. The best implementation of an object model is also usually the simplest one. Navigating complex object models can be not only confusing but it also can take a lot of unnecessary time to dereference all of the objects in the path the object you are working with. Following are some pointers on designing a good object model.

Root Objects

The name of the root object varies depending on the type of component. For example, application servers usually use the name Application for the root object, while in-process components usually use a name that is more indicative of the functionality of the component and that has a name very similar to the name of the type library.

The root object’s function is twofold. Its first job is to pass out dependent objects (instanced as PublicNotCreatable) to the client application as they are requested through properties that are normally defined as collections. Because the root object is usually the gatekeeper for all of the objects lower down in the object model, you almost always want to instance it so that it is externally creatable, that is, use any of the options except Private or PublicNotCreatable. The second job a root object performs is to expose the properties and methods that affect the entire component. For example, if you wanted to allow the client to run the component in Silent mode and suppress all messages normally sent to the user by the component, you might implement a SilentMode property in the root object.

Getting References to Dependent Objects

The most common method that root objects expose dependent objects to the client is an Add method. An Add method is exposed by root objects that expose collections of dependent objects. In the add method, the client specifies the parameters for how the new instance of the object should be created. The root object will create the object, add it to a collection that is normally exposed through another property, and return a reference to that object. Here is an example of how this could be coded in a Book object that contains a Pages collection of Page objects:

‘*** This code is contained in the Book Class

‘****Local storage of property value
Private mPages as Pages

Public Property Get Pages as Pages
Pages = mPages
End Property

‘*** This code is exposed in the Pages class

‘****Local storage of property value
Private mPagesCol as Collection

Public Function Add(PageNumber as integer, PageText as string ) as Page
Dim NewPage as Page

NewPage.PageNumber = PageNumber
NewPage.PageText = PageText
mPagesCol.Add NewPage
Set Add = NewPage
End Function

‘This is code in the client used to get a page object
Public Sub DoBookThing
Dim MobyDick as New Book
Dim NewPage as Page
Set NewPage = MobyDick.Pages.Add(1,"Call me Ishmael…")
End Sub

Dealing with Circular References

Containment relationships, like the one shown in the example above, allow you to navigate through a hierarchy from a high-level object to any of the objects it contains. Normally these containment relationships flow down the chain with each level containing the level below it. However, this model also allows for objects to be linked to objects higher in the chain as well. The most common form of circular references is the parent property. A parent property is a property used to reference the parent object that contains it. In most cases it is a good idea to avoid structures like this in VB because they violate the rules of encapsulation and can cause problems tearing down the object hierarchy when you are done with it. I will use the Book component to illustrate this point. Say we have created a Book object that contains a Pages property that references a Custom Pages Collection object, which in turn references Individual Page object. At some point in our program after we have been using this object model in our library program we destroy the object by setting the Book object to Nothing. Here is the sequence of events:

  1. The Book object in the client is set to Nothing. The reference counter drops to zero for the Book object and all of the local variables are released.
  2. One of the local variables is the member variable that points to the Pages collection. When this variable is released it lets the Pages object’s reference counter hit zero. The Pages object is unloaded along with its local variables.
  3. As each of the references in the Pages object to the individual pages is wiped out, their reference counters hit zero, and they are released until the entire object structure is unloaded from memory.

Now let’s see what happens when we add a parent property to the Pages collection that points back to the Book object that contains it.

  1. The Book object in the client is set to Nothing. The reference counter drops to one (remember the Pages collection is still pointing at it). The Book object remains in memory because it still has a reference pointing to it.
  2. You now have no way to get back to the object hierarchy because you no longer have a reference to any object in it from the client.
  3. The objects remain in memory, inaccessible, until you shut down VB and the inevitable GPF occurs when one of those objects tries to reference memory in the process space that is now gone.
  4. Oops!

If you must set up parent properties, the best way to avoid the above problem is by explicitly setting the parent property to Nothing before destroying the parent object. Here is an example:

‘**** Clear the parental reference
Set MyBook.Pages.Parent = Nothing

‘****Now it is safe to destroy the parent reference.
Set MyBook = Nothing

Debugging COM Components

A component’s external nature requires special techniques to debug the object. Here is some guidance on how to successfully test and debug an ActiveX component.

Debugging an In-Process Component

To debug an in-process component you will need to create a client to test the component because a component cannot run on its own. To set up a client to test your application:

  1. With your component project open, select the Add Project… item from the File menu and add a new Standard EXE-type project to the development environment.
  2. Make sure the new EXE project is selected in the Project Explorer window and add a reference to the component using the References dialog box. Note: VB sets up a temporary registration for the component when it is loaded into the current project. Any projects you add to this project group can use the component for testing purposes without the bother of registering the component first.
  3. Add code to the EXE project to instantiate each of the objects in your component and use their functionality.
  4. Set breakpoints in either the client or component code as necessary, then run the project group by selecting Start With Full Compile from the Run menu. You will be able to step through code and stop on breakpoints in both the client and component just as you would in a single standard EXE project.

On the Job: In-process components will not unload while running in the development environment, even if all references to objects are released by the test project, and all other component shutdown conditions are met. The only way to test DLL unloading behavior is with the compiled component.

Debugging an Out-of-Process Component

Out-of-process means the component is running in a separate process from the client application. Therefore you need to run your test program in a separate process. This means starting a separate instance of the VB.

To test an out-of-process component:

  1. Compile your component into an ActiveX EXE project. The executable is only used to allow the test project to keep its reference. It is not used for debugging the component.
  2. If your component is an application server, set the Component mode in the Start Mode box on the Components tab of the Project Properties dialog box.
  3. Start your component by selecting the Start option from the Run pull-down menu or by pressing Ctrl-F5. Running your component makes VB switch the Registry entries for the component from the compiled version to a temporary reference to the one running in the VB IDE. This allows you to use breakpoints, watches, and all of the other debugging features of VB with your component.
  4. Open a second copy of VB and start a new standard EXE-type project.
  5. Add a reference to the ActiveX EXE project you are testing in the References dialog box. If it does not appear in the References dialog box, check that it is still running in the other copy of VB. If your project is running and you still don’t see it in the References dialog box, make sure that the component has at least one externally creatable class in the project.
  6. Create the test project that calls the methods and properties of each public class provided by the component you are testing.
  7. If testing reveals that changes need to be made to the component—and there will be changes—make sure to recompile the component with the Project Compatibility or Binary Compatibility option selected after you make the changes and before you restart the test application.

Debugging an ActiveX Document Component

An ActiveX document component is yet another special case for debugging components. Because these projects run only in OLE container applications that support ActiveX documents like Word or Internet Explorer, they can not normally be embedded in a VB client application. This means that you will not be able to use either the in-process or out-of-process debugging methods described above on these components.

Don’t be discouraged here—you can still use all the tools available in VB such as setting breakpoints, watching variables, using debug statements, and so on when debugging ActiveX documents, but you will need to use the Internet as the test client. Set the project so that it starts in Internet Explorer for debugging by selecting the Start Component radio button on the Debugging tab of the Project Properties dialog box, shown in Figure 7-10.

Figure 10: If you have more than one UserDocument in your component you can specify which one you want to debug on the Debugging tab of the Project Properties dialog box

At this point you should be able to debug the ActiveX document using Internet Explorer as the test client. If you need to put an ActiveX document in Break mode while it is running in Internet Explorer, press Ctrl-Break. Also, because the container that hosts the ActiveX document is using the objects that the ActiveX document provides, Internet Explorer may throw a bunch of errors if you stop the project while the document is being displayed. To avoid this, close Internet Explorer before pressing the Stop button in VB.

Implementing Business Logic in COM Components

The modular nature of COM components and the ability to use DCOM to seamlessly run components on separate machines makes them a good choice for implementing multitiered applications. A multitiered application is simply an application that has its functionality broken out into components that are each tasked with a particular aspect of using the application. For example, in the most common two-tier model is a server-side database. The database application is broken down into two pieces: the client piece, which presents the information to the user and collects information from the user, and the server piece, which receives requests from the client, executes them against its data, and returns the result to the client.

The main benefit in this type of client/server model is that the code executing against the data is run on a machine much closer to the data. Consequently it can search the database more quickly, without the bottleneck of going over the wire to the network disk. Also, less network traffic is introduced because only the answer is returned, not the entire database for parsing by the application running on the local machine. Yet another benefit of this approach is that when you need to increase database performance you only need to upgrade the server machine rather than every workstation running the client.

This model worked so well that programmers kept finding ways to add levels (or tiers) to this structure. It is now very common to hear about a third tier in the mix called a business logic or business services layer. The purpose of this layer is primarily to ensure that the data moving into the database has been checked and or massaged to fit into the database so that the business rules are enforced and data integrity is maintained. In this model the business logic component acts as a translator of sorts for the data. By distributing the load over three components, we can split the processing onto yet a third machine, further improving performance in some cases. Although it is common for multitier architectures to split each tier onto a separate machine, this is not necessary. Many times this is done to make a logical service more componentized and easier to swap out should the need arise.

Creating Business Objects

Creating a middle layer is all about defining a good object model that resembles the data your application uses. The first step is to define the objects that your system will expose. These objects could represent employees, invoices, or orders, for example. At this stage it is a good idea to put down the computer manuals, forget programming for a while, and think about your task in real-world terms. Answer the following questions about your data:

After answering these questions, an object model will begin to emerge. Fine-tune this model as you go working through different scenarios of working with the objects you are defining. I would highly recommend modeling these structures on paper before attempting to code them. It is very easy to get started on the wrong track with object models to the point where you have invested too much time to go back and correct the model. I have said it before and I will say it again—planning may seem time-consuming up front, but not planning is twice as time-consuming later in the application’s life cycle.

Let’s put these new skills to use and build an ActiveX document that functions as a mortgage calculator that could be used on a Web page for a mortgage brokering company.

Exercise 7-1 Creating an ActiveX Document

  1. Open VB and select the ActiveX Document DLL project type. This will create a new project group containing a single UserDocument object. For the most part you can treat these objects just like forms.
  2. Open the Project Properties dialog box and set the project name to Rcalc and close then close the dialog box to save the change.
  3. Open the UserDocument1 object in Design mode and change its name property in the property sheet to udRateCalc.
  4. Add the controls as shown in Figure 7-11 to the UserDocument object in the same way you would to a standard VB form. The TextBox control names are exactly as shown in the figure. The command buttons should be named cmdCalculate and CmdGoHome.
  5. Figure 11: Your UserDocument should look like this when you have completed Step 4

  6. Add the following code to the click event of the Calculate Now button:
  7. Private Sub cmdCalculate_Click()
    Dim Payment As Currency
    Dim Term as double
    Dim MonthlyRate as double
    Dim Principle as Currency

    ‘Divide the Rate by 12 to get the monthly rate
    Let MonthlyRate = cDbl(txtRate/100)/ 12

    ‘Convert the Term and Principle values to Doubles for call to PMT
    Let Term = cDbl(txtTerm)
    Let Principle = cDbl(txtTerm)

    ‘Don’t forget those seldom used VBA functions
    Let Payment = Pmt(MonthlyRate, Term, Principle)

    ‘Tell the user the damage
    MsgBox "Your monthly payment would be " & CCur(Abs(Payment))
    End Sub

  8. The UserDocument object, which is available in ActiveX document projects, exposes a Hyperlink property that allows you to force the container application to navigate to another document, replacing itself in the container. Add the following code to the Click event of the Go Back to Our Home Page button to allow the user to navigate to a different page.
  9. Private Sub cmdGoHome_Click()

    ‘Send the user off to whatever website you designate
    UserDocument.Hyperlink.NavigateTo "http://www.microsoft.com"

    End Sub

  10. Now that we have our form designed, compile it using the Make… option from the File menu. When prompted, the name of the compiled DLL will be RCALC.DLL.
  11. Unlike compiling other project types, compiling an ActiveX document will produce several files. In this Case an RCALC.DLL and UDRATECALC.VBD file will be created. If we had added additional UserDocument objects to the project, there would be VBD (VB document) files for each UserDocument. The VBD file is the one that is opened from the OLE container application and links to the functionality of the ActiveDocument DLL or EXE.
  12. Let’s open our new VB document. A quick way to do this is to open Internet Explorer and drag and drop the VBD file onto it. You can also open the ActiveX document by typing the path or URL to it in the address bar or even through a hyperlink on a Web page pointing to the VBD file. Open the VBD file in Internet Explorer using one of these methods.
  13. Fill in values for the fields and click the calculate button to see what the car payments would be on that new Ferrari.
  14. Now click the Go Back to Our Home Page button to jump to Microsoft’s Web site to look up dates for taking your next VB Exam.

Certification Summary

This chapter has covered a lot of material. I have discussed how to design your components to make the most efficient use of the resources on the client machine by selecting the proper component type, subtype, and instancing properties. You should have also picked up several new techniques that will make the component more accessible to the developer using it by using a sound object model and taking advantage of some of the built in features associated with class modules in VB. Hopefully the special attention I paid to the gotchas of component and class will help you avoid making costly mistakes in your components like breaking compatibility or creating circular references that can be very difficult to debug. Remember, above all, planning is the key to developing good components.

Two-Minute Drill

Self Test

The following Self-Test questions will help you measure your understanding of the material presented in this chapter. Read all the choices carefully, as there may be more than one correct answer. Choose all correct answers for each question.

  1. Which of the following situations is not a good fit for an out-of-process component?
    1. The component will be run on a separate machine from the client using DCOM.
    2. The component needs to run very quickly and exchanges a lot of data with the client.
    3. The client needs to be able to call the component and not have to halt execution until the component is finished processing the tasks.
    4. The component must also act as a standalone application.
    5. The component must work in both 32- and 16-bit clients.
      B. The component needs to run very quickly and exchanges a lot of data with the client. In-process servers are much better suited for applications where speed is critical.
  2. What types of projects cannot be compiled to run as out-of-process components?
    1. ActiveX documents
    2. ActiveX code components
    3. ActiveX controls
    4. None of the above
      C. ActiveX controls are compiled as OCX files, which always run in-process.
  3. Which of the following instancing options is not available to ActiveX DLL components?
    1. Private
    2. PublicNotCreatable
    3. GlobalSingleUse
    4. MultiUse
    5. GlobalMultiUse
      C. GlobalSingleUse instancing is only available in out-of-process ActiveX EXE projects.
  4. You are designing component A, which is used by component B. What should happen if an error occurs in component B?
    1. Components A and B should not trap the error, allowing it to propagate to the client for handling there.
    2. Component A should try to handle the error and if it can’t, pass it up to component B, which will try to handle the error. If it also can’t deal with the error, it will translate the error message in terms of itself and pass it up to the client.
    3. Component B should in no situation pass an error up to component A.
      B. Component A should try to handle the error. The error should always be handled as close as possible to the source. When it can’t be handled it should only be passed up one level. If your component is to exhibit good encapsulation, it should effectively hide the fact that it uses subcomponents from the client application.
  5. A class has its instancing property set to MultiUse and the client requests two instances of the object provided by the class. Which of the following is true?
    1. MultiUse instancing does not allow a second instance to be created from the same component so an error is generated.
    2. Two instances of the component are created, one to handle each object instance.
    3. A single instance of the component creates both objects for the client.
      C. A single instance of the component creates both objects for the client. MultiUse Instancing allows a single component to create as many instances of that class as are requested by the client application without the need for another instance of the component. SingleUse instancing requires a separate component for each instance of the objects created by the class.
  6. Which instancing option is used to create dependent objects?
    1. Multiuse
    2. PublicNotCreatable
    3. GlobalSingleUse
    4. Private
      B. PublicNotCreatable is used to create dependent objects because they allow objects to be used outside of the component, but they must be created within the application.
  7. Which of the following versioning options can be used to make sure a component is compatible with earlier versions of the same component?
    1. Project compatibility
    2. No compatibility
    3. Version compatibility
    4. Binary compatibility
    5. All of the above
      D. Binary compatibility. Although project compatibility can preserve compatibility for debugging purposes, it is not sufficient to ensure backward compatibility with previous versions of the component. Only binary compatibility ensures that your component is version-compatible with previously released versions of your component.
  8. How can you set the description text for a member of a class to be shown in the Object Browser dialog box?
    1. Create a Description property in each class you want to document.
    2. By setting the Description field in the Project Properties dialog box.
    3. By setting the Description field in the Procedure Attributes dialog box.
    4. By adding a specially formatted comment to the top of each method or property.
      B. By setting the Description field in the Project Properties dialog box. Setting this property adds the description into the type library that is compiled into the component.
  9. Which of the following components would need to use marshalling to talk to the client application?
    1. ActiveX controls
    2. ActiveX document DLLs
    3. ActiveX EXE code components
      C. ActiveX EXE code components. Out-of-process components like ActiveX EXE code components and ActiveX EXE documents use cross-process marshalling to talk to one another because they each run in their own process space.
  10. Which of the following is the best option for a component that will probably crash frequently?
    1. ActiveX EXE code component
    2. ActiveX DLL code component
    3. ActiveX document
    4. ActiveX server
      A. Active X EXE code component. By running in a separate process, an ActiveX EXE insulates the client from GPF-type errors in the component.
  11. Which of the following structures is a common cause of circular object references?
    1. Property containing a collection of another type of object in the component
    2. A HasChildren property
    3. A Parent property
    4. Too Many Root Nodes
      C A Parent Property. The Parent property can cause circular reference problems if you are not especially careful when tearing down the object structure.
  12. When debugging an out-of-process component, why must you remember to compile the component after each change?
    1. Because the test project is using the compiled version of the component and must be updated.
    2. To update the interface in the Registry so the client can access the new functionality of the component.
    3. Both A. and B.
      B. To update the interface in the Registry so the client can access the new functionality of the component. The test project does not use the compiled component, but rather the one in memory in the debug environment in the other instance of VB. While the component is running in Debug mode, the Registry entry for it is temporarily changed to reference the component that is in Debug mode.
  13. When creating a test project to debug an ActiveX EXE project you don’t see a listing in the References dialog box for the component. What could be the problem?
    1. The component is not in Run mode.
    2. The component does not expose any publicly creatable classes.
    3. The component needs to be registered using REGSVR32.
    4. All of the above
      A (the component is not in Run mode) and B (the component does not expose any publicly creatable classes). The component must be in Run mode and expose externally creatable objects to show up in the References dialog box.
  14. Which of the following instancing methods is a good choice for a Root object that is designed to be externally creatable?
    1. Public Not Creatable
    2. Private
    3. MultiUse
      C. MultiUse. Of the options listed, only C can be used to create an object that is externally creatable. Root objects normally should be instanced as externally creatable so that they can be accessed from the client application.
  15. Which of the following cannot host ActiveX documents?
    1. Internet Explorer
    2. Microsoft Word
    3. Microsoft Binder
    4. VB forms
      D. VB forms. ActiveX documents can not be used in VB forms; the only way to use an ActiveX document in VB is to insert it inside the Internet Explorer Web Browser ActiveX control.
  16. Which of the following instancing options allows you to refer to call the properties and methods of an object without first creating an instance of that object?
    1. Private
    2. GlobalMultiUse
    3. SingleUse
    4. GlobalSingleUse
      B (GlobalMultiUse) and D (GlobalSingleUse). GlobalMultiUse and GlobalSingleUse are the two instancing options designed to allow this behavior.
  17. What will happen when the following statement executes on the object named MyObject?
  18. Dim V as variant
    V = MyObject

    1. V will be assigned to the value of the default property if one has been defined.
    2. V will now contain an object reference to an instance of MyObject.
    3. V will be set to a long that is a handle to an instance of MyObject.
      A. V will be assigned to the value of the default property if one has been defined. Setting the default value allows you to use coding shortcuts like this to get commonly used property values. The default property is also known as the Value Property.
  19. What is the term that describes methods that execute using synchronous processing?
    1. Call-backs
    2. Blocking
    3. External component execution
    4. MultiUse instancing
      B. Blocking is a term used to describe the behavior of a method that makes the client wait until it is done processing before continuing with the next line of code.