The Life and Times of an Object

By Bob Zale


What is an object, anyway?

An object is a pre-defined set of data (variables), neatly packaged with a group of subroutines (code) which manipulate the data and provide any other functionality you need.

For example, a string array containing names and addresses (data) might be packaged with a subroutine (code) that displays a popup dialog to edit the data, another subroutine (code) to print mailing labels, and so forth. That's a great candidate for an object.

In short, an object is a complete little programming package, code and data, all in one tightly contained place. It's safer and protected, easier to debug, maintain, and reuse. An object can be written to perform most any task you might imagine.

In object terminology, a CLASS is used to define an object. A CLASS is much like an enhanced user-defined type; it's a description of of both the variables and the subroutines which make up the object. When you instruct the compiler to create an object, it uses the definitions found in the CLASS to do so. It allocates memory for the variables, establishes pointers to the subroutines, and makes this new object available to your program.

Each time you create a new OBJECT, it is called an INSTANCE of its definition (an instance of the CLASS). That's why these variables are called INSTANCE variables. When you create multiple objects (from the same CLASS definition), each instance gets its own individual copy of these INSTANCE variables, and each instance gets individual access to the subroutines.

In PowerBASIC, objects are optional. Objects are a great programming tool, but your existing code remains fully functional. Standard Subs and Functions will always be supported, so you can blend the techniques at a comfortable pace.

PowerBASIC objects are practical. They're lightning fast with very little overhead. We've tried very hard to give you the best ratio of straightforward design to performance and features. We thu,n‡you'll find PowerBASIC objects very hard to resist.

Thousands of books have been written to describe objects and object oriented programming. In most cases, the buzz words and abstract definitions make it seem as though they're designed to confuse, not enlighten. We'll try to limit the use of strange descriptors, but some of it just can't be avoided. In these cases, we'll try to give you clear definitions as they're needed.

A key trait of PowerBASIC objects (and objects in general) is the concept of encapsulation. Data is "hidden" within the object, so INSTANCE variables cannot be accessed from the outside.

INSTANCE variable data may only be set, altered, or retrieved by the subroutines
in the object. These variables are hidden from the rest of the program.

Over the years, objects have gained a reputation for slow, bloated programming. In many cases, this reputation was well deserved. But don't let that fool you. With PowerBASIC, you'll find you have a whole new "Object World"! All the power, yet all the performance, too. PowerBASIC objects give you every ounce of performance possible... the same breathtaking speed as procedural programming!

 

Where are objects located?

Since an object is a complete programming package (sort of like the idea of a sub-program), it can be located in many different places. However, regardless of where the object is found, PowerBASIC will still handle all the messy details for you... automatically.

In many cases, objects will be located right within your main program. You can create a single, self-contained program, with one object or a thousand objects. Get all the power of objects, but keep the details private -- for your eyes only.

Objects can be located in a Dynamic Link Library (DLL). This is usually called a COM object, but is also known as an OCX or an ActiveX object. The actual file extension is largely irrelevant. The subroutines offered by these objects are generally available to any program which knows the subroutine definitions, and wishes to access them. This type of object is known as an "in-process" object because it is loaded into the address space of the calling application, just like a standard DLL.

Objects can also be located in an executable program (EXE). In this case, the calling application is frequently called a "controller", as it can control how the executable operates by manipulating its objects. A good example of this functionality is Microsoft Word -- by simply calling object subroutines, you can load a DOC file, display it to the user, make changes, then save the new document. All under the control of your calling application. Once again, the object subroutines are generally available to any program which knows the subroutine definitions. This type of object is known as an "out-of-process" object because it does not share address space with the calling application.

Whenever an object is accessed outside of your program, PowerBASIC uses the COM (Component Object Model) services of Windows to make the "connection" for you. COM is an important tool which will open many opportunities for you. But more about COM later...

 

Why should I use objects?

* Objects help you maintain your code. Objects break up your project into small, easily viewed parts. Usually, the input and output is clearly defined. You have all of the code and all of the data right at your fingertips.

* Objects help you write bug-free code. When you keep an object small and well-defined, you greatly enhance the stability of your programs. Consider the comparison to procedural programming: With standard Subs and Functions, it's typical to create the data (variables) in the calling code, but manipulate the data in the target procedures when they are executed. This separation of code and data has caused some of the most insidious bugs known to programmers. When you need to extend the range of data to a larger data type, it's easy to change the code. A piece of cake, so to speak. But what about the data? Now you must search out every reference to every involved Sub and Function. Find every data creation, every data change, and every other reference to these variables. What are the chances of missing a critical one? Far too great to ignore.

* Objects help you re-use your code. Since the object contains all the subroutines, and all the data, how could it be easier? Put one object source in one include file... Or put it in one DLL... Just use it when you need it!

* Objects help with team programming. Objects are self-contained. All of the subroutines and all of the data, all in one concise place. It's easy to create a precise definition for each object, and there's little dependency between the implementation of various objects. Each team member builds an object, one at a time, so it all comes together neatly in the end.

* Objects are an increasingly popular standard. Do you need to access the Windows API? Many of the newer API functions (DirectX graphics, for example) use only an object interface, and nothing else. If you don't use objects, you simply can't access them. Do you want to control an important application, like an Internet browser, word processor, or spreadsheet? COM objects are the only way to do it. As time goes by, objects will only become more embedded in day-to-day programming. Don't be left behind!

 

What are the parts of an object?

* METHOD: A subroutine, very similar to a user-defined Sub/Function. A method has the special attribute that it can access the variables stored in the object. A method can return a value like a Function, or return nothing, like a Sub.

* PROPERTY: This is a METHOD, but in a specific form, for a specific purpose. A PROPERTY has all the attributes of a standard METHOD. It has a special syntax, and is specifically used to read or write private data to/from the internal variables in an object. This helps to maintain the principle of "excapsulation". Properties are usually created in pairs, a GET PROPERTY to read a variable, and a SET PROPERTY to write to a variable. Paired properties use the same name for both, since PowerBASIC will choose the correct one based upon the usage in your source code. You should note this important fact: Since a PROPERTY is a form of METHOD, all of the documentation about METHODS also applies to PROPERTIES, unless we specifically state otherwise.

* INTERFACE: A definition of a set of methods and properties which are implemented on an object. You might think of it as a list of DECLARE statements where the sequence of the Declares must be preserved. Remember, the interface is just the definition, not the actual code. Every interface is associated with a GUID (a 128-bit number or string) which uniquely identifies this particular interface from all other interfaces, anywhere in the world. This identifier is called the Interface ID, or IID for short.

An interesting note is that one particular interface definition may become a part of several different classes and objects. In fact, the internal code for an interface in CLASS A may be entirely different from the internal code for the same interface in CLASS B. Method names, parameters, and return values must be identical, but the internal code could vary significantly.

An important point: interfaces are immutable. Once an interface has been defined and published, the Method and Property definitions (sequence, names, parameters, return values, etc.) may never be altered. If you find you must change or extend an interface, you would usually define a new interface instead.

* CLASS: A definition of a complete object, which may include one or more interfaces. This is the place where you declare INSTANCE variables, and write your code for the enclosed METHOD and PROPERTY procedures. While some object implementations allow only a single interface per class, PowerBASIC objects (and COM objects in general) support the idea of optional multiple interfaces. Still, remember that a CLASS is the complete definition of an object. It defines all of the code and all of the data which will be found in a particular object. For this reason, there is only one copy of a CLASS. Every class is associated with a GUID (a 128-bit number or string) which uniquely identifies this particular class from all others, anywhere in the world. This identifier is called the Class ID, or CLSID. A friendlier version of the CLSID is a shorter text name, which also identifies the Class. This text name is known as the Program ID (PROGID), though it's possible this PROGID may not be totally unique. As it's a simpler construct, it might be duplicated in another program.

* CLASS METHOD: This is a private method, which may only be called from within the same CLASS. It is not a part of any interface, so it is never listed there. It is called a CLASS METHOD because it is a member of the class, not an interface. It is not visible to any code outside the class where it is defined. Code in a CLASS METHOD may call other CLASS METHODS in the same CLASS. Class Properties do not exist because there is no need for them. Within the object, variables can be accessed directly, so there is no need to use a PROPERTY procedure as an intermediary.

* CONSTRUCTOR: This is a special form of CLASS METHOD, which is executed automatically whenever an object is created. It is optional, but if present, it must be named CREATE.

* DESTRUCTOR: This is a special form of CLASS METHOD, which is executed automatically whenever an object is destroyed. It is optional, but if present, it must be named DESTROY.

* OBJECT: An instance of a class. When you create an object in your running program, using the LET (with objects) statement, or its implied form, PowerBASIC allocates a block of memory for the set of instance variables you defined, and establishes a virtual function table (a set of function code pointers) for each of the interfaces. You can create any number of OBJECTS based upon one CLASS definition.

It might be useful to think of an OBJECT in terms of an electrical appliance, like a television set. The TV is the equivalent of an OBJECT, because it's an instance of the plans which define all the things which make it a television. Of course, those plans are the equivalent of a CLASS. You can make many instances of a television from one set of plans, just as you can make many OBJECTS from one CLASS. The individual buttons and controls on the television are the equivalent of METHODS, while all of the controls, taken as a whole, are equivalent to the INTERFACE.

We don't need to know how a television works internally to use it and benefit from it. Likewise, we don't need to know how an object works internally to use it and benefit from it. We only need to know the intended job of the object, and how to communicate with it from the outside. The internal workings are well "hidden", which is called encapsulation. Since we can't "look inside" an Object, it's not possible to directly manipulate internal variables or memory. This provides a increased level of security for the internal code and data.

* INSTANCE DATA: Each CLASS defines some INSTANCE variables which are present in every object. When you create multiple objects (of the same class), each object gets its own unique copy of them. These variables are called INSTANCE variables because a new set of them is created for each instance of the object. For example, if you created a CUSTOMER object for each customer of your business, you might have INSTANCE variables for the Name, Address, Balance owed, etc. Each object would have its own set of INSTANCE variables to describe the attributes of that particular customer. INSTANCE variables are always private to the object. They can be accessed directly from any METHOD on the object, but they are invisible to any code outside of the object.

* VIRTUAL FUNCTION TABLE: Commonly called a VFT or VTBL, this is a set of function code pointers, one for each METHOD or PROPERTY in an interface. This is a tool used internally to direct program execution to the correct method or property you wish to execute. While it is a vital and integral part of every object, you need give it no concern other than to be aware of its existence. PowerBASIC manages these items for you, with no programmer intervention required.

 

Are there other important "Buzz-Words"?

* GUID: This is a "Globally Unique Identifier", a very large number which is used to uniquely identify every interface, every class, and every COM application or library which exists anywhere in the world. GUIDs identify the specific components, wherever and whenever they may be used. A GUID is a 16-byte (128-bit) value, which could be represented as an integer or a string. This 128-bit item is large enough to represent all the possible values, anywhere in the world. The PowerBASIC GUID$() function (or a hot-key in the PowerBASIC IDE) can generate a random GUID which is statistically guaranteed to be unique from any other generated GUID. Each of these identifying GUIDs may be assigned by the programmer, or they will be randomly assigned by the PowerBASIC compiler. When a GUID is written in text, it takes the form: {00CC0098-0000-0000-0000-0000000000FF}.

* DIRECT INTERFACE: This is the most efficient form of interface. When you call a particular METHOD or PROPERTY, the compiler simply performs an indirect jump to the correct entry point listed in the virtual function table (VFT or VTBL). This is just as fast as calling a standard Sub or Function, and is the default access method used by PowerBASIC.

* DISPATCH INTERFACE: This is a slow form of interface, originally introduced as a part of Microsoft Visual Basic. When you use DISPATCH, the compiler actually passes the name of the METHOD you wish to execute as a text string. The parameters can also be passed in the same way. The object must then look up the names, and decide which METHOD to execute, and which parameters to use, based upon the text strings provided. This is a very slow process. Many scripting languages still use DISPATCH as their sole method of operation, so continued support is necessary.

* DUAL INTERFACE: This is a combination of a Direct Interface and a Dispatch Interface. This most flexible form allows either option to be used, depending upon how the calling application is implemented.

* AUTOMATION: This is a special calling convention, defined by MS is simply one which adheres to the rules for Automation COM Objects. It may offer just a direct interface, just a Dispatch interface, or both of them (DUAL). It should be noted that some programmers use the word AUTOMATION to mean DISPATCH. Even though that's not correct, you should keep the possibility in mind whenever you encounter the term. Automation Methods must use parameters, return values, and assignment variables which are AUTOMATION compatible: BYTE, WORD, DWORD, INTEGER, LONG, QUAD, SINGLE, DOUBLE, CURRENCY, OBJECT, STRING, and VARIANT. All Automation Methods return a hidden result code which is called the hResult. This is not really a handle, as the name suggests, but a result code to report the success or failure of a call to a METHOD or PROPERTY.

* IUNKNOWN: This is the name of a special interface which is the basis for every object. It has three methods, which are always defined as the first three methods in every interface. These 3 methods are used by compilers (PowerBASIC or others) to look up other interfaces on the object, and to keep track of usage of this object. While IUNKNOWN is mandatory for every object, you won't ever need to reference it directly. PowerBASIC handles all those messy details automatically.

* OBJECT REFERENCE: This is a reference (a pointer) to an object, which is the only way objects are used. In PowerBASIC, an object variable initially contains NOTHING. When you create an object, or duplicate one, a reference to that object is placed in an object variable by the compiler. That is, a pointer to the object is automatically inserted in the object variable. It is now considered to contain an OBJECT REFERENCE until such time as the reference is deleted or set to NOTHING.

* COMPONENT: An object that encapsulates code and data, providing a set of publicly available services.

* MONIKER: An object that implements the IMoniker interface. A moniker acts as a name that uniquely identifies a COM object. In the same way that a path identifies a file in the file system, a moniker identifies a COM object in the directory namespace.

 

What does a Class look like?

Here is the PowerBASIC source code for a very simple class. It has just one interface and one instance variable.

CLASS MyClass
  INSTANCE Counter as Long
  INTERFACE MyInterface
      INHERIT iUnknown            ' inherit the base class
      Method BumpIt(Inc as Long) as Long
        Temp& = Counter + Inc
         Incr Counter
         Method = Temp&
       End Method
     END INTERFACE
     ' more interfaces could be implemented here
 END CLASS

Just like other blocks of code in PowerBASIC, a class is enclosed in the CLASS statement and the END CLASS statement. Every class is given a text name (in this case "MyClass") so it can be referenced easily in the program.

The INSTANCE statement describes INSTANCE variables for this class. Each object you create from this class definition contains its own private set of any INSTANCE variables. So, if you had a SHIRT class, you might have an instance variable named COLOR, among others. Then, if you create two objects from the class, the COLOR instance variable in the first object might contain WHITE, while the second might be BLUE.

Next comes the INTERFACE and END INTERFACE statements. They define the one interface in this class, and they enclose the methods and properties in this interface. Every interface is given a text name (in this case "MyInterface") so it can be referenced easily in the program. You could add any number of additional interfaces to this class if it suited the needs of your program.

The first statement in every Interface Block is the INHERIT statement. As you learned earlier, every interface must contain the three methods of IUNKNOWN as its first three methods. In this case, INHERIT is a shortcut, so you don't have to type the complete definitions of those methods in every interface. There are more complex (and powerful) ways to use INHERIT as well, but more about that later.

Finally, we have the METHOD and END METHOD statements. They are just about identical to a FUNCTION block, but they may only appear in an interface. In this case, the METHOD is named "BumpIt". It takes one ByRef parameter, and returns a long integer result.

How do you reference this object?

Function PBMain()
  Dim Stuff as MyInterface
  Let Stuff = Class "MyClass"
  x& = Stuff.BumpIt(77)
End Function

The first line of PBMain (DIM...) defines an object variable for an interface of the type "MyInterface". The LET statement creates an object of the CLASS "MyClass", and assigns an object reference to the object variable named "Stuff". The next line tells PowerBASIC that you want to execute the method "BumpIt", and assign the result to the variable "x&". It's just that simple!

 

What is a Base Class?

The term "Base Class" is truly a misnomer, since it's actually an interface. The truth is, this term probably originated from those who use a programming language which supports only one interface per class. (Note: PowerBASIC allows an unlimited number of interfaces.) On those limited platforms, the distinction between a class and an interface tends to blur. However, since the term "Base Class" enjoys fairly wide usage already, it's probably best if we just learn to live with it and love it.

Every PowerBASIC interface must ultimately derive from the IUnknown interface, since it provides information about an object that the compiler must have to manage these affairs accurately. Previously, we discussed the concept of adding INHERIT IUNKNOWN as the first line of every Interface Block. In that way, PowerBASIC just inserts the necessary source code for you, so that the new interface you are creating will derive all the functionality of IUNKNOWN, but still save you from all that typing. What we didn't tell you at first was that there are really 3 System Base Classes in PowerBASIC. The other two can be used, because they, too, are derived from IUNKNOWN.

So, the real definition of a Base Class is "The interface from which a newly created interface is derived". To implement any of the system interfaces, you would just use INHERIT followed by the Base Class name as the first line of the interface block. They are:

INHERIT IUNKNOWN

If this option is chosen, your methods may only be accessed using a Direct Interface, the most efficient form of access. It uses the STDCALL calling conventions, and uses return value conventions normally associated with C++. This style of Base Class is also known as a CUSTOM INTERFACE, so you can use "INHERIT CUSTOM" in place of "INHERIT IUNKNOWN" if that's more comfortable for you.

INHERIT IAUTOMATION

If this option is chosen, your methods may only be accessed using a Direct Interface, the most efficient form of access. It uses the STDCALL calling conventions, and uses return value conventions involving a hidden parameter on the stack. Automation Methods must use parameters, return values, and assignment variables which are AUTOMATION compatible: BYTE, WORD, DWORD, INTEGER, LONG, QUAD, SINGLE, DOUBLE, CURRENCY, OBJECT, STRING, and VARIANT. All Automation Methods return a hidden result code which is called the hResult. This is not really a handle, as the name suggests, but a result code to report the success or failure of a call to a METHOD or PROPERTY. "AUTOMATION" is a synonym for "IAUTOMATION", so you can substitute "INHERIT AUTOMATION" in your code if that's more comfortable for you. Automation Interfaces have become more popular than Custom Interfaces in recent times, likely due to availability of the hResult hidden result code.

INHERIT IDISPATCH

If this option is chosen, PowerBASIC will automatically create a DUAL Interface for you. That means your methods can be accessed using a Direct Interface (using Automation conventions described above), or the slower DISPATCH Interface, if that's what is needed. This is certainly the most flexible Base Class, and the only one which should be used if your methods will be accessed by code from a programming language other than PowerBASIC. In a DUAL interface, both forms return the hResult hidden result to report the success or failure of the operation. You may use the term "INHERIT DUAL" in place of "INHERIT IDISPATCH", if that's more comfortable for you. While a class may have any number of direct interfaces, only one DUAL or IDISPATCH interface is allowed.

 

What does an Interface look like?

An INTERFACE is a definition of a set of methods and properties which may be implemented on an object. Think of it as as much like a TYPE declaration, except that it contains Method and Property declarations instead of member variables. One interface definition may be used in many different classes and objects.

An Interface may appear in two general forms: the declaration form and the implementation form.

In the declaration form, the Interface just provides the "signature" of the member methods, without any other source code:

INTERFACE MyInterface
  INHERIT IAutomation
  METHOD Method1(parm AS LONG)
  PROPERTY GET Prop1() AS STRING
  PROPERTY SET Prop1(ByVal Text AS STRING)
END INTERFACE

This type of declaration interface can be used to provide a description of external interfaces, which you plan to access through COM services, or just as additional self-documentation of internal code.

In the implementation form, it is part of a CLASS definition, so it contains the complete source code to implement each of the member Methods and Properties.

CLASS AnyClass
  INTERFACE AnyInterface
     INHERIT IAutomation
     METHOD Method1(parm AS LONG)
       CALL abc(parm)
     END METHOD
 
     METHOD Method2(parm AS LONG)
       CALL abc(Parm*1)
     END METHOD
  END INTERFACE
END CLASS

In this case, you have the complete definition of an object, with code implemented so the methods can be called and executed.

The first entry in every INTERFACE block must be the base class upon which it is built. In PowerBASIC, you choose one of the System Base Classes (IUnknown, IAutomation, or IDispatch), or you might decide to inherit a User Base Class instead.

INTERFACE CustomIface
  INHERIT IUnknown
  METHOD  MethodDef()...
END INTERFACE

The above code defines a custom interface whose methods are available for direct access only. It uses custom calling conventions and does not support an hResult (OBJRESULT) return value.

INTERFACE AutoIface
  INHERIT IAutomation
  METHOD  MethodDef()...
END INTERFACE

The above code defines an automation interface whose methods are available for direct access only. It uses automation calling conventions and always supports an hResult (OBJRESULT) return value. The above two forms will typically be used for internal objects, since they offer the best performance. Every PowerBASIC interface and every COM interface must ultimately inherit from IUnknown. As required base classes, the IUnknown and IAutomation declarations are built into the PowerBASIC Compiler.

INTERFACE DispatchIface
  INHERIT IDispatch
  METHOD  MethodDef()...
END INTERFACE

The above code defines a dual interface whose methods are available for both direct access and Dispatch access. This is the form you will typically use for COM objects, since it offers the best compatibility with varied client modules.

Every method and property in a dual interface needs a positive, long integer value to identify it. That integer value is known as a DispID (Dispatch ID), and it's used internally by COM services to call the correct function on a Dispatch interface. You can specify a particular DispID by enclosing it in angle brackets immediately following the Method/Property name in an Interface definition block.

INTERFACE DualIface
  INHERIT IDispatch
  METHOD  MethodOne <76> ()
  METHOD  MethodTwo <77> ()
END INTERFACE

If you don't specify a DispID, PowerBASIC will assign a random value for you. This is fine for internal objects, but may cause a failure for published COM objects, as the DispID could change each time you compile your program.

 

Just what is COM?

COM is an acronym. It represents the words "Component Object Model".

The short answer is that COM defines a way to communicate between modules of code. The slightly longer answer follows.

You should know that every object created or defined in PowerBASIC is fully compatible with the COM specification. Many popular compilers are not able to make that claim accurately. The COM specification defines a standardized method of communication between modules of code (frequently called components), regardless of the platform or the tool used to create them. COM components are reusable chunks of code and associated data, which may be accessed by other "COM-Aware" components and applications.

One of the most frustrating things about this technology has been the ever-changing list of buzz-words used to describe it. We've evolved through OLE, VBX, and OCX, to COM, ActiveX, and more. Though nuances of difference abound, the important thing to remember is that COM and ActiveX describe a means of accessing code and data located outside of the current module. COM+ refers to some extensions which are specific to Win2000, WinXP, and WinVista. Throughout this discussion, we'll use the terms COM Object and ActiveX Object to describe components: reusable chunks of code and associated data.

Prior versions of PowerBASIC introduced client COM services, which were accessible through the COM DISPATCH interface. While the DISPATCH interface is very flexible and easy-to-use, that very flexibility adds a level of overhead which is unacceptable for many applications. This version of PowerBASIC adds the capability to create and access COM objects through a DIRECT INTERFACE or a DISPATCH INTERFACE.

All objects in PowerBASIC, COM or not, follow all the guidelines and implementation rules established for COM Objects. This simplifies usage by the programmer, yet adds no measurable overhead at run-time. PowerBASIC encapsulates all the low-level details of the actual COM communication process. This provides a straightforward way to load and communicate with a COM component using BASIC syntax. You'll find that the PowerBASIC object implementation is very efficient, with virtually no degradation of execution speed as compared to standard Subs and Functions.

 

What is a COM component?

A COM component is commonly referred to as a COM Object. We can visualize a COM component or Object as simply a "black box" that comprises a set of methods and associated data. Internally, these Objects contain reusable code (Methods), and provide ways for an application to call the object's Methods and read/write its associated data through its Interfaces. Notice that this is the same definition as an object internal to your program. The difference is that COM offers a way to perform this functionality on an object external to your program.

A COM Component is generally known as a COM SERVER, because it serves up information or actions requested by a COM CLIENT. A COM SERVER makes its Methods and Properties public, so that a COM CLIENT can call them as needed.

COM Components usually take the form of an EXE, or DLL/OCX file, but the actual file extension is largely irrelevant. However, DLL/OCX versions of a component are generally referred to as "in-process", since they are loaded into the address space of the calling application. EXE-versions are typically "out-of-process" because they will not share the address space of the calling application.

To summarize, a COM Object (COM Server) is a special form of code library (similar to a standard DLL) that conforms to the COM specification. It provides at least one public interface, and is identified by a globally unique PROGID and CLSID.

Every class is associated with a GUID (a 128-bit number or string) which uniquely identifies this particular class from all others, anywhere in the world. This identifier is called the Class ID, or CLSID. A friendlier version of the CLSID is a shorter text name, which also identifies the Class. This text name is known as the PROGID, though it's possible this PROGID may not be totally unique. As it's a simpler construct, it might be duplicated in another program. These identifiers are stored in the Windows Registry when the COM component is installed and registered. PowerBASIC programmers reference COM components by their PROGID string, and rarely by their CLSID. However, since these two items are stored in pairs, it is straightforward to retrieve the matching PROGID for a known CLSID, and vice versa.

As mentioned earlier, you don't need to know how a television works internally to use it and benefit from it. Likewise, you don't need to know how a COM Object works internally to use it and benefit from it. You only need to know the intended job of the object, and how to communicate with it from the outside. The internal workings are well "hidden", which is called encapsulation. Since we aren't able to "look inside" a COM Object, it's not possible to directly manipulate internal variables or memory. This provides a increased level of security for the internal code and data.

 

How do you publish an object?

Publishing an object means making it available for access and use by other applications through the facilities of the COM Services of Windows. With some compilers, this requires pages upon pages of code. With PowerBASIC, you'll find it's fairly straightforward. Just add a CLSID GUID and the words "AS COM" to the end of the CLASS statement. Then, add an Interface ID (IID) to the end of the INTERFACE statement. Believe it or not, that's just about it!

$MyClassGuid = guid$("{00000099-0000-0000-0000-000000000008}")
$MyIfaceGuid = guid$("{00000099-0000-0000-0000-000000000009}")
 
CLASS MyClass $MyClassGuid AS COM
  INTERFACE MyInterface $MyIfaceGuid
    INHERIT IAutomation
    METHOD Method1(parm AS LONG)
       CALL abc(parm)
    END METHOD
  END INTERFACE
END CLASS

PowerBASIC handles all the messy details of COM for you. The name of the CLASS (in this case MyClass) will be used as the ProgID for COM registration of the DLL. The GUIDs you selected will be used for the CLSID and IID, so you're ready to go...

 

How are GUIDs used with objects?

A GUID is a "Globally Unique Identifier", a very large number which is used to uniquely identify every interface, every class, and every COM application or library which exists anywhere in the world. GUIDs identify the specific components, wherever and whenever they may be used. A GUID is a 16-byte (128-bit) value, which could be represented as an integer or a string. This item is large enough to represent all the possible values needed.

The PowerBASIC GUID$() function (or a hot-key in the PowerBASIC IDE) can generate a random GUID which is statistically guaranteed to be unique from any other generated GUID. When a GUID is written in text, it takes the form:

{00CC0098-0000-0000-0000-0000000000FF}
When a GUID is used in a PowerBASIC program, it is typically assigned to a string equate, as that makes it easier to reference.
$MyLibGuid   = guid$("{00000099-0000-0000-0000-000000000007}")
$MyClassGuid = guid$("{00000099-0000-0000-0000-000000000008}")
$MyIfaceGuid = guid$("{00000099-0000-0000-0000-000000000009}")
Every COM COMPONENT, every CLASS, and every INTERFACE is assigned a GUID to uniquely identify it,and set it apart from another similar item. As the programmer, you can assign each of these identifiers, or they will be randomly assigned by the PowerBASIC compiler.

When you create objects just for internal use within your programs, it's common to ignore the GUIDs completely. PowerBASIC will assign them for you automatically, so you don't need to give it a thought. However, if you plan to publish an object for any external use through COM services, it's very important that you assign an explicit identifier to each item in your code. Otherwise, the compiler will assign new identifiers randomly, every time you compile the source. No other application could possibly keep track of the changes.

The APPID or LIBID identifies the entire application of library. You specify this item with the #COM GUID metastatement:

#COM GUID  $MyLibGuid
The CLSID identifies each CLASS. You specify this item in the CLASS statement:
CLASS MyClass $MyClassGuid AS COM
  ....
END CLASS
The IID identifies each INTERFACE. You specify this item in the INTERFACE statement:
INTERFACE MyInterface $MyIfaceGuid
   ....
END INTERFACE

 

What is inheritance?

Inheritance is all about code reuse. You can reuse the definitions of an interface, or you can reuse complete sections of code.

INTERFACE INHERITANCE is defined by COM standards, and available for use by any COM object. This form of inheritance applies only to the definition of each item in an interface, rather than the underlying code. Interface inheritance gives you the option to use one interface in multiple classes (objects). Because the interface definition remains identical in each instance, you can often use the identical (or similar) code to manipulate different objects. With this form of inheritance, the programmer must provide appropriate code for each of the Methods and Properties in every implementation of the interface.

IMPLEMENTATION INHERITANCE is the process whereby a CLASS derives all of the functionality of an interface implemented elsewhere. That is, the derived class now has all the methods and properties of this new, extended version of a Base Class! This form of inheritance is offered by PowerBASIC, even though it is not required by the COM Specification.

You can extend the functionality of an interface you created earlier by adding new methods and properties to the derived interface/class. The syntax for adding extra methods (not in the Base Class) is the same as adding methods to a standard class -- just add methods and properties, as always.

You can add to, or replace, the functionality of a particular method or property by coding a replacement which is preceded by the word OVERRIDE. The overriding method must have the same name and signature (parameters, return value, etc.) as the one it replaces. When you implement a new method in a derived class, you may call a method in the Base Class by using the pseudo-object MYBASE. This allows you to extend the original functionality, or replace it entirely.

Inheritance is implemented by use of the INHERIT statement within an INTERFACE / END INTERFACE block. The word INHERIT is followed by the class name and interface name of the code to be inherited. Both are necessary, because COM allows you to have multiple implementations of any particular interface.

CLASS MyClass
  INTERFACE MyFace
    INHERIT IDispatch
    METHOD aaa()
      ' code...
    END METHOD
    METHOD bbb()
      ' code...
    END METHOD
    METHOD ccc()
      ' code...
    END METHOD
    METHOD ddd()
      ' code...
    END METHOD
  END INTERFACE
END CLASS
 
CLASS TheClass
  INTERFACE TheFace
    INHERIT MyClass, MyFace
    OVERRIDE METHOD bbb()
      ' new code
    END METHOD
    OVERRIDE METHOD ddd()
      ' new code
    END METHOD
    METHOD xxx()
    END METHOD
  END INTERFACE
END CLASS
Note that the derived interface "TheFace" first inherits IDISPATCH, and then, all four methods from "MyFace" (aaa,bbb,ccc,ddd). However, because of the OVERRIDE statements, both bbb() and ddd() are replaced by newer versions of these methods. Note that a derived class may be inherited by yet another class, repetitively. The depth of this inheritance is limited only by available memory. The pseudo-object MYBASE may be used within a derived class to access a method in the original base class. For example, if you placed:
MyBase.bbb()
in the above derived code, it would execute the method bbb() in the parent interface/class. You could then use the results to extend or modify actions in your newer code.

When you inherit an interface, the inherited constructor and destructor methods (CREATE and DESTROY) are disabled, in case you wish to change their functionality in the derived interface. If you wish to execute them as-is, you can simply add MYBASE.CREATE and/or MYBASE.DESTROY in the derived CREATE/DESTROY methods.

 

How do you create an object?

This operation is frequently known as "Creating an INSTANCE of an OBJECT." Yes, this is just one more buzz-word -- but you'll hear it frequently.

In order to create an object, you first need an OBJECT VARIABLE. This object variable can be located most anywhere in your program, and have any scope: LOCAL, GLOBAL, THREADED, etc. This object variable is declared by using the name of the interface you wish to access on the object. This is done so that PowerBASIC knows which Methods can be called via this variable. This variable is expected to be a "container" for an OBJECT REFERENCE (that is, a pointer to the actual object). Initially, this variable is automatically set to "NOTHING". If you wish to use the generic DISPATCH interface to access the object, you would use the name DISPATCH instead.

LOCAL object1 AS MyInterface
LOCAL object2 AS DISPATCH
There is actually one more special case, that of an IDBIND DISPATCH interface. Since object creation works the same on those interfaces, as well, we'll have more on that special topic in a later session. So, now that you have two empty object variables, what do you do with them? Use the assignment statement (LET) to create an object!

To create an object, you need to specify a CLASS and an INTERFACE. The interface is implied by the object variable you use, so it only remains that you specify the CLASS name. If the requested CLASS is internal to your program, use the word CLASS:

LET object1 = CLASS "MyClass"
The class name ("MyClass") must be specified as a quoted string literal, which is the name of a class implemented within the program. Since the class is internal (the name is known at compile-time), you may not use a string variable or expression. Upon execution, a new object is created, and a reference to that object is assigned to the object variable object1. The interface requested is determined by the original declaration of object1. If the interface name is DISPATCH, you can call the methods with the OBJECT statement -- otherwise, regular Method and Property references are used for direct interfaces.
LET objvar = NEWCOM ProgID$
LET objvar = GETCOM ProgID$
LET objvar = ANYCOM ProgID$
This form of the LET statement is used to obtain an object reference external to the program using the COM facilities of Windows. If the requested object is in a DLL (an in-process server), you will always use the NEWCOM option, as you're asking Windows to supply a new object. If the request is successful, an OBJECT REFERENCE (a pointer to the object) is assigned to the objvar.

If the requested object is in an EXE (out-of-process server), you may use any of the three options. If the director word NEWCOM is specified, a new instance of a COM application is created. With GETCOM, an interface will be opened on an existing, running application, which has been registered as the active automation object for its class. With ANYCOM, the compiler will first try to use an existing, running application if available, or a new instance if not.

Of course, as with any other LET (assignment) statement, you are free to simply omit the word LET entirely.

If an object creation or assignment fails for any reason, the object variable is set to NOTHING. If this statement fails, no errors are generated, nor is an OBJRESULT set. You should test for success of the operation with ISOBJECT(objvar) before trying to use the object or execute its methods.

But what about the rare case when there's no ProgID$ available? There's an answer for that, too.

LET objvar = NEWCOM CLSID ClassID$
LET objvar = GETCOM CLSID ClassID$
LET objvar = ANYCOM CLSID ClassID$
This new form also obtains a COM object reference, just as in the previous example. However, it is only used in the unusual case of a COM Object which has no ProgID. It works exactly as the original form above, except that it describes the requested object by its 16-byte GUID which is the ClassID of the object.
LET objvar = NEWCOM CLSID ClassID$ LIB DLLPath$
PowerBASIC offers the unique ability to create and reference COM objects without any reference to the registry at all. As long as you know the CLSID (Class ID) and the file path/name of the DLL to be accessed, you can do so with no registry access at all. You don't need a special type of COM server. This technique can be used with any server, whether created by PowerBASIC or another compiler. By using this method of object creation, there is simply no need for the server to be registered at all. That allows you to keep local copies of the COM servers you use, with no chance they will be altered or replaced by another application. You use the above form, where the clause "CLSID ClassID$" identifies the 16-byte Class ID, and the clause "LIB DllPath$" identifies the file path and file name of the COM Server. Once you've obtained the COM object reference in objvar, it is used exactly as you would with a traditional object.

 

How do you duplicate an object variable?

In the previous section, you learned to create an object, which assigns an OBJECT REFERENCE to the object variable:

LOCAL object1 AS MyInterface
LET object1 = CLASS "MyClass"
What if you need to duplicate it? Well, you first must decide whether you want to create a completely new object, or if you just want a second object variable which points the the same object. This is a very important distinction. With two objects, they each have their own set of INSTANCE variables. The variables in each set remain independent of the other set until they are destroyed. You would create two objects by writing:
LOCAL object1, object2 AS MyInterface
LET object1 = CLASS "MyClass"
LET object2 = CLASS "MyClass"
If you have two object variables pointing to the same object, they would share the same set of INSTANCE variables. You would create two OBJECT REFERENCES TO ONE OBJECT by writing:
LOCAL object1, object2 AS MyInterface
LET object1 = CLASS "MyClass"
LET object2 = object1
Of course, now we can take this one step further. You already know that an OBJECT may have two (or even more) interfaces defined in a CLASS. How would you actually use two interfaces on the same object? Just declare an object variable for each interface, much like:
LOCAL object1 AS MyInterface
LOCAL object2 AS HisInterface
LET object1 = CLASS "MYClass"
LET object2 = object1
The code is very much like the preceding example, except that the two object variables are declared as two different interfaces. When the last line is executed, PowerBASIC looks at the object variables to determine if they represent the same interface or not. If they do, it simply creates an extra variable, pointing to the same object. If they differ, PowerBASIC checks object to ensure the new interface is supported. If so, it creates a new OBJECT REFERENCE via the new interface, and assigns it to object2. It's just that simple!

The final issue in this topic is how to destroy an object variable. Generally speaking, you do nothing at all. When an object variable goes out of scope, PowerBASIC will handle all the messy details for you. For the most part, just forget about it. However, in the rare case that you need to destroy an object variable at a specific time and place, you can do do so with the following statement:

object1 = NOTHING
Setting an object variable to NOTHING handles it all for you.

 

How do you call a Direct Method?

First, you should remember that INSTANCE variables may only be accessed from within the object. The only way to access them from the "outside", is by a parameter or return value of a METHOD or PROPERTY function. Of course, Methods and Properties may also utilize the other data scopes: Global, Local, Static, and Threaded.

In PowerBASIC, the basic unit of code in an object is the METHOD. A METHOD is a block of code, very similar to a user-defined function. Optionally, it can return a value, like a FUNCTION, or merely act as a subroutine, like a SUB. Methods are implemented when you write:

METHOD name [alias "altname"] (var as type...) [AS type]
  statements...
  METHOD = expression
END METHOD
Methods can only be called through an object variable, which is an integral part of the calling syntax. The object variable must be valid, that is, it must contain a valid object reference which was assigned to it with the LET statement. If you attempt to call a method on a null object, you'll likely experience a GPF and a total failure of your program. Methods may be called by writing:
Dim ObjVar as MyInterface
Let ObjVar  = Class "MyClass"
 
[CALL] objvar.Method1(param)
Note the word CALL is optional. This example shows how to call "Method1" when "Method1" does not return a value. If it did have a return value, use this form instead:
var = ObjVar.Method1(param)
A PROPERTY is a special type of METHOD, which is only designed to GET or SET data in INSTANCE variables. While the work of a PROPERTY could readily be accomplished with a standard METHOD, this distinction is convenient to emphasize the concept of encapsulation of INSTANCE data within an object. There are two forms of PROPERTY procedures, PROPERTY GET and PROPERTY SET. As implied by the names, the first form is used to retrieve a data value from the object, while the second form is used to assign a value. Properties are implemented:
PROPERTY GET name [alias "altname"] (ByVal var as type...) [AS type]
  statements...
  PROPERTY = expression
END PROPERTY
 
PROPERTY SET name [alias "altname"] (ByVal var as type...)
  statements...
  variable = value
END PROPERTY
When you use PROPERTY SET, the last (or only) parameter is used to pass the value to be assigned. A PROPERTY may be considered "Read-Only" or "Write-Only" by simply omitting one of the definitions. However, if both GET and SET forms are defined for a particular Property, parameters and the property must be identical in both forms, and they must be paired. That is, the PROPERTY SET must immediately follow the PROPERTY GET. It's important to note that all PROPERTY parameters must be declared as BYVAL.

Properties can only be called through an object variable, which is an integral part of the calling syntax. The object variable must be valid, that is, it must contain a valid object reference which was assigned to it with the LET statement.

You can access a PROPERTY GET with:

Dim ObjVar as MyInterface
Let ObjVar  = Class "MyClass"
var = ObjVar.Prop1(param)
You can access a PROPERTY SET with:
Dim ObjVar as MyInterface
Let ObjVar  = Class "MyClass"
 
[CALL] ObjVar.Prop1(param) = expr
Note that the choice of Property procedure is syntax directed. In other words, depending upon the way you use the name, PowerBASIC will automatically decide whether the GET or SET PROPERTY should be called.

In every Method and Property, PowerBASIC automatically defines a pseudo-variable named ME, which is treated as a reference to the current object. Using ME, it's possible to call any other Method or Property which is a member of the class: var = ME.Method1(param)

Methods and Properties may be declared (using AS type...) to return a string, any of the numeric types, a specific class of object variable (AS MyInterface), a Variant, or a user defined Type.

 

What is a Compound Object Reference?

There is an interesting "shortcut" available to you by using "Compound Object References". In some cases, you'll find that you can combine two, three, or more method calls into a single line of PowerBASIC source code.

The notion here is that you may need to execute a METHOD which returns an object variable, just so you can use that temporary object variable to call another method. In fact, you may even find you need to nest this type of operation several levels deep! While this is certainly workable, you may find yourself with a maze of temporary objects and object variables, all of which need to be destroyed at some point.

For example, assuming you have an object variable named MyDBase, which is an instance of the interface named DataBase. The interface DataBase offers a method named ErrorObject which returns an Errors object. Errors is a second interface, which has a method named Count. Count returns a long integer, to tell the number of errors which have occurred. In order to retrieve Count, you would normally have to write:

LOCAL MyErrors as Errors
LET MyErrors = MyDBase.ErrorObject
ErrorCount& = MyErrors.Count
MyErrors = Nothing
However, with Compound Object References, this can be combined into a single line of code:
ErrorCount& = MyDBase.ErrorObject.Count
In particular, note that the temporary object called MyErrors is gone completely, since PowerBASIC automatically handles the lifetime of temporary objects. You can even declare the methods and properties with parameters, if it's appropriate to allow:
ErrorCount& = MyDBase.ErrorObject(item&).Count

 

What is an hResult?

Methods may optionally have an explicit return value which you specifically declare. However, in addition to this, all Automation or Dispatch Methods and Properties have another "Hidden Return Value", which is cryptically named hResult. While the name would imply a handle for a result, it's really not a handle at all, but just a long integer value, used to indicate the success or failure of the Method. After calling a Method or Property, you can retrieve the hResult value with the PowerBASIC function OBJRESULT. The most significant bit of the value is known as the severity bit. That bit is 0 (value is positive) for success, or 1 (value is negative) for failure. The remaining bits are used to convey error codes and additional status information. If you call any object Method/Property (either Dispatch or Direct), and the severity bit in the returned hResult is set, PowerBASIC generates Run-Time error 99: Object error. When you create a Method or Property, PowerBASIC automatically returns an hResult of zero, which implies success. You can return a non-zero hResult value by executing a METHOD OBJRESULT = expr within a Method, or PROPERTY OBJRESULT = expr within a Property.

 

How do you register a COM Component?

All COM Components (COM Servers) must be listed in the system registry. A variety of information is kept there, but the most important is the definition of the PROGID and the CLSID. These are the terms used to uniquely identify the component, so that the operating system can locate them for a client program that wants to use their services. PowerBASIC COM DLL's provide self-registration and unregistration services by automatically exporting two Subs:

Declare Function DllRegisterServer alias "DllRegisterServè|$mp;quot; as long Declare Function DllUnregisterServer alias "DllUnregisterServer" as long

You could write a small executable program to call these registration functions, or use the Microsoft registration utility (REGSVR32.EXE) for that purpose. REGSVR32.EXE is included with Windows.

 

What is a Class Method?

A CLASS METHOD is one which is private to the class in which it is located. That is, it may only be called from a METHOD or PROPERTY in the same class. It is invisible elsewhere. The CLASS METHOD must be located within a CLASS block, but outside of any INTERFACE blocks. This shows it is a direct member of the class, rather than a member of an interface.

CLASS MyClass
  INSTANCE MyVar AS LONG
 
   CLASS METHOD MyClassMethod(ByVal param as long) AS STRING
     METHOD = "My" + STR$(param + MyVar)
   END METHOD
 
  INTERFACE MyInterface
    INHERIT IUnknown
    METHOD MyMethod()
      Result$ = ME.MyClassMethod(66)
    END METHOD
  END INTERFACE
END CLASS
In the above example, MyClassMethod() is a CLASS METHOD, and is always aâL«ssed using the pseudo-object ME (in this case ME.MyClassMethod). Class methods are never accessible from outside a class, nor are they ever described or published in a type library. By definition, there is no reason to have a private PROPERTY, so PowerBASIC does not offer a CLASS PROPERTY structure.

 

What are Constructors and Destructors?

There are two special class methods which you may optionally add to a class. They meet a very specific need: automatic initialization when an object is created, and cleanup when an object is destroyed. Technically, they are known as constructor and destructor methods, and can perform almost any functionality needed by your object: initialization of variables, reading/writing data to/from disk, etc. You do not call these methods directly from your code. If they are present in your class, PowerBASIC automatically calls them each time an object of that class is created or destroyed. If you choose to use them, these special class methods must be named CREATE and DESTROY. They may take no parameters, and may not return a result. They are defined at the class level, so they may never appear within an INTERFACE definition.

CLASS MyClass
  INSTANCE MyVar AS LONG
 
  CLASS METHOD Create()
    ' Do initialization
  END METHOD
 
  CLASS METHOD Destroy()
    ' Do cleanup
  END METHOD
 
  INTERFACE MyInterface
    INHERIT IUnknown
    METHOD MyMethod()
      ' Do things
    END METHOD
  END INTERFACE
END CLASS
As displayed above, CREATE and DESTROY must be placed at the class level, before any INTERFACE definitions. You should note that it's not possible to name any standard method (one that's accessible through an interface) as CREATE or DESTROY. That's just to help you remember the rules for a constructor or destructor. However, you may use these names as needed to describe a method external to your program.

A very important caution: You must never create an object of the current class in a CREATE method. To do so will cause CREATE to be executed again and again until all available memory is consumed. This is a fatal error, from which recovery is impossible.

 

What is DISPATCH?

The DISPATCH INTERFACE is a slower form of interface, originally introduced as a part of Microsoft Visual Basic. An implementation of COM DISPATCH support was introduced in a prior version of PowerBASIC. It has now been substantially improved to offer COM SERVER as well as client support, Dual Interfaces, relaxed typing, exception information, and much more.

When you use DISPATCH, the compiler actually passes the name of the METHOD you wish to execute as a text string. The parameters can also be passed in the same way. The object must then look up the names, and decide which METHOD to execute, and which parameters to use, based upon the text strings provided. As if that weren't enough, DISPATCH requires that all parameters and return values be passed as VARIANT variables, with all those conversions the responsibility of the programmer. That's right, you. This is a slow process. However, DISPATCH is flexible, convenient, and forgiving. Further, you'll find that many scripting languages and application use DISPATCH as their sole method of operation, so continued support is absolutely necessary.

Late Binding

The standard methodology of DISPATCH is called "Late Binding", because nothing is done in advance. No method definitions. No Interface signatures. You can pretty much just start writing code:

LOCAL DispVar AS DISPATCH
LET DispVar = NEWCOM "DispProgID"
OBJECT CALL DispVar.Method1(x&, y$)
It's just that easy. The first line declares an object variable which assumes the DISPATCH interface, while the second line creates an object and assigns a reference to DispVar. The third line just executes a method on the new object.

The OBJECT statement is always used to execute methods in a DISPATCH interface. This differentiates it from direct access, so PowerBASIC can handle your request in the appropriate manner.

It's important to note that this version of PowerBASIC relaxes the strict type checking of Dispatch parameters. While DISPATCH interfaces require that all parameters and return values be passed as a VARIANT variable, this version of PowerBASIC relaxes that requirement for you. You may substitute any COM-compatible variable, and PowerBASIC will convert them automatically to and from Variant variables as an integral part of the OBJECT statement. How could it get easier?

So, how does this work internally?

Well, each method name is assigned a positive integer number as its Dispatch ID (or DispID), to differentiate it from the other methods. In a similar fashion, each parameter is numbered from 0 - n to identify each of them uniquely. When you execute a statement like:

OBJECT CALL DispVar.Method1(x&, y$)
PowerBASIC packages up the Method Name (Method1) and the names of any named parameters (none in this example - more about that later), and passes them to a special DISPATCH function. After a bit of time for lookup, the Dispatch ID (let's say the number 77) is returned. PowerBASIC then converts the two parameters to Variants and packages the whole thing up to call another special Dispatch function. This tells the server to execute Method number 77 using the two enclosed parameters. Finally, it returns with an hResult code to indicate success or failure. That's classic "Late Binding" Dispatch. "Late Binding" is flexible and easy to use because everything is resolved at run-time. That flexibility comes at a price -- it's the slowest form of COM.

ID Binding

So, how can we speed things up?

Well, the worst bottleneck is the name lookup, and that's something we can deal with! We usually know all the METHOD definitions at compile-time. If we can tell the compiler the DispID's and the parameter info at compile-time, one whole step can be eliminated! That's called ID-BINDING of the Dispatch Interface. We create a simple IDBIND Interface, which is written like this:

INTERFACE IDBIND MyDispIfaceName
  MEMBER CALL Method1<77> (WideVal AS Long, WideText AS String)
  MEMBER CALL Method2<78> ()
  MEMBER CALL MethodX<79> ()
END INTERFACE
PowerBASIC can use this IDBIND Interface to create faster Dispatch execution. Just create this structure, and place it in your source code prior to any references. Then, when you create an object variable, just use the IDBIND Interface Name instead of DISPATCH:
LOCAL DispVar AS MyDispIfaceName
LET DispVar = NEWCOM "DispProgID"
OBJECT CALL DispVar.Method1(abc&, xyz$)
must supply interface definitions in your source code.

How do you get this information? Most likely from the PowerBASIC COM Browser! At your convenience, it will scan your system registry, and find any COM objects available. It will create all of the Interface definitions for you with just a click.

Creating a DISPATCH Object

DISPATCH objects are easy to create. The technique is virtually identical to that for direct interfaces. You must first declare the object variable -- if you wish to use "Late Binding", you'll use the generic name DISPATCH.

LOCAL DispVar AS DISPATCH
LET DispVar = NEWCOM "DispProgID"
If you wish to use "ID Binding", you'll use the interface name from your Interface IDBIND structure.
LOCAL DispVar AS MyDispIfaceName
LET DispVar = NEWCOM "DispProgID"
If all went well, you now have an object! (And an object reference in your object variable). Of course, it's always a good idea to use the ISOBJECT(DispVar) function to be certain that the operation was a success. If it failed, an attempt to use the object variable could cause a fatal exception.

What are Connection Points?

Generally speaking, a client module calls a server module to perform specific operations as they are needed. However, in many situations, it's convenient and efficient for a server to notify its client of a condition or event immediately, without forcing the client to inquire about the status. At the appropriate time, the server calls back to a client method, passing information via the method parameters. This is the exact opposite of normal communication, because the server module is now calling the client module. In effect, the client is acting as a server for the purpose of handling these events. In the world of objects, a server which can call such "Event Methods" is said to offer a "Connection Point". A Connection Point can be used with COM objects or internal objects. Further, it may use either a direct interface or the DISPATCH interface. Event methods may take parameters, but may not return a result.

In COM terminology, a server which offers a Connection Point is known as an "Event Source". A client which can attach to a Connection Point and handle events is known as an "Event Sink" or "Event Handler". The terms source and sink are analogous to the electrical engineering terms source and sink.

Perhaps you have a server object which performs complex arithmetic, and may take quite some time to finish. You'd like to notify the client of your progress towards completion at regular intervals. In that way, the client can continue other work, or just notify the user of the status. If a server object offers a Connection Point, it must declare the event interface:

INTERFACE STATUS $StatusGuid AS EVENT
  INHERIT IUNKNOWN
  METHOD Progress(Percent AS LONG)
END INTERFACE

Finally, the server class must include a declaration of the event interfaces it supports via a Connection Point by adding one or more EVENT SOURCE statements within the class definition:

EVENT SOURCE STATUS
EVENT SOURCE DISPATCH

Each server class created by PowerBASIC may offer up to four event interfaces. A client module may subscribe to any or all of these event interfaces. When it's time for the server object to notify the client of an event, the RAISEEVENT statement is used. For the Dispatch interface, OBJECT RAISEEVENT is used instead. RAISEEVENT may only appear within a class which declares the Event Source interface. The concept of RAISEEVENT is very similar to the CALL statement, but it may only be used to execute event methods:

RaiseEvent Status.Progress(10) ' advise the code is 10% done

It should be noted that RaiseEvent does not reference an object variable at all, because it calls any and all Event Methods which are currently attached to the Connection Point. Instead, it references the interface name (in this case "Status"), followed by the name of the Event Method to be executed (in this case "Progress").

The client may choose to support the event by creating the appropriate event code (it must precisely match the declaration in the server), or the client could just ignore the event completely. If supported, the client must have an event method to handle the event, and create an event object to do so. In effect, the client actually becomes an object server for this one purpose. The client code might be something like:

CLASS EventClass AS EVENT
  INTERFACE STATUS AS EVENT
    INHERIT IUNKNOWN
    METHOD Progress(Percent AS LONG)
      CALL DisplayIt(Percent)
    END METHOD
  END INTERFACE
END CLASS

In addition, the client must initiate a connection to the server with EVENTS FROM, and disconnect when done with EVENTS END:

DIM oEvent AS STATUS
oEvent = CLASS "EventClass"
EVENTS FROM MyObject CALL oEvent

' execute some code here...

EVENTS END oEvent

A Connection Point may be attached to one Event Method, multiple Event Methods, or no Event Method at all. Whenever a RAISEEVENT statement is executed, all Event Methods attached to the source object are called, one after another. There is no guarantee of the sequence of the calls, and you must consider the possibility that RAISEEVENT with a ByRef parameter could change the value of a parameter variable before any particular Event Method is executed.

Here is a complete program which demonstrates the execution of a Connection Point in a single, self-contained application. It uses only internal objects. Since the objects are all internal, it is not necessary to assign a GUID to each class and interface.

#COMPILE EXE

CLASS EvClass AS EVENT
  INTERFACE Status AS EVENT
    INHERIT IUNKNOWN
    METHOD Done
      MSGBOX "Done!"
    END METHOD
  END INTERFACE
END CLASS

CLASS MyClass
  INTERFACE MyMath
    INHERIT IUNKNOWN
    METHOD DoMath
      MSGBOX "Calculating..."   ' Do some math calculations here
      RAISEEVENT Status.Done()
    END METHOD
  END INTERFACE

  EVENT SOURCE Status

END CLASS

FUNCTION PBMAIN()
  DIM oMath AS MyMath, oStatus AS Status
  LET oMath = CLASS "MyClass"
  LET oStatus = CLASS "EvClass"

  EVENTS FROM oMath CALL oStatus
  oMath.DoMath
  EVENTS END oStatus
END FUNCTION

Here is a set of programs which demonstrate the execution of a Connection Point using a COM SERVER and a COM CLIENT. It uses an in-process COM server (DLL created with PB/Win 9), and a COM CLIENT as an executable program. First the COM SERVER:

#COMPILE DLL "EvServer.dll"

$EvIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000002}")
$MyClassGuid = GUID$("{00000098-0000-0000-0000-000000000003}")
$MyIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000004}")

INTERFACE Status $EvIFaceGuid AS EVENT
  INHERIT IUNKNOWN
  METHOD Done
END INTERFACE

CLASS MyClass $MyClassGuid AS COM
  INTERFACE MyMath $MyIFaceGuid
    INHERIT IUNKNOWN
    METHOD DoMath
      MSGBOX "Calculating..."   ' Do some math calculations here
      RAISEEVENT Status.Done()
    END METHOD
  END INTERFACE

  EVENT SOURCE Status

END CLASS

Next the COM CLIENT:

#COMPILE EXE "EvClient.exe"

$EvClassGuid = GUID$("{00000098-0000-0000-0000-000000000001}")
$EvIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000002}")
$MyIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000004}")

CLASS EvClass $EvClassGuid AS EVENT
  INTERFACE STATUS $EvIFaceGuid AS EVENT
    INHERIT IUNKNOWN
    METHOD Done
      MSGBOX "Done!"
    END METHOD
  END INTERFACE
END CLASS

INTERFACE MyMath $MyIFaceGuid
  INHERIT IUNKNOWN
  METHOD DoMath
END INTERFACE

FUNCTION PBMAIN()
  DIM oMath AS MyMath
  DIM oStatus AS STATUS

  LET oMath = NEWCOM "MyClass"
  LET oStatus = CLASS "EvClass"

  EVENTS FROM oMath CALL oStatus
  oMath.DoMath
  EVENTS END oStatus
END FUNCTION

 

Enumerating Collections

A collection is simply a set or group of items, where each can be accessed through its own Interface. For example, Microsoft Word&tm; can have multiple documents open at the same time, and it can provide an Interface reference for each open document.

Therefore, enumerating a collection is simply a matter of determining the number of items in the collection, looping through and retrieving the appropriate information for one or more Interface members of the collection.

We'll start off with the Visual Basic syntax and show how to perform the same kind of task with PowerBASIC.

Visual Basic syntax for enumerating a collection looks something like this:

Dim Item  As InterfaceItem

Dim Items As InterfaceItemsCollection

[statements]

For Each Item In Items

  'do something with the Item.member Method/Property, e.g.,

  var$ = Item.StringProp

Next

In PowerBASIC, we can perform the same enumeration. For example:

DIM oItem  AS InterfaceItem

DIM oItems AS InterfaceItemsCollection

[statements]

OBJECT GET oItems.Count TO c&

FOR Index& = 1 TO c&

  OBJECT GET oItems.Item(Index&) TO oItem

  'do something with the Item.member Method/Property, e.g.,

  OBJECT GET oItem.StringProp TO var$

NEXT

 

What are Type Libraries?

A Type Library is a block of data which describes one or more COM Object Classes. The internal format of the data is not important, because it is seldom accessed by application programs. Typically, it is only accessed by COM Browsers such as PBROW.EXE (supplied with PowerBASIC), TypeLib Browser from Jose Roca, or OLEVIEW.EXE from Microsoft. In the unusual circumstance that you must access this data directly, the Windows API provides numerous functions for just that purpose.

A Type Library is usually supplied by the author of the COM server. It's frequently supplied as a standalone data file with a file name extension of TLB. The data can also be embedded as a resource in the associated DLL or EXE. In practice, you would generally use a COM Browser to extract enough information about a COM Object to allow you to use these classes in your program. Generally speaking, a Type Library usually supplies specific details about every METHOD and PROPERTY (function), and the parameters of each of them. This would include the names, data types, return values, and more. The Type Library may also offer information about related equates, User-Defined-Types, and more. To include a numeric equate in your type library, just append the words AS COM to the equate definition:

%ABCD = 99 AS COM

Traditionally, it was common to use Interface Definition Language (IDL) to create the source code for the definitions you wish to describe in a Type Library. IDL was created specifically for this purpose and resembles C++ syntax. Once the source code was written, you would use Microsoft's MIDL Compiler to create the final Type Library. That's a fairly cumbersome process.

With PowerBASIC, it's a bit simpler than that. Whenever you create a COM server, simply add the #COM TLIB ON metastatement to your source and your Type Library will be created automatically. You can prevent a Type Library from being created by using the #COM TLIB OFF metastatement. A Type Library is created with the same primary name as your COM server, and a file extension of TLB. That is, if you create a COM server named XX.DLL, PowerBASIC will name the Type Library as XX.TLB. The Type Library offers a description of every published class on the server. You can then use any COM Browser to display the type information in a format that meets your needs. The PowerBASIC COM Browser converts it directly to PowerBASIC source code declarations which can then be dropped into your COM client program. If any of your Methods or Properties use data types not supported by Type Libraries, you will receive a Error 581 - Type Library creation error. If you wish to create a Type Library for you COM server, then only use data types that are compatible with Type Libraries, which are BYTE, WORD, DWORD, INTEGER, LONG, QUAD, SINGLE, DOUBLE, CURRENCY, OBJECT, STRING, and VARIANT.

As mentioned earlier, you can consolidate your distribution files by embedding your Type Library right into your DLL or EXE as a resource. A utility program named PBTYP.EXE is provided for just that purpose. PBTYP.EXE is executed with one or two command line parameters used to specify the files to be used in the embedding process. The syntax is:

PBTYP.EXE   TargetFile  [ResourceFile]

The PBTYP.EXE utility requires that you supply two or three files: the Target File (the DLL or EXE which receives the resource), the TypeLib File (the Type Library to be embedded), and optionally a resource file to be used. Since it's assumed that the Target File and the TypeLib file share the same primary name, only the Target file name is needed. If an extension is not supplied, the default of ".DLL" is used. When executed, PBTYP.EXE scans the original resource file (such as ABC.RC), and replaces any references to a Type Library with a reference to the new Type Library. It then compiles it to a resource object file (such as ABC.RES), and then creates a final PowerBASIC version (such as ABC.PBR). Finally, it removes any prior resource from the target file, and replaces it with the newly created resource. It should be noted that RC.EXE and PBRES.EXE must be present in your path for the process to complete.

 

How are GUIDs used with objects?

A GUID is a Globally Unique Identifier, a very large number which is used to uniquely identify every interface, every class, and every COM application or library which exists anywhere in the world. GUIDs identify the specific components, wherever and whenever they may be used. A GUID is a 16-byte (128-bit) value, which could be represented as an integer or a string. This item is large enough to represent all the possible values needed.

The PowerBASIC GUID$() function (or a hot-key in the PowerBASIC IDE) can generate a random GUID which is statistically guaranteed to be unique from any other generated GUID.

When a GUID is written in text, it takes the form:

{00CC0098-0000-0000-0000-0000000000FF}

When a GUID is used in a PowerBASIC program, it is typically assigned to a string equate, as that makes it easier to reference.

$MyLibGuid   = GUID$("{00000099-0000-0000-0000-000000000007}")
$MyClassGuid = GUID$("{00000099-0000-0000-0000-000000000008}")
$MyIfaceGuid = GUID$("{00000099-0000-0000-0000-000000000009}")

Every COM COMPONENT, every CLASS, and every INTERFACE is assigned a GUID to uniquely identify it, and set it apart from another similar item. As the programmer, you can assign each of these identifiers, or they will be randomly assigned by the PowerBASIC compiler.

When you create objects just for internal use within your programs, it's common to ignore the GUIDs completely. PowerBASIC will assign them for you automatically, so you don't need to give it a thought. However, if you plan to publish an object for any external use through COM services, it's very important that you assign an explicit identifier to each item in your code. Otherwise, the compiler will assign new identifiers randomly, every time you compile the source. No other application could possibly keep track of the changes.

The APPID or LIBID identifies the entire application or library. You specify this item with the #COM GUID metastatement:

#COM GUID $MyLibGuid
The CLSID identifies each CLASS. You specify this item in the CLASS statement:
CLASS MyClass $MyClassGuid AS COM
  [statements]
END CLASS

The IID identifies each INTERFACE. You specify this item in the INTERFACE statement:

INTERFACE MyInterface $MyIfaceGuid
  [statements]
END INTERFACE

 

Built-in Interfaces

The compiler provides a set of built-in Interfaces, including: