The Shomsky home page     
HOME  
Technical White Papers Samples Tech Links About Us
 
 
 
Technical  
 
Home
Technical
    White papers
    Sample code
    Tech Links
About us
Contact us
 
Go back to previous page
 
 
 

This site best viewed with:
Microsoft Internet Explorer

     
Implementing Interfaces in VB6

By Gary Shomsky

Introduction
     Why Do You Care?
     What This Article Covers
     What You Need to Know
Object Oriented Programming (OOP) in VB6
     Polymorphism
         Check it Out: Polymorphic Behavior of VB Forms
     Early Binding vs. Late Binding
     Inheritance
     Interfaces Better Than Inheritance
     Implementing Interfaces
     Working with XML
Summary

download sample code

Introduction

Why Do You Care?

Interfaces have been available to Visual Basic developers since VB5, yet very few know what they are, and even fewer use them. An interface is a predefined set of properties and methods.

  • Standardizing your application with interfaces can speed development time and improve maintainability.
  • Understanding interfaces is necessary to implement code created by others, such as the SAX parser for XML.
  • Understanding interfaces prepares you for object-oriented features coming in future Visual Basic versions.

Back to top

What This Article Covers

This article describes the following items:

  • Object Oriented Programming in VB6
  • Polymorphism
  • The difference between early binding and late binding, and why interfaces give you the best of both sides
  • How to implement interfaces in VB6
  • How interfaces are used in XML processors

Back to top

What You Need to Know

This article assumes that the reader is an intermediate level VB6 developer. You must understand using class modules in ActiveX DLL projects to build objects that other developers will use. You need to also understand property procedures in class modules. In addition, a familiarity with XML will be helpful in the discussion of the SAX parser. As far as tools are concerned, you will need Visual Basic 6 to work the code samples and exercises. You will also need Microsoft’s XML processor (MSXML3) in order to work with the SAX parser.

Back to top

Object Oriented Programming (OOP) in VB6

Visual Basic developers have been able to implement interfaces since VB5, yet so few understand its unique place in the object oriented programming (OOP) model, that some review of the basics of interfaces is often necessary, even for experienced developers. In VB5, some who developed in a true OOP language like C++ would say that VB is not really object oriented, because it does not have inheritance. An additional jab was that implementing interfaces in VB5 was a poor man's inheritance that didn't really work. The proof in code was often convincing, however. Proving that interface inheritance is not a substitute for implementation inheritance is an easy thing.

Let's talk about OOP. The main job of the object builder is to make life simple for the object user. Object building is more involved and often considerably more complex than object using. As an object user, I simply want to write code that invokes properties and methods of the objects that someone else built. I would like to use syntax like:

object.Property = value object.Method

Examples of invoking properties and methods are:

Form1.Show Form1.BackColor = vbWhite Label1.Caption = Text1.Text

The main job of the object builder is to make life simple for the object user.

Back to top

Polymorphism

Now suppose you wanted to write code that applied to more than one object, say several VB forms. Looping through the Forms collection will allow you to do the same thing to each form, such as changing the BackColor property. In other languages, there are constructs just like collections, often called by the same name. There is a frequent need to iterate through this structure and perform the same actions on each object in the collection.

The steps below will demonstrate effects of different variable declarations. Declaring a variable of a specific data type is called early binding because the object's properties and methods are known at compile time. Declaring a variable of a generic data type is called late binding, because the object's properties and methods are not known until run time. Follow the steps below to see the advantages and disadvantages of both early and late binding.

Back to top

Check it Out: Polymorphic Behavior of VB Forms
1. Start a standard EXE project in VB6, and add 4 additional forms. They will be named Form1 through Form5 by default. Leave them all the same default size.

2. Add a standard module, and put Sub Main in it. Make Sub Main the startup object.

3. Add the following code to sub Main to load all of the forms and then to change each form's back color property to white

Public Sub Main() 'Load all of the forms Form1.Show Form2.Show Form3.Show Form4.Show Form5.Show 'loop through and change each form's back color Dim MyForm As Form For Each MyForm In Forms MyForm.BackColor = vbWhite Next End Sub

4. Run the code. It will happen so fast that you probably don't see all five forms show up gray, then change to white. They probably appear on the screen as white. this is not very impressive visually, but the next few steps demonstrate effects of different variable declarations.

5. Return to design time and modify the variable declaration as follows

Dim MyForm As Form1

6. If you retype the line of code in the loop, you will notice that when you type the "dot" notation, IntelliSense pops up. This is often misunderstood as VB recognized what the variable is in the loop in which it is coded. VB only recognizes that you declared a variable as a certain data type, and therefore it knows what set of properties and methods it should allow you to use. This set of properties and methods is called an interface.

7. Now run it again. You get a run time error because in the second iteration through the loop, the MyForm variable needs to be able to represent an instance of a Form2, but it cannot because it is declared as a Form1.

8. Return to design time and modify the variable declaration as follows

Dim MyForm As Object

9. If you retype the line of code in the loop, you will notice that when you type the "dot" notation, IntelliSense does not pop up. You may type in the property lower case as "backcolor", and as soon as the cursor leaves the line of code, VB adjusts it to the mixed case "BackColor". This is often misunderstood as VB recognized what the property is. VB only recognizes that you used a name of a property that it has in a library of keywords and variable names, and tries to "help" you by forcing the proper case sensitivity.

10. Now run it again. You do not get a run time error because in each iteration through the loop, the MyForm variable may be able to represent an instance of any object, so it will work if each instance has a BackColor property.

Back to top

Early Binding vs. Late Binding

Late binding means that type checking cannot be done until run time. This is referred to as late binding, because it is too late for you to do anything about it. The advantage of late binding is that you get the flexibility that comes with a mixed group of instances. Your code will compile, and will run, as long as you don't stumble into a run time error because an instance in the collection does not have a BackColor property. The disadvantage of late binding is that you don't know if it will work until run time. Therefore, you must add error-handling code to trap specific errors that might occur. You also must test your additional error handling code, and the added code will also add to the size of your compiled executable. But still, for the power of the flexibility, many developers are willing to risk it. There is also a performance problem with late binding. The VB run time DLL must check each time through the loop whether the MyForm variable represents an instance of something that has a BackColor property, before it attempts to execute the line of code.

Early binding means that the type declaration is recognized at compile time. There are several advantages here. IntelliSense pops up and keeps me from too many typo’s (this may not seem like much to some, but it makes a big difference to me.). The compiler recognizes the type library that I have bound my variable to, and can detect at compile time whether I have used properties and methods properly. The compiled code is optimized for early binding, but not for late binding. Therefore, it already knows how to access the property or method, rather than identifying at run time where in the class is this member. The problem with early binding is that I cannot have the flexibility that I often need.

The declaration “As Form” seems to have the advantages of both early and late binding. I get IntelliSense, compile time checking, and run time performance along with the flexibility of late binding. The keyword Form is the name of the interface that every VB form is created from. Every VB form has a BackColor property, and it is always the same data type. We cannot create an instance of Form. We can only create instances of forms that have been derived from the Form interface (which is every VB form). Some might describe this in terms of inheritance; so let's talk about inheritance.

Back to top

Inheritance

The purpose of inheritance is to build a set of objects, or a hierarchy, such that top-level objects help define the functionality in lower level objects. In other words, parent objects have properties and methods already coded, so that child objects that inherit from them do not have to repeat the code.

As an example, say that you use a class to build a Human object. It has properties like Name, Age and DOB (date of birth), along with various other properties and methods. Now say you want to build an employee object. It will, of course, have all of the properties and methods of the Human class. If you make the Employee class inherit from the Human class, then all of the functionality exists immediately in the Employee class. Now we can add properties and methods that are additional for the Employee, such as JobTitle and Salary properties. If these are the only two properties added to the Employee class beyond what was already defined (and coded) for the human class, then we have very little work to be done. Now go another step further, and let's say we also want to define a Developer class. This is a human employee who is programming in a particular language. So we want to create a new class called Developer, which inherits from Employee, which is already inheriting from Human. At this point, the Developer class already has all of the functionality of Human and of Employee. All we need to do is add a Language property, and the Developer class is done!

We will run into problems with an inheritance model when the base class from which we are inheriting changes. If the properties and methods in Human need to be modified, then everything that inherits from it is affected. Now think about the business objects that you would write to describe your business rules. Your Customer or Vendor objects are business objects; they need to reflect the real world entities that they describe. It is not too difficult to envision the many circumstances that can cause a change in a business object. What if government regulations or industry standards change? What if you adjust your business model in an effort to become more competitive? What if you realize that your existing objects do not completely reflect the business logic that is applied in the real world business practices, and they need to be improved so that the software models the real business model being executed? Clearly, for business objects in a practical application, an extensive inheritance hierarchy can have a terrible affect on the maintainability of your application.

Though I would like to have had inheritance as a feature in VB before now, it has not been a real problem waiting for it, because my business objects are not the best place to use it. If the base classes are stable, then inheriting from them is safe. Think of the VB controls we have in the Toolbox to place on a form. The combo box is a control that is derived from two other controls: a list box and a text box. If the text box control were to have properties and methods changing every month, then so would the combo box, because it inherits from the text box control. Stable base classes are certainly necessary for successful use of an inheritance hierarchy.

When you look at the code necessary to simulate real inheritance in VB5 or VB6 by using interfaces, you will see that it does not make it easier for the object user, but more difficult. So what is the use of an interface? Inheritance is to be used when implementation code is largely the same across multiple objects, like the Human/Employee/Developer hierarchy discussed above. Interfaces should be used when it is beneficial to use the same properties and methods, but the implementation code is significantly different.

Back to top

Interfaces Better Than Inheritance

This brings us to an OOP principle called polymorphism. Literally translated, this word means "many forms". Many different objects may share the same properties and methods. How is this different from inheritance? In inheritance, the implementation code is created in the parent class, and both the method signature and the implementation code get inherited. When using interfaces, we just want the public set of properties and methods to be copied to another class, and implemented there.

An interface is a predefined set of properties and methods.

If you have a Doctor class and a Dentist class, they may each have a checkup method. When the Patient invokes the Doctor's Checkup method, he gets a complete physical. But when the Patient invokes the Dentist's Checkup method, he only gets the teeth examined. If you had defined the Checkup method in a separate class, then you could make sure that both the Doctor class and the Dentist class will perform the same method. The implementation code for each is different, as it should be.

In VB6, when you have a set of properties and methods defined on class A, that is to be applied to another class B, we say that class B provides the implementation code for the properties and methods in class A. Now substituting the term "interface" for the phrase "a set of properties and methods" we can say that B implements the interface from A, or that B implements A.

Back to top

Implementing Interfaces

There are two main reasons for implementing interfaces: 1) to put dissimilar instances in the same collection and then iterate through the collection as we did with the VB forms, treating them as if they were all the same, and 2) to standardize the interface that should be called, so that the calls will always work. Let's discuss these one at a time.

When you want to have many instances that were not all created from the same class placed into the same container like a collection, you can only treat them as a homogenous group if they apply polymorphism, and can be guaranteed to support the same interface. This is what we demonstrated with VB forms.

Let's create an Appliance class, which will define the interface for other appliances. We will create properties and methods that all appliances have in common. Keeping it simple, lets say that every appliance has a way of turning power on or off. In creating objects in software, methods perform actions, and properties maintain values. So, parallel to the VB form's Show and Hide methods, and Visible property, we will create an appliance that has PowerOn and PowerOff methods, and a Ready property. In some languages, we can determine that a class used for definitional purpose may not be allowed to be instantiated. In OOP terms this is called abstraction, and this class is called an abstract class. In VB6, every class module is the same kind of class module. We cannot define an abstract class. By convention, we name the class prefixed with an upper case "I" for Interface. So we would name the class we will treat as abstract "IAppliance".

This class will define the public properties and methods. Only the procedure signatures need to be added. No code inside of the procedures is needed, and no private variables should be added. The private variables and procedures, along with the implementation code belong in the class that will implement this interface. The following is an example of a class such as we are describing for IAppliance:

Option Explicit 'This is the interface for an Appliance 'compare to a VB Form's Show and Hide methods 'and Visible property 'methods Public Sub PowerOn() End Sub Public Sub PowerOff() End Sub 'property Public Property Get Ready() As Boolean End Property Public Property Let Ready(ByVal booReady As Boolean) End Property

Then you create a class for each appliance you want to create instances of. Start with something simple like a lamp. Create a class module named Lamp, and the first thing to do is to add the following code in the General Declarations section:

Implements Iappliance

Notice after typing "Implements" and then a space, IntelliSense pops up with a list of available interfaces that you might implement. This two-word declaration can be described as stating to the compiler "This class implements IAppliance", or "This class provides the implementation code for the IAppliance interface".

After typing the implements statement, locate the object drop-down box that says (General), and see that the name of the interface class is in the list. Select it, and then drop down the procedure box to look at all of the members of the interface.

Notice the strange syntax that VB creates for you. The name of the procedure is of the form "InterfaceName_Member", where member is a property or a method name. Also note that the procedure is declared as Private. This is very similar to how VB creates event procedures for you.

Private Sub IAppliance_PowerOff() End Sub Private Sub Command1_Click() End Sub

You must implement the interface members with private subs, just as VB gives them to you. The public members of the Lamp class are actually a different interface. Public members of a class are referred to as the default interface of the class. If you change the private declaration of an implemented interface member to public, you have just moved it out of one interface, and added it to the default interface of the class.

Think of what would happen if you added a VB form to your project, but were not certain that it would have a BackColor property. It would be like late binding all of the time! If you can't depend on the members of an interface being implemented, then you would have to add a lot of error handling code because of the many possibilities that would exist. This is why implementing an interface is handled like a contract. If you implement an interface, you must implement every member of that interface.

If you implement an interface, you must implement every member of that interface.

The syntax of the procedures in the implementing class resembles event procedure syntax. VB handles calling and executing the proper procedure at the right time, just like for events and event procedures. Here is the complete code for the Lamp class that implements the IAppliance interface:

Option Explicit Implements IAppliance Private m_Ready As Boolean Private Sub IAppliance_PowerOff() IAppliance_Ready = False End Sub Private Sub IAppliance_PowerOn() IAppliance_Ready = True End Sub Private Property Let IAppliance_Ready(ByVal booReady As Boolean) m_Ready = booReady End Property Private Property Get IAppliance_Ready() As Boolean IAppliance_Ready = m_Ready End Property

Now let's consider another kind of appliance, that should have the same interface, but different implementation. A computer can be turned on and off, but there is a different behavior. The real world object that we want to model in software has a time delay when you turn it on, and again when you turn it off. It also beeps at you. Here is an example of an IAppliance interface member as implemented on the Computer class:

Private Sub IAppliance_PowerOn() Dim lng As Long If Not IAppliance_Ready Then For lng = 1 To 500 Debug.Print "Starting services, please wait..." Next lng IAppliance_Ready = True Beep End If End Sub

The purpose of the loop is to create the time delay. The reason the Debug.Print statement is included is also for the time delay.

In the form, code can be written to treat all instances that implement the IAppliance class the same. Adding instances to a collection can be done with code like the following. Note that objAppliances has been declared As Collection and an instance exists. The variable used to create instances of different classes has been declared as the name of the interface. If interfaces are new to you, up until this point you have always declared a variable as the name of the class you wanted to instantiate, and then use the name of the class again with the New keyword to create the instance. What you have been doing is to declare the variable as the default interface of the class.

Dim objNewAppliance As Appliances.IAppliance Set objNewAppliance = New Appliances.Lamp objAppliances.Add objNewAppliance Set objNewAppliance = New Appliances.Computer objAppliances.Add objNewAppliance

Then code that iterates through the collection can assume polymorphism, which the compiler will enforce. Notice that the incrementing variable objAppliance in the For Each loop has been declared as the name of the interface.

Dim objAppliance As Appliances.IAppliance For Each objAppliance In objAppliances objAppliance.PowerOn Next

If you later add types of appliances to your object model, you do not have to change this code that loops through the collection, as long as you implement the same interface. This is an important principle for developing maintainable software solutions.

Back to top

Working with XML

In the XML technology, there are two widely accepted API's for accessing contents of an XML document: The Document Object Model (DOM) and the Simple API for XML (SAX). Both API's define a set of programmatic interfaces that model the XML document. The DOM uses interfaces, but is more transparent to the developer. You declare variables as the name of an interface, such as:

Dim objDomDoc As MSXML2.DOMDocument30 Dim objRoot As MSXML2.IXMLDOMElement Set objDomDoc = New MSXML2.DOMDocument30 objDomDoc.Load App.Path & "\sample.xml" Set objRoot = objDomDoc.documentElement

Note that the objRoot variable is declared as an interface with the conventional prefix "I" to indicate an interface class. The code above does not use the New keyword to create an instance, so it is easy to miss the fact that the instance is not necessarily an instance of IXMLDOMElement, but an instance of something that supports the IXMLDOMElement interface. The DOM conveniently hides this working with interfaces from the developer. The DOM creates a hierarchical object model, doing all the work of creating instances as needed. We only get references to instances with object variables so that we can invoke the properties and methods, and get the work done. The hideous details of implementing interfaces were done for us behind the scenes.

We are not so lucky with the SAX interface, however. We have a couple of classes that we use, the SAXXMLReader object (Reader), and the MXXMLWriter object (Writer). But the bulk of the work is done in our classes. To get something working, we need an instance of the Reader object, and then we may invoke its properties and methods.

Dim objReader As MSXML2.SAXXMLReader30 Dim objContentHandler As clsContentHandler Dim objErrorHandler As clsErrorHandler Set objContentHandler = New clsContentHandler Set objErrorHandler = New clsErrorHandler Set objReader = New MSXML2.SAXXMLReader30 Set objReader.contentHandler = objContentHandler Set objReader.errorHandler = objErrorHandler

Notice that the content handler object and the error handler object are our classes. What the objReader wants as a reference in its contentHandler property is an object that implements the IVBSAXContentHandler interface. So in our clsContentHandler class module we need to start with

Implements MSXML2.IVBSAXContentHandler

Then we must implement every member of that interface, or our class will cause compile errors. The clsErrorHandler class must implement the IVBSAXErrorHandler interface in the same manner.

The essence of the SAX parser is that we write the implementation of the interfaces, and it is our code in our classes that do the work. Even though we implement methods of the interfaces, we refer to SAX as being event based. This is because it is the Reader object that calls the methods we implemented in our classes. Therefore, to you, as the developer, the code that calls your implemented procedures comes from elsewhere, just like an event is triggered at some other location and broadcast to your objects that are listening.

Back to top

Summary

This article has described the following items:

  • Object Oriented Programming in VB6
  • What is polymorphism
  • The difference between early binding an late binding, and why interfaces gives you the best of both sides
  • How to implement interfaces in VB6
  • How interfaces are used in XML processors

Back to top


download sample code
 

© 2002-2005 by Gary Shomsky. All rights reserved.