VB Class Modules

There are three basic tasks in creating an Object Linking and Embedding (OLE) Automation Server: class creation, OLE Automation, and remote automation. I will deal with each of these topics separately. This month in Part 1 of this series, I'll show you how to create and use VB classes and objects. Part 2, OLE Automation, will show you how to create object collections, handle errors, and build object models. And Part 3, Remote Automation, will discuss how to distribute these objects across the network, where they're actually running on remote machines.

Class modules, new in Visual Basic 4.0 (VB4), allow you to create encapsulated, reusable objects. When incorporated into an OLE Automation server, these objects can be used by external applications. If you use the remote-automation capability of the Enterprise Edition of VB4, they can even be located on remote machines.

VB4 classes encapsulate both code and data--in object-oriented terms--as methods and properties, respectively. These concepts are already familiar to those who have used VB's forms and controls in the past. VB has always supported properties and methods, as well as events: In fact, the language has built-in events, methods, and properties. You can create new properties and methods, but not events, for the classes you define. In fact, VB forms and controls have always been defined by classes, and handling them now as objects makes even more sense. For instance, the following code for creating a form at runtime has always worked: Dim frm as Form, Set frm = New Form1.

Many classes are virtually unchanged from previous versions of VB: If you use the Object Browser to look at built-in VB objects, you will find some that are familiar, including App, Form, and Screen, as well as individual classes for each of the built-in controls. These are objects that you can create in VB4 and expose through OLE Automation via a type library, which contains information on each class's properties and methods, including suggested syntax and links into an associated Help file.

There is also a Control class, which is often referred to as a generic object type. Using it, you can create an object variable that works with any control type: This is most useful when passing several types of controls to a subroutine, but it's also handy when setting or examining properties for all controls on a form by looping through the Controls collection. However, programming with specific object types provides better performance because less object-binding information has to be determined at runtime.

Some supplied classes have changed a bit with VB4. For instance, the data-access objects for working with the Jet database engine (e.g., Table, DynaSet, and SnapShot) have been superseded by the RecordSet objects. Because VB4 allows you to add references to object libraries via the Tools | References command--first seen in the VB for Applications (VBA) included with Excel 5.0 and Project 4.0--using external objects is now more straightforward.

This VB3 code was necessary to obtain a reference to an Excel worksheet:

Dim objXL As Object
Dim objWS As Object
Set objXL = CreateObject("Excel.
  Application")objXL.Workbooks.Add Set objWS = objXL.ActiveSheet

In VB4, it can be replaced with something as simple as:

Excel.Workbooks.Add
Set ws = Excel.ActiveSheet

There are even a couple of standard OLE objects that you can use: the built-in StdFont and StdPicture. To create a stand-alone font object that can be assigned to other objects or passed among applications that understand it, you might use code like this:


Set fnt = New StdFont
fnt.Size = 24
fnt.Bold = False
fnt.Italic = True

This font object can be assigned to a VB form or control: Form1.Font = fnt. In a similar way, you can assign a VB form or control's font object to a font variable:fnt2=Form1.Font.

VB Objects
Now that you know how to use existing objects, let's look at how to create new ones in VB. Class modules are dead simple to create: You insert a class module into your project just as you would a VB form or code module. Classes have very little overhead, needing only 40 bytes of runtime memory space for the class definition. (It's a good thing, too, because each object in your application requires its own class module.) The objects created from this definition are slightly more expensive: Each object created at runtime--instantiated, in object-oriented terminology­requires approximately 72 bytes of memory.

If you start with a VB class module, the simplest way to add properties is to declare a Public variable in the class's General Declarations section:PublicStuff asString. Similar to the Global keyword in previous versions of VB, the Publickeyword indicates that other classes-- or modules--can see it. When you start your minimal application and execute DimfooAsNewMyObject--or, alternatively, DimfooasMyObject, Setfoo= NewMyObject--you have created an instance of MyObject with the single property,Stuff.This property can be as-
signed as foo.Stuff = "Comment Area", and its value can be retrieved by varTemp = foo.Stuff.

Reading and writing a property this way, however, leads to uncontrollable results: Unfortunately, there is no way to check for runtime access, security, and data validation. Runtime access is significant because many object properties, including Name, may not be changeable at runtime. Without a doubt, security is important when you have multiple users of an object and you don't want all of them to have access to a property. For instance, perhaps only managers should have access to a Salary property in an Employee object. Data validation is obvious when you consider such commonly implemented properties as Visible and Enabled: Setting these to anything other than True or False could lead to a whole host of errors in your program.

To address all of these situations, access to properties can be contained in property procedures. For each property, you can use a Property Get procedure to retrieve its value and a Property Let procedure to assign it a value. For instance, instead of the Public declaration for Stuff used above, a private declaration, Private sStuff As String, can be used along with the following Property Let procedure:

Public Property Let Stuff(sTemp)
        'Data validation code
        '...
        sStuff = sTemp
End Property

The corresponding Property Get procedure is:


Public Property Get Stuff(sTemp)
sTemp = sStuff
End Property

For both procedures, the calling syntax is the same. The difference is that by using property procedures, you can encapsulate the data in the object and control any access to it. Because any Public property or procedure is available to code in both internal and external applications, by determining at runtime who controls the object, you can prevent an internal object from being directly accessed by any external applications.

Note that the internal data name sStuff (in the above example) is different from the publicly exposed Stuff method. While it's a long-standing tradition among developers to use an internal naming convention that usually prefaces variable names with identifiers for data types (e.g., "s" for String, "i" for Integer, and so on), it's also common to shield the users of the objects from their internal working details. This is one of the principles behind the object-oriented concept of encapsulation. For this reason, it's common practice not to expose those object names that include the data-type identifier.

Methods are the other major code construct used to build classes. They are implemented as the familiar Sub and Function procedures. Declaring them to be Public allows you to call methods from other code--the same as properties. In practice, methods are not much different from properties, and you can implement many things as either one.

For instance, a VB form or control's Move method is equivalent to setting the Left and Top properties--and optionally the Height and Width. You might implement an Add method for an Employee database that accepted multiple arguments; for example:


Public Function Add(iID As
  Integer, sName As String, sSex
  As String) As Integer
        Add = False
        'Code to process adding member
        Add = True
End Function

You could call the Add method with ret% = cls.Add(1, "Fred Flintstone", "M"). This syntax is a little different from that calling a VB form or control's built-in methods. It's also possible to declare the procedure as a Sub instead of a Function and to call it with cls.Add 1, "Fred Flintstone", "M", which may be more recognizable. However, it's usually considered good programming practice to return success or failure indicators on subroutine calls. That's how almost all of the Win32 applications programming interface (API) is implemented: If a Function fails and returns False, then the GetLast-
Error API is called to find out what the problem was.

When building OLE Automation servers, VB automatically creates the type library. Once compiled, this information is available in the Object Browser to anyone using those classes. Another advantage of using property procedures is that unlike Public properties, they can be assigned topics in a Help system.

Although you can't create your own event procedures, it's important to note that every class module has predefined Initialize and Terminate events that execute whenever an instance of that class is created or destroyed. The Initialize event is guaranteed to be the first code to execute: It runs before any properties are set or any methods are called. Thus, it is commonly used to initialize runtime properties, such as Name, that don't often change during the life of the object. The Terminate event executes when an object is about to be destroyed.

Interestingly, Forms and Multiple Document Interface Forms (MDIForms) also have these events. (The File Manager and most Microsoft Office applications are examples of MDIForms.) As you might expect, the Initialize event occurs before the Load event. In practice, it's possible to program around the inability to define additional class events: You can create corresponding methods and call them from other objects or applications.

There is some performance overhead involved when working with objects. Normally, it's not an issue for objects used internally in an application. However, it can be noticeable when you call external objects that run either in another process on the local machine or on a remote machine accessed through a network connection. In those situations, it's generally better to create methods that accept multiple arguments rather than several properties that each work with an individual value.

Cleaning Up
An object is released when all references to it are destroyed. It's important to understand how object references are incremented and decremented. Either Dim foo As New MyObject or Dim foo As MyObject, Set foo = New MyObject will create a new instance of MyObject. The difference is that the Dim statement in the second case merely creates a variable--internally, it's a true OLE variant--that's capable of containing an object pointer; it doesn't actually create the object. Objects are created only when GetObject or CreateObject statements are executed or when the New keyword is used.

An object reference can be destroyed either directly or indirectly. If an application terminates normally, in theory all of its object references are released. However, I strongly advise that any object references created in code be directly released by assigning the Nothing keyword to them; for example, Setfoo=
Nothing. This helps to protect against situations where the object variable goes out of scope while the controlling application continues to run, leaving the server in memory.

You should also be aware of VB's reference counting scheme for forms. A statement such as Form1.Show can actually increment Form1's reference count twice--once for the form itself and once for a variable named Form1. Therefore, it's common practice to decrement the reference count to zero immediately after unloading a form: UnloadForm1, SetForm1=Nothing.

An OLE server may remain in memory after all its references are released if a form is left open. This is consistent with VB's reference counting system for forms: VB maintains an internal reference to a form object. You do have an opportunity to clean up before the object is actually destroyed. The class's Terminate event executes when the last reference to an object has been released and the object is about to be destroyed. What you do at this point is up to you. Common actions include saving changed information (similar to cleaning up a "dirty" copy of Notepad when it closes) and releasing any references to dependent objects.

To control an OLE server:

  • Avoid trying to control your server's "lifetime" of (typically done by faking the reference count to add a bogus reference)
  • Avoid using the End statement in your OLE server
  • Avoid keeping a form loaded but hidden when there are no objects using it
  • Avoid circular object references

Externally Creatable
So, how do you use your newly created VB objects from external applications? You only have to set a couple of options in the design environment. You must give each class that you want to expose a Name and set its Public property to True. Then, you change the Instancing property from the default of Not Creatable to either Creatable SingleUse or CreatableMultiUse. A SingleUse server will load an additional copy of the server into memory each time an instance of the class is created. A MultiUse server will be shared across all applications using that class.

There are various implications to creating an instance of an OLE server: for example, the main memory requirement of starting additional SingleUse executables.

For MultiUse servers, which by their nature serialize requests from all controllers, the major concern is whether a single executable becomes a bottleneck in the system as multiple controllers queue up to use it. Multi-Use servers can run into problems if they rely on any state information internally. Using globally scoped variables internally is usually a mistake.

The final step is to set the StartMode to "OLE Server" on the Tools | Options | Project tab. VB does everything else automatically: It manipulates the Registry to fully register each class in the server, create a browsable type library, and so on.

Real Value Is Reusability
The class capabilities of VB4 are an important mechanism in creating reusable objects. In effect, the class's methods and properties become the pro-
gramming interface. Although reusing the objects internally in an application is useful, the real value of object classes is seen when they are reused in other applications. As the old programming proverb goes: "The best code of all is the code that you don't have to write."