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?
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
GUID's 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.
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?
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:
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.
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.
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.
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.
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.
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!
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. GUID's 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:
When a GUID is used in a PowerBASIC program, it is typically
assigned to a string equate, as that makes it easier to reference.
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:
The CLSID identifies each CLASS. You specify this item in the CLASS
statement:
The IID identifies each INTERFACE. You specify this item in the
INTERFACE statement:
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.
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:
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.
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:
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.
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.
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.
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:
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:
[code]
LOCAL object1, object2 AS MyInterface
LET object1 = CLASS "MyClass"
LET object2 = CLASS "MyClass"
de]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:
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:
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:
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:
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:
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:
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:
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:
You can access a PROPERTY SET with:
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:
However, with Compound Object References, this can be combined into
a single line of code:
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:
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 DLLs provide self-registration
and unregistration services by automatically exporting two Subs:
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.
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.
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:
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:
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:
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:
/---------------------------------------------------------\
| 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.
If you wish to use "ID Binding", you'll use the interface name from
your Interface IDBIND structure.
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:
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:
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:
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:
In addition, the client must initiate a connection to the server with EVENTS FROM, and disconnect when done with EVENTS END:
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.
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:
Next the COM CLIENT:
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™ 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:
In PowerBASIC, we can perform the same enumeration. For example:
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:
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.
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:
The CLSID identifies each CLASS. You specify this item in the CLASS statement:
The IID identifies each INTERFACE. You specify this item in the INTERFACE statement:
Built-in Interfaces
The compiler provides a set of built-in Interfaces, including:
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
GUID's 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.
Code:
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
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?
Code:
Function PBMain() Dim Stuff as MyInterface Let Stuff = Class "MyClass" x& = Stuff.BumpIt(77) End Function
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:
Code:
INTERFACE MyInterface INHERIT IAutomation METHOD Method1(parm AS LONG) PROPERTY GET Prop1() AS STRING PROPERTY SET Prop1(ByVal Text AS STRING) END INTERFACE
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.
Code:
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
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.
Code:
INTERFACE CustomIface INHERIT IUnknown METHOD MethodDef()... END INTERFACE
for direct access only. It uses custom calling conventions and does
not support an hResult (OBJRESULT) return value.
Code:
INTERFACE AutoIface INHERIT IAutomation METHOD MethodDef()... END INTERFACE
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.
Code:
INTERFACE DispatchIface INHERIT IDispatch METHOD MethodDef()... END INTERFACE
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.
Code:
INTERFACE DualIface INHERIT IDispatch METHOD MethodOne <76> () METHOD MethodTwo <77> () END INTERFACE
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!
Code:
$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
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. GUID's 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:
Code:
{00CC0098-0000-0000-0000-0000000000FF}
assigned to a string equate, as that makes it easier to reference.
Code:
$MyLibGuid = guid$("{00000099-0000-0000-0000-000000000007}") $MyClassGuid = guid$("{00000099-0000-0000-0000-000000000008}") $MyIfaceGuid = guid$("{00000099-0000-0000-0000-000000000009}")
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:
Code:
#COM GUID $MyLibGuid
statement:
Code:
CLASS MyClass $MyClassGuid AS COM .... END CLASS
INTERFACE statement:
Code:
INTERFACE MyInterface $MyIfaceGuid .... END INTERFACE
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.
Code:
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
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:
Code:
MyBase.bbb()
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.
Code:
LOCAL object1 AS MyInterface LOCAL object2 AS 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:
Code:
LET object1 = CLASS "MyClass"
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.
Code:
LET objvar = NEWCOM ProgID$ LET objvar = GETCOM ProgID$ LET objvar = ANYCOM ProgID$
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.
Code:
LET objvar = NEWCOM CLSID ClassID$ LET objvar = GETCOM CLSID ClassID$ LET objvar = ANYCOM CLSID ClassID$
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.
Code:
LET objvar = NEWCOM CLSID ClassID$ LIB DLLPath$
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:
Code:
LOCAL object1 AS MyInterface LET object1 = CLASS "MyClass"
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:
[code]
LOCAL object1, object2 AS MyInterface
LET object1 = CLASS "MyClass"
LET object2 = CLASS "MyClass"
de]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:
Code:
LOCAL object1, object2 AS MyInterface LET object1 = CLASS "MyClass" LET object2 = object1
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:
Code:
LOCAL object1 AS MyInterface LOCAL object2 AS HisInterface LET object1 = CLASS "MYClass" LET object2 = object1
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:
Code:
object1 = NOTHING
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:
Code:
METHOD name [alias "altname"] (var as type...) [AS type] statements... METHOD = expression END METHOD
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:
Code:
Dim ObjVar as MyInterface Let ObjVar = Class "MyClass" [CALL] objvar.Method1(param)
"Method1" when "Method1" does not return a value. If it did have
a return value, use this form instead:
Code:
var = ObjVar.Method1(param)
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:
Code:
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
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:
Code:
Dim ObjVar as MyInterface Let ObjVar = Class "MyClass" var = ObjVar.Prop1(param)
Code:
Dim ObjVar as MyInterface Let ObjVar = Class "MyClass" [CALL] ObjVar.Prop1(param) = expr
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:
Code:
LOCAL MyErrors as Errors LET MyErrors = MyDBase.ErrorObject ErrorCount& = MyErrors.Count MyErrors = Nothing
a single line of code:
Code:
ErrorCount& = MyDBase.ErrorObject.Count
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:
Code:
ErrorCount& = MyDBase.ErrorObject(item&).Count
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 DLLs provide self-registration
and unregistration services by automatically exporting two Subs:
Code:
Declare Function DllRegisterServer alias "DllRegisterServè|$mp;quot; as long Declare Function DllUnregisterServer alias "DllUnregisterServer" as long
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.
Code:
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
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.
Code:
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
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:
Code:
LOCAL DispVar AS DISPATCH LET DispVar = NEWCOM "DispProgID" OBJECT CALL DispVar.Method1(x&, y$)
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:
Code:
OBJECT CALL DispVar.Method1(x&, y$)
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:
Code:
INTERFACE IDBIND MyDispIfaceName MEMBER CALL Method1<77> (WideVal AS Long, WideText AS String) MEMBER CALL Method2<78> () MEMBER CALL MethodX<79> () END INTERFACE
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:
Code:
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.
Code:
LOCAL DispVar AS DISPATCH LET DispVar = NEWCOM "DispProgID"
your Interface IDBIND structure.
Code:
LOCAL DispVar AS MyDispIfaceName LET DispVar = NEWCOM "DispProgID"
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:
Code:
INTERFACE STATUS $StatusGuid AS EVENT INHERIT IUNKNOWN METHOD Progress(Percent AS LONG) END INTERFACE
Code:
EVENT SOURCE STATUS EVENT SOURCE DISPATCH
Code:
RaiseEvent Status.Progress(10) ' advise the code is 10% done
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:
Code:
CLASS EventClass AS EVENT INTERFACE STATUS AS EVENT INHERIT IUNKNOWN METHOD Progress(Percent AS LONG) CALL DisplayIt(Percent) END METHOD END INTERFACE END CLASS
Code:
DIM oEvent AS STATUS oEvent = CLASS "EventClass" EVENTS FROM MyObject CALL oEvent ' execute some code here... EVENTS END oEvent
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.
Code:
#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
Code:
#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
Code:
#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™ 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:
Code:
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
Code:
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:
Code:
%ABCD = 99 AS COM
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.
Code:
$MyLibGuid = GUID$("{00000099-0000-0000-0000-000000000007}") $MyClassGuid = GUID$("{00000099-0000-0000-0000-000000000008}") $MyIfaceGuid = GUID$("{00000099-0000-0000-0000-000000000009}")
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:
Code:
#COM GUID $MyLibGuid
Code:
CLASS MyClass $MyClassGuid AS COM [statements] END CLASS
Code:
INTERFACE MyInterface $MyIfaceGuid [statements] END INTERFACE
Built-in Interfaces
The compiler provides a set of built-in Interfaces, including:
- ICLASSFACTORY
- ICONNECTIONPOINTCONTAINER
- ICONNECTIONPOINT
- IDISPATCH
- IUNKNOWN