ComHelper
COM and ACTIVEX
Programming made easier in HotBasic
Part III – Using
a helper file with the Use Invoke option
Part III – Using a helper file with the Use Invoke
option
Part IV – Using a helper file
without the Use Invoke option
Part V – Using an ActiveX helper
file
Contents of the Invoke-based helper
file
By default, the Use Invoke option is checked.
In this case, the helper file contains:
- some generic declarations for COM programming
(unless you unchecked the Include COM Generics option),
- the declaration of the data types you chose to include (if any),
- the declaration of the constants you chose to include (if any),
- templates to help you use the interfaces you selected.
Note that the interface part is totally generic, that
is, no specific code is generated. The interface part of the helper file simply
consists of templates for you to copy and paste.
Include the helper file into your main program, by
adding:
$include "XXX.inc"
where XXX is the name you gave your helper file.
Note: if you want to use COM objects from different
files, you can generate a helper file for each of them and include them all in
your main program.
Declaring an Invoke-based Interface
An Interface is declared as a HotBasic INTERFACE
variable.
dim intf as INTERFACE
For the interface to be functional, it must refer to
an existing object. You typically do that by using CreateObject:
Intf.CreateObject("Word.Application")
By the way, you can notice in this example that in the philosophy of COM
programming, an application is considered an object! That's because you can run
an instance of the application, and there is a set of subs and functions (in
other words an interface) that works with it.
There are other ways to initialize an interface. For example, some functions return
interfaces. (More on this below).
Using an Invoke-based Interface
You access an object’s subs and functions with 3
HotBasic keywords: invoke, getnum and getstr.
Invoke is used to make a call that doesn’t return a
result. You specify the name of the method to call as the first argument of
invoke. (“Method” is a synonym of sub). Example:
intf.invoke("Quit")
Getnum is used when the call returns a number.
Example:
x=intf.getnum("Height")
Sometimes the result is a pointer to another object.
Example:
newdocument=docs.getnum("Add", 1)
'new document
Getstr is used when the call returns a string.
Example:
s$=intf.getstr("Name")
The helper file contains templates for all the
object’s methods and properties, indicating their syntax. What some methods do
is pretty straightforward from their name and the kind of arguments they take.
For some others, it is not obvious and you should look for some documentation
or some third-party code to help you.
(I suggest you familiarize with COM programming by
porting code from other languages to HotBasic with the help of ComHelper. This
way you'll get a feeling of how COM works.)
Passing arguments to a method
Some methods take arguments. Just add them as
additional parameters after the method name. Example:
wb.invoke("Navigate", a$, 1)
Some arguments are optional, that is, you may omit
them. In the corresponding template, those arguments bear the [optional]
comment. In order to omit an optional parameter, you must pass void
instead. Void is a pre-defined variant used for that purpose. Example:
dd.invoke("Add", void, void, void, void)
intf2.invoke ("Method", intf1.pointer)
However the following worked:
defint x=intf1.pointer : intf2.invoke ("Method", x)
So if you experience strange failures, review the
arguments you pass to interface methods.
(advanced) Filling
the interface pointer
An interface has a .pointer
property, which is an integer initialized with 0. Actually you can do nothing
with the interface until you have somehow filled its .pointer with a value.
An interface with .pointer=0 is like a knob without a door, that is, it
is not connected to an object and therefore does nothing useful. It can cause
nice application crashes, though.
There are 4 ways to set
.pointer:
1 - some interfaces are
associated to objects (they are their
"default interfaces")
In that case, use the CreateObject
function to create the corresponding object and return an interface to it.
Example:
dim cal as
INTERFACE
cal.CreateObject("Microsoft.Calendar")
In the example above,
“Microsoft.Calendar” is the identifier of the object type. It is called the object’s
ProgID.
Alternatively, you can pass
the object’s CLSID. This is an uncivilized-looking string between curly
brackets. The result is the same. Example:
cal.CreateObject("{8E27C92B-1264-101C-8A2F-040224009C02}")
For instance, the Word
Application interface has a property called Documents that returns a Documents
interface. Example:
$include
"word.inc" 'file generated by ComHelper
dim a as INTERFACE 'Application
dim dd as INTERFACE 'Documents
a.CreateObject("Word.Application")
dd.pointer=a.getnum("Documents")
'result is an interface pointer
This is the equivalent of
Visualbasic's "Set" statement: Set dd=a.Documents
For instance the CreateILockBytesOnHGlobal
function creates a memory object and returns a pointer to an ILockBytes
interface.So you can write:
declare function
CreateILockBytesOnHGlobal lib ...
$include "lockbytes.inc" 'file generated by ComHelper
dim o as INTERFACE
o.pointer = CreateILockBytesOnHGlobal (...)
4 – the GetObject returns
an interface pointer
GetObject is a ComHelper
function meant to provide the functionality of Visual Basic’s GetObject function.
It is used to retrieve an interface pointer from an identifier name. You can
then use the interface’s Invoke, Getnum and Getstr. An example can be found in
the Examples folder.
(advanced) Enumerating the members of a
collection
Some objects really represent a set of other objects.
They are called “collections”. If you’ve done some Visual Basic programming in
Microsoft Office, you’ve probably encountered them: collections like Documents, Worksheets, etc.
You sometimes need to loop through all the elements in
a collection. In Visual Basic, this is done with the For Each keyword:
For each Doc in
Documents ... Next
This is not supported in Hot Basic. However, ComHelper
provides you with an object called ENUM that helps you automate that. The
syntax is the following.
Dim foreach as
ENUM 'an object of the ENUM type
Dim item as INTERFACE 'item will receive the pointers to the objects in the
collection, one by one
foreach.Enum(Collection) 'Collection is the INTERFACE you want to enumerate.
item.pointer=foreach.NextItem 'get
the next item in the collection
if item.pointer then [use this item
to do what you need] 'You MUST test for .pointer<>0 before using it
foreach.EndEnum 'end of the loop
To what objects does this apply? It applies to Collections. Technically speaking, that
means interfaces that support the _NewEnum
method. If you try ENUM on an interface that doesn’t support it, you’ll get a
warning message.
Application
crashes
COM programming is not very
simple so when you venture into it, you may experience some application crashes
when debugging your programs. Don't worry, this is I think almost inevitable.
Personally I've gone through hundreds of them and, thanks to the stability of
recent Windows systems, I've never lost data or needed to restart my computer
as a consequence of such a crash.
What application crashes
indicate is VERY OFTEN that you've used an Interface while its .pointer
is zero. So when you initialize an interface variable, it is wise to check that
.pointer did receive a non-zero value before proceeding.
COMRESULT
variable
The ComHelper include file
declares a global variable (integer) called COMRESULT. When you access an
interface property or method, COMRESULT is set so as to indicate if the
operation succeeded or not.
The code for success is
ZERO. Other values indicate that an error has occurred.
ComHelper also provides you
with the WinErrorMsg function that translates the error code to a text
message.
Example:
myInterface.invoke("DoThis")
if COMRESULT then showmessage WinErrorMsg(COMRESULT)
Unicode strings
Unicode has nothing to do
with unicorns. It is a standard for coding character strings. This standard is
different from the ANSI code that HotBasic uses.
Unfortunately, COM interfaces
normally only recognize unicode strings. So when you use a string as a COM
function parameter, it must be unicode. And likewise, the result of a COM
function, when it is a string, is coded in unicode.
That means some coding and decoding
is needed. ComHelper takes care of that conversion. It means that in simple
cases, you don't need to worry. Examples:
tli.pointer=tli.getnum("TypeLibInfoFromFile", filename)
defstr
sname=tli.getstr("Name")
Just use your normal
HotBasic "filename" string. The conversion to unicode is included in
ComHelper. Similarly, "Name" returns an ANSI string that you can
display, write to a text file, etc. The conversion is already taken care of.
HOWEVER, things may not
always be so simple. For instance:
- some information might be lost in those ANSI
<--> unicode conversions if special characters or foreign characters are
used;
- as an exception to the rule, some COM
objects may actually use ANSI strings and they'll be surprised to receive
Unicode (so far I haven’t seen any but who knows?).
So if you're getting
strange results when working with strings, you may want to try and disable the
unicode conversion by tweaking the routines inside the COM Generics routines.
Using foreign character sets
You may want to send text with foreign encoding to an interface. For
instance, sending a string of Chinese characters to a speech synthesizer. In
order to do that, you must specify that strings will have a specific encoding.
Every encoding has a serial number, called its code page. Look up on the
internet or somewhere what the code page is. For instance, it is 0 for ANSI
strings, 1200 for unicode, 936 for Chinese GB, 65001 for UTF-8, etc.
Then set the COMencoding internal variable to that value. ComHelper
will perform the necessary conversions from and to unicode. Example:
COMencoding =
936 'Chinese GB encoding
defint x = voice.getnum("Speak", "ÎÒÊÇ·¨¹úÈË¡£", 0)
COMencoding = 0 'reset to default
Conclusion of
this section
If you've read through this
section, unless you're already a COM expert, you should feel a little dizzy.
That's normal: COM programming is oh so powerful but not so simple. At least,
you should now be ready to take a look at the examples that come with ComHelper
and, more important, start experimenting by yourself.
And remember: the internet
is an invaluable source of code snippets and documentation to start with.
. Using
a helper file without the Use Invoke option