COMatePLUS

COMatePLUS - Easy access to COM via (OLE) automation.

Copyright © 2009

http://www.nxSoftware.com

With thanks to...

Thomas Schulz (ts-soft) http://www.realsource.de
Provided code for interfacing with ActiveX controls.
Tested early versions of COMate (which is now COMatePLUS).
Created some of the COMate demo programs.

Timo Harter (freak) http://freak.purearea.net/
Provided the know-how and code for connecting EventSinks with COM objects.

With thanks to Kiffi and mk-soft for their work on the PureDispHelper demo programs which have been translated for use with COMatePLUS.

http://www.nxSoftware.com


Overview

COM OVERVIEW.

The subject of COM is of course a vast and technical one. However, the business of using COM in the sense of making use of a COM component is really not a difficult one and, considering how many COM components there are 'out there', learning how to use and tap in to this wealth of resource can only be to our considerable advantage.

So, million dollar question number 1; what exactly is COM?
Put simply, COM is an interface standard designed to allow different software 'components' to communicate and interact and, unlike many other such 'standards' which are defined at the source code level, COM is defined at the binary level. This means that COM components are not tied to any particular programming language. A COM component created in, say, c++, could be consumed by an application (client) created in say Purebasic -and visa-versa!

However, we are not concerned here with the fiddly business of creating COM components. Instead, we simply wish our applications to be able to make use of the zillions of 'components' which have been created for us, and so instead of asking 'what exactly is COM', we should perhaps instead be asking; what exactly is a COM component?

Well, a component is simply a chunk of executable code sitting inside either a dll file (the most common scenario) or within a .exe executable. Such a component may reside on the local machine or on a machine across a network (which is all the same to COM). This code can be considered as consisting of a number of almost self-contained 'objects' offered to client applications which are able to 'connect' to the component. For example, a component created for the purpose of providing basic math processing functionality might expose an object dedicated to trigonometric functions and another separate object for logarithmic functions and so on. From the point of view of our client application wishing to make use of this component, each object is a self-contained entity sitting there, waiting to be called on at any time to provide the services for which it was designed. Our client need not care how each object is implemented; which coding styles were employed (oop or procedural), which mathematical techniques were used to implement the various functions etc. In this respect the client is advised to happily mind it's own business!

A component then is an impenetrable 'black box' which, when us lowly developers are armed with a little bit of know how, can be bolted on to the side of our applications and called upon when it's services are required. A very nice arrangement - very nice indeed, at least in principle!

If this is sounding a little OOPish (Object Oriented Programming), then I will admit that the obvious comparisons are inevitable. It must be remembered, however, that OOP is a programming paradigm rather than a binary standard and thus there is a real distinction to be made between OOP as applied to the design of a source code program and a COM component which can itself be created in a programming language which does not offer OOP at all! What is arguably true, is that the means by which a client application typically 'connects' (or interfaces) with a COM component is an example of OOP programming, even if such concepts are applied at too 'low a level' for the underlying programming language used to create the client to recognise or even acknowledge.

Million dollar question number 2; how do we go about 'interfacing' our application with a COM component?
Ah, an excellent question and the one providing what is perhaps the major hurdle for those inexperienced with COM from proceeding into the murky depths! First, let us conceptualise a COM component a little further.

A component consists of a set of functions normally related by a common purpose. E.g. a component written to provide a library of mathematics functions would undoubtedly offer functions such as sin, cos, tan, atan, sqrt, log, exp... and so on. Now rather than mixing these functions up into a right royal mess, a sensible component writer would instead 'group' these functions together into tightly knit groups of related functions - called objects. Perhaps he would decide on the following grouping :

Trigonometric : sin(), cos(), tan(), asin(), acos(), atan(), ......

Logarithmic : log(), ln(), exp(), ......

Hyperbolic : sinh(), cosh(), tanh(), asinh(), ......

Other : sqrt(), croot(), mod(), ......

and so on.

We can now picture the math component quite nicely :

In COM 'speak' the groups are called 'objects' and the functions sin(), cos() etc. are known as object methods (or just methods) and in essence this is exactly what a COM component is, or at least this is the 'face' it shows to our client applications. What goes on behind the scenes to implement these methods is of no concern to our client applications.

So, to the question of how our client applications interface with a given component; well, the first job is one of locating the binary file housing the component. That is a job we essentially leave to Windows since it insists that all COM components register themselves on the computer in which the underlying binary file resides. Registering involves the component making various entries in the registry which, amongst other things, point to the location of the underlying binary file. When our client application wishes to 'interface' with a given component, it simply asks Windows to rush off and trawl the registry looking for the particular component etc. Of course, if the component has not registered itself with the system then Windows will return with it's tail between it's legs to inform us that our application should make other arrangements!

A successful trawl through the registry will result in Windows instigating 'negotiations' with the component file and, with all being well, will return with a pointer to one of the objects supported by the component; usually an object which our client application requested in the first place. For example, an application making use of our mathComponent might well see fit to request that Windows track down the component file and return a 'Trigonometric' type object. On a successful outcome our application will then be in a position to start calling the underlying object methods, in this case the sin(), cos(), tan() ... methods.

Simple!

Okay, where's the catch? There must be a catch right?

Actually, whilst this is a simple business in principle; there are a couple of uhm, complications shall we say! A couple of points which even now the discerning (and wide awake) reader will already be contemplating and wondering if I myself have gone completely nuts! This leads nicely to ...

Million dollar question number 3; is srod completely nuts?
In a word; yes!

Questions of my sanity aside, however, we have reached the pivotal point in this discussion; namely the point where we briefly touch on what I would call the 'traditional' way of connecting with and using COM components and all of the complications inherent therein before turning, finally, to COMate itself!

As you will soon appreciate (if not already familiar with OLE automation) COMate takes a somewhat different approach to interfacing with COM components. An approach akin to those taken by many scripting languages in which simplicity is the first and foremost consideration when utilising COM components. An approach in which the burden of using such components is largely removed from the compiler and placed instead at the feet of the component itself. And simplicity is my middle name!

Before you can fully appreciate 'COM automation' however, we should really delve just a little further into just how the 'traditional' approach to using COM components operates.
Accessing COM components through interfaces.

Look again at our math component :

All nicely packaged and 'raring to go'!

As mentioned above, the first task for an application wishing to make use of our component is one of bullying Windows into rummaging through the registry searching for the underlying component file etc. That's good then; except how does Windows know which component it is looking for? I can't really expect Windows to accept my asking for the 'mathComponent' because there is nothing about the name 'mathComponent' which guarantees to Windows that it will not find more than one such component in the registry! What if two different component vendors had independently created their own math components and both settled, inadvertently, upon the same name and both of these components had been registered on my machine? This would be throwing what is technically known as a 'spanner in the works'!

This problem was resolved by the use of what are termed GUID's (Globally Unique IDentifiers) which are 16-byte (128 bit) quantities, each of which is used to uniquely identify a component (or an object) etc. Thus, our two conflicting math components would be given their own unique GUID and it is this we supply to Windows when asking it to rush off in search of our sought after component.

Of course, we are left with the business of tracking down the GUID of our mathComponent before we can instruct Windows to go and locate the component file etc. but we would expect the component vendor to provide us with that information.

Okay then, let us suppose that, armed with the appropriate GUID, Windows has successfully navigated the registry and has not only located the component file belonging to our math component (and loaded it into memory), but has successfully negotiated the creation of a nice 'Trigonometric' type object for our consumption which it then duly returns to our application.

The question now is "what the hell are we supposed to do with this 'object'?" "How do we access the object methods such as sin() or cos()?"

Well, with 'traditional' methods we must now turn to our language compiler (e.g. Purebasic, c++, Delphi...) and ask it a question like :

"Hey, if at run-time I can lay my hands on a 'Trigononometric' object obtained from the mathComponent COM component, would you know how to use it, would you know how to call it's methods?"

The answer, at least from the 3 programming languages mentioned above, would be a resounding 'yes - providing you tell me which methods the object exposes (sin(), cos() etc.) and the order in which they are listed and what parameters each method takes and the return type etc.' Remember that, at the end of the day, a COM component is merely a binary file and so we cannot expect our language compiler to go take a look at it and be able to determine this information for itself. This information is not stored within component files for one thing!

You see, when I say that Windows retrieves a 'Trigonometric' object, what it actually retrieves is a pointer to a memory structure created by the component itself and part of this structure (as defined by the COM standard) points to the method functions themselves (it actually points to what is called a 'virtual table' which is a table of pointers to the method functions.) It is these method pointers which allow our client application to call the methods and all our language compiler needs to resolve the individual function calls, is a detailed listing of the functions themselves. (It would be no good my application calling the cos() method only to be sent to the function implementing tan() etc!)

The point of this lecture is that, with 'traditional' access to COM components, there is quite a heavy burden on the compiler being used to create the client application in that not only must it create the object code necessary to call the method functions, it must do so by relying on 'interface definitions' which list all the method functions and their parameters etc. So if we are creating an application which will be working with, say, 200 different kinds of objects obtained from various COM components, then the compiler must have advance knowledge of the 200 objects and their underlying method functions in order to be able to create the proper function calls. It must also have advance knowledge of all of the appropriate GUID's (each of the 200 objects will have it's own GUID!) This information would be placed in header files in the case of c++ and in residents files (or simple source code includes) in the case of Purebasic etc.

Because of all of this 'advance' work undertaken by the compiler, interfacing with COM components using this method is often called 'early binding' as opposed to... 'late binding' which is the focus of the COMate library. Of course, the advantages of having the compiler undertake a lot of this work is that the resulting application can run as fast as possible in terms of it's COM operations. The whole business of accessing our components has, to some extent, been hard--coded into the executable itself.

Of course, 'early binding' is not appropriate for all programming languages. Take any popular scripting language; php for example in which there is no compiler as such and so such 'hard-coding' as can be employed by compilers such as Purebasic is not an option. Early versions of Visual Basic also lacked full compilation and thus were also unable to access COM through early-binding (at least in any sensible manner!) And yet, if you know anything about php, you will know that it can indeed access most COM components in a very simple manner - even simpler than Purebasic or c++ using early-binding (although at a performance cost).

And this leads us nicely to the business of COMate and that of (OLE) automation.
COM automation
(COM) automation grew out of a desire to allow interpreted and scripted languages easy access (and control over) COM components. More specifically it was designed to allow scripted languages to control (or automate) large scale applications (such as Microsoft office) which were built on top of large libraries of COM components. Chief amongst these languages were Microsoft's very own Visual Basic and Visual Basic Script (VBS).

These days COM automation can do much more than simply automate the task of printing MS Word documents for example, as the majority of 'new' COM components now embrace the concept of COM automation and offer up their services to scripted languages which would otherwise be unable to leverage the power of COM.

Recall that interpreted languages cannot easily work with COM because they are generally unable to work with virtual tables and the like. What automation does is remove a lot of the work from the compiler (parameter checking, function lookup etc.) and instead passes this over to the component itself meaning that the majority of method type checking and function lookup is performed at run-time. This of course is perfect for interpreted languages which generally perform all of their type checking at run-time anyhow.

Because of this run-time checking of parameters and function lookup, interfacing with COM components using automation is often called... as mentioned previously; 'late binding'.

So what is it all about then? How do we 'late-bind' to a COM component.

The process starts, as before, by bullying Windows into rummaging through the registry searching for the underlying component file etc. However, at least in the case of the majority of interpreted and scripted languages, we do not identify our sought after component by means of it's GUID (which would kind of defeat the point of having a 'simplified' mechanism for scripted languages to access COM), we instead generally pass it what is termed the component's 'friendly name' (ProgID)! In our math component's case we may well ask Windows to go search for a component named 'math.Component' etc. which is of course a statement seeming to contradict what I said in the above section about naming conflicts! With this mechanism Window's accepts (as must we) that this name may not be unique, although naming conventions are such that a clash is unlikely!

Window's is also instructed not to retrieve one of our math object pointers (previously we described the retrieval of a 'Trigonometric' object), but to retrieve instead an instance of an object which is specifically for use in automation. This is called an iDispatch object and any COM server (component) wishing to allow access via automation must implement such an object. The methods of this object are pretty much unimportant in the context of this discussion, but needless to say that all functionality offered by a COM server to our COM controller (client application) can be accessed through this iDispatch object. This includes calling member functions (methods) which is the main goal of a COM controller after all!

The point is that all a scripted language has to do in order to offer COM support is to understand and be able to use iDispatch objects. Simple, but damn effective! Of course the real beauty of this is that the COM controller doesn't even need to know what objects a particular component exposes before it can use those objects! Contrast this with the early-binding system in which every object a client application wishes to use must be explicitly laid out (in header files etc.) before hand. At no point does an automation controller need to know anything about tables of function pointers etc. and, more importantly, neither does the underlying interpreter or scripting engine! It needs only a good understanding of iDispatch.

Beautiful!

Of course, all of this comes at a price. Driven by the requirement that Visual Basic provides an easy mechanism through which to access COM, those engaged in creating COM components are now, through the shear popularity of VB, having to work twice as hard to support automation. This, for various reasons, often results in components that run slower when using even early-binding through interfaces and virtual tables etc. because of the way automation is often implemented through what are termed dual interfaces. It is also true that with languages like Purebasic and c++, it is far harder to interface with COM components through automation than it is through the more direct methods employed in early-binding. This is because these languages generally do not wrap up the 'unpleasant' details of using iDispatch into a nice convenient package in the same way that, say, Visual Basic does.

For example, consider the following VB code :

Dim excel As Object
Set excel = CreateObject("Excel.Application")
excel.Visible = blnTrue

With this code, VB itself will use automation to access the underlying Excel component, but does it in such a way that the user is totally unaware that VB is actually working with an iDispatch object obtained from the Excel component.

In Purebasic (or c++ etc.) we would be left to work with iDispatch ourselves and this can involve a lot of work. Yes, a whole heap of work!

However, consider the alternative. To reproduce the functionality of the above code using early-binding would require a lot of code, much of which would be buried in header files (interface definitions, GUID's etc.) and all this code actually does is fire up an instance of Excel and make it visible! Add some text to some cells within an Excel sheet and, with early-binding, you are looking at a mountain of code in all! With VB; a couple of extra lines of code!

So, on one hand, automation has the potential to open up the world of COM to the plethora of scripting languages which abound, but, on the other hand, is difficult to utilise if not wrapped up in a convenient manner as is the case with Visual Basic. Certainly languages like Purebasic and c++ do not make using automation anywhere near as simple as using VB.

And this is where COMate makes it's entry and takes a well deserved bow! Not only does it wrap the whole automation business up into a nice Purebasic OOP class, but it does it in a very VB like manner. To see what I mean, consider the following simple code which is a line by line translation of the above VB code to Purebasic using COMate :

ExcelObject.COMateObject
ExcelObject = COMate_CreateObject("Excel.Application")
ExcelObject\SetProperty("Visible = #True")

Very nice if I say so myself!

Very nice indeed!


COMate To COMatePLUS

From COMate to COMatePLUS.

COMatePLUS represents quite a jump forward over earlier versions of COMate, both in terms of it's internal workings and the facilities offered to the developer when working with automation servers. These changes were motivated by a desire to allow much faster access to COM methods/properties than was currently available through COMate's existing command processor.

COMatePLUS can process 'strings' of 'closely related' method/property calls far far faster than earlier versions of COMate, although you do have to specifically make use of the new facilities in order to take advantage of any possible speed increases etc.

Earlier versions of COMate, when given a command string as part of a method/property call, would first undertake the sometimes lengthy process of tokenising and parsing the command string looking for method calls, parameter types, type modifiers ...... etc. and only when this process was complete would it consider executing the COM method etc. When the COM method finished its work, the results of all that tokenising and parsing would be discarded, meaning that if the same method/property needed to be called again (perhaps with modified parameters) then the processing of the command string would need to be undertaken again!

A somewhat wasteful affair don't you think? :)

COMatePLUS takes a different approach, one inspired by SQLite's use of 'prepared statements' etc.

Basically, COMatePLUS allows you to pre-compile a command string right down to a level suitable for passing directly to a COM server. The result of this compilation is what I have termed a COMatePLUS statement which can be executed at any time and as many times (against different COMate objects if required) as required etc. Parameters can be altered between calls very quickly.

The important thing is that the original COMateObject class now uses these pre-compiled statements 'behind the scenes' and so all existing COMate based code should run fine with COMatePLUS with no modifications whatsoever. Of course, all existing code will not run any faster; only code specifically making use of these prepared statements can benefit from these changes.

A couple of newly added demo programs written to showcase COMatePLUS' prepared statements show a 10-fold increase in speed on my system, -something which is not to be sniffed at! :)

The following functions have been added by COMatePLUS for working with prepared statements (see the relevant sections of this manual for details) :

  1. COMate_PrepareStatement()
  2. COMate_GetStatementParameter()
  3. COMate_FreeStatementHandle()

In addition, a new method has been added to the COMateObject class :

GetCOMObject()

which is, in a sense, the reverse of the COMate_WrapCOMObject() function.

All of the COMateObject class methods dealing with calling COM methods/properties have also been modified by the addition of an optional parameter in which we can pass a COMatePLUS statement handle etc. See the COMateObject class section of this manual for details.

Other alterations to the COMate library.

You can now also opt to remove all error reporting from your COMatePLUS application. Simply define the constant #COMATE_NOERRORREPORTING = 1 at the top of your source, before including the main COMatePLUS source file.

In a nutshell, that's it folks! :)


COMateOverview

COMate OVERVIEW.

As discussed previously, COMate is a small source code utility (written in Purebasic) which allows for the easy creation of automation controllers. It is based upon a translation of the DispHelper source code (written in c) but upgraded to offer a true Visual Basic experience in the way that we invoke COM object methods and get/set properties etc. (The idea of a COM property is another Visual Basic simplification to aid readability of source code etc. To all intents and purposes a property is simply a method function used to access some private variable or other.)

Before we delve into COMate proper, perhaps a simple demo program is in order.

The following Purebasic code instantiates an Excel.Application object, adds a new workbook and fills in a few cells :

XIncludeFile "COMatePLUS.pbi"

Define.COMateObject ExcelObject, WorkBook
ExcelObject = COMate_CreateObject("Excel.Application")

If ExcelObject
If ExcelObject\SetProperty("Visible = #True") = #S_OK
WorkBook = ExcelObject\GetObjectProperty("Workbooks\Add")
If WorkBook
ExcelObject\SetProperty("Cells(1,1) = 'Hello'")
ExcelObject\SetProperty("Cells(1,2) = 'from'")
ExcelObject\SetProperty("Cells(1,3) = 'COMate!'")
ExcelObject\Invoke("Quit()")
WorkBook\Release()
EndIf
EndIf
ExcelObject\Release()
Else
MessageRequester("COMate -Excel demo", "Couldn't create the application object!")
EndIf

A rather innocuous looking example!

Those of you who have used the DispHelper library will see the differences straight away! DispHelper uses a Printf() syntax whilst COMate uses a VB type syntax which I, for one, am far more comfortable with.

With COMate you call object methods by passing a single command string, which is designed to mimic a VB like command as far as is possible in a compiled language. Examples of these command strings can be seen in the above code and are reproduced below :

"Visible = #True"
"Workbooks\Add"
"Cells(1,1) = 'Hello'"
"Cells(1,2) = 'from'"
"Cells(1,3) = 'COMate!'"
"Quit()"

With COMate then, working with a COM automation server (component) is a matter of identifying the progID of the component (Excel.Application in the above example) and using the various COMate functions to call methods and get/set properties etc. Communication with the underlying objects (passing parameters etc.) is achieved through VB style command strings which we build up using the Purebasic string commands.

And that is essentially it!

Of course, these VB style command strings do make the task of converting VB COM automation code to Purebasic a snip, and let's be honest, a majority of such example code to be found is written in VB!
COMate FEATURES.

Here we list COMate's most important features and offer up a few words of explanation where appropriate.
  1. An oop interface.
    COMate wraps it's entire operation up within two easy to use classes; one for dealing with the basic COM objects and the other for enumerating collections (more on this later). You can see the main COMate class in action in the code above in which we utilised two instances of the COMateObject class.

    (For those with a detailed knowledge of automation, the underlying iDispatch object pointers are 'embedded' within these COMate objects.)
  2. Method / property parameters are encoded in a very natural way within the command strings.
    Before passing parameters to a COM method/property, COMate will take it's best guess as to the type of each parameter unless the user includes a type modifier in the command string.

    For example, use of the 'As Boolean' modifier in the command string "Visible = 1 As Boolean" will ensure that the value of 1 is converted to the underlying boolean type (#Variant_True in this case).

    'BYREF' is supported for individual parameters for compatibility with many VB ActiveX components.
  3. Method/property calls can embed a chain of property-gets, each of which return a subobject etc.
     This is similar to DispHelper.
  4. Command strings can include escape characters in the form $xxxx where xxxx represents a 4 hexadecimal digit character code.
    E.g. $0027 would insert a ' character into the string.
  5. Enumeration of collections.
    Many COM objects expose collections of objects (or variants) which COMate can retrieve through instances of it's COMateEnumObject class.
  6. ActiveX support.
    Active X controls can be hosted within an application and controlled through the standard COMate classes. There are functions for registering and un-registering OCX libraries.
  7. Support for receiving 'events' sent from COM objects.
    Particularly relevant for ActiveX controls.
  8. Facility for setting 'design-time' read-only properties at run-time for those ActiveX controls which check the ambient 'user mode'.
    Some controls have 'read only' properties which are not intended to be altered at run-time. COMate allows us to set such properties at run-time.
  9. Automatic freeing of all strings (unlike DispHelper).
  10. Completely threadsafe.
    More accurately, COMate is as threadsafe as the underlying components being used!
  11. Full threaded error reporting.
    Access to error codes (HRESULT values) and, where possible, error descriptions in simple text format.

    In particular, if two threads are utilising the same object instance then each will have separate error reporting facilities


COMateUsing

Using COMate in an application (creating an automation controller).

Any application making use of COM automation to control an automation server is known as an automation controller, just in case you were wondering!

COMate is intended (and indeed designed) for use as a source code include file only and to this end, using COMate within your own applications is a simple matter of including the main COMate source file :

XIncludeFile "COMatePLUS.pbi"

That's it; you're ready to go!

Including this source code file within your application exposes the two aforementioned COMate classes to your application, namely the COMateObject class and the COMateEnumObject class. The first of these is the most important of the two classes and the one which allows us to directly control an automation server.

Creating an instance of a COMateObject class involves using one of the following four functions :

  1. COMate_CreateObject()
  2. COMate_GetObject()
  3. COMate_WrapCOMObject()
  4. COMate_CreateActiveXControl()

the first two of which (the most important two) operate in very similar ways to their Visual Basic counterparts!

These functions are the starting point for any COMate inspired automation controller!

Controlling the COMate compilation.

There are two constants which you can define in your source which will alter which parts of the COMate library are compiled.

#COMATE_NOINCLUDEATL

Defining this constant to have a non-zero value before including the main COMate source will result in all code related to ActiveX controls being excluded from the compilation.

I have found this useful in certain applications which do not require ActiveX support because it then removes the worry about whether the ATL library is installed on the target machines. I have found that some machines running Windows NT, for example, do not have this library installed by default!

#COMATE_NOERRORREPORTING (new for COMatePLUS)

Defining this constant to have a non-zero value before including the main COMate source will result in the 'majority' of code related to COMatePLUS error reporting being excluded from the compilation.

This is for those looking to squeeze that extra ounce of speed from the release versions of their applications!

NOTE about datasections and Data statements.

COMate uses a data section within it's main source file and so, if using COMate in source code form, you should use the 'Restore' keyword whenever reading from a data section. Failure to use restore could result in your program reading from the wrong data section.

NOTE about placing COMate within a dll.

COMate is not suitable for placing within a dll for subsequent use by an external program because of certain methods returning strings and dynamically allocated variants. Also, the COMate function COMate_CreateActiveXControl() requires an 'active' gadget-list and so this would need altering if being used within a dll.

Should you wish to place COMate within a dll then please let me know and I will see about making the necessary modifications. I will not make the modifications for the 'general' release version of COMate because I do not wish to change the nature of the offending methods.


Errors

COM ERRORS.

Most methods exposed by a COM object will return what is called a HRESULT value, which is a code used to signify 'success' or 'failure' and varying degrees of severity etc. Consequently, most of COMate's functions and member functions will provide access to these values as well as a text description of the error (where appropriate).

These errors are provided on a thread-by-thread basis in that COMate provides each thread with it's own local storage in which to record these errors. Thus, for example, two threads can be working with the same COM object instance and will have access to their own error returns without impinging upon the other thread etc.

Particular HRESULT values to look out for are :

#S_OK

The operation completed successfully.

#S_FALSE

The operation completed, but was only partially successful.

#E_FAIL

Outright failure.

There are of course many other possible HRESULT values, although the most common can be seen in the COMate source itself.

If your COMate application throws an exception, and if the error retrieval methods built into COMate do not shed any light on the possible cause, simply grab the resulting HRESULT value and search the web. A particularly good site to try is : http://comerrors.com/.
COMate ERROR REPORTING.

The following two COMate functions are used to retrieve the most recent HRESULT code and associated error text (if any) from the most recent invocation of a function or class method which itself deals with HRESULT values:
  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

I think these functions are self-explanatory and need no further explanation except to say that these two functions should perhaps be the first point of call if an automation controller starts to misbehave!

What I will say, however, is that COMate cannot possible offer text descriptions for every possible COM error and so if you come across one such error, I would appreciate you forwarding it to me so that I can add it to COMate etc.


COMateObjectClass

The COMateObject class.

In order for a COMate application to work with an automation server (component) it must first create an instance of a COMateObject class. This class not only exposes simple methods for controlling the automation server, it also wraps and hides all the mundane and somewhat technical details of working with COM automation.

The COMate library offers up four functions for creating an instance of the sought after COMateObject class :

  1. COMate_CreateObject()
  2. COMate_GetObject()
  3. COMate_WrapCOMObject()
  4. COMate_CreateActiveXControl()

which are detailed within the following pages.


COMate_CreateObject

Creating instances of the COMateObject class.

COMate_CreateObject()

This function is similar to VB's CreateObject() function and has the following prototype :

myCOMateObject = COMate_CreateObject(progID.s [, hWnd [, blnInitCom = #True]])

The first thing to say is that, on successful completion, the return value is a pointer to a COMateObject object. With this pointer you can control the underlying COM object by using the COMateObject class methods etc. These methods are detailed later. If the function fails then the return value is zero. A failure would likely be the result of supplying a progID of an unregistered COM component. Check the spelling in these cases.

The parameters of this function are simple enough :

progID

This is the friendly-name (programmatic identifier) of the COM component with whom we wish to 'connect to' or the component's GUID (class identifier) in string form, both of which are stored in the registry.

In the case of the friendly-name, whilst these are not guaranteed to be unique, clashes would be very rare indeed because of the naming conventions employed throughout the world of COM.
From a practical standpoint, obtaining valid progID's is best done by some kind of 'interface generator' program which will scan a computer's registry looking for valid progID's etc.

In the case of a component's GUID, you must supply this in string form surrounded by {} braces. E.g. {8B217746-717D-11CE-AB5B-D41203C10000}. Again you can find these identifiers by using some kind of 'interface generator' program.

hWnd

An optional window / control handle used as a container if interfacing with certain kinds of ActiveX control.

blnInitCom

An optional boolean parameter which you can set to #False if the Windows COM library has already been initialised. Otherwise COMate will initialise the COM environment itself.

Note that you can set hWnd to zero if using the blnInitCom parameter.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMate_GetObject

Creating instances of the COMateObject class.

COMate_GetObject()

This function is similar to VB's GetObject() function and has the following prototype :

myCOMateObject = COMate_GetObject(file.s, progID.s [, blnInitCom = #True])

The first thing to say is that, as with the COMate_CreateObject() function, a successful completion will result in the return of a pointer to a COMateObject object. Failure results in a zero return.

This function serves a couple of purposes, but the main one is to return a new instance of a currently active COM object. For example, imagine your application already holds an instance of a Microsoft Word document object, using COMate_GetObject() you can essentially duplicate this object and this have two separate COMateObject objects reference this same instance of the Word document object etc.

Another use is to hasten the business of connecting to very specific objects within a COM component. Recall that a single component can house many different objects and sometimes you do have to 'drill down' into the component until you can obtain an instance of the sought after object etc. In these cases, the file parameter can often speed this process up.

See the demo program "Demo_retrieve text from Word doc using GetObject.pb" for an example of using this function.

The only parameter requiring an explanation is of course the file parameter :

file

If an empty string, the function attempts to create a new COMate object containing a new instance of a currently active object (the existing COM object's reference count is increased).
Otherwise, COMate will attempt to load a COM object form the specified file (or based upon this file if, for example, it is a Word document file). progID is used in these cases to specify the class of the object in cases where the file contains multiple objects (or leads to a component containing multiple objects).

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMate_WrapCOMObject

Creating instances of the COMateObject class.

COMate_WrapCOMObject()

myCOMateObject = COMate_WrapCOMObject(object.iUnknown)

This function is for convenience really as it takes a COM interface pointer and attempts to wrap the underlying COM object up within a COMateObject object so that you can subsequently control the COM object through the COMateObject class methods etc. This is particularly useful for 'event procedures' attached to COM objects (usually ActiveX controls) in which some parameters may contain COM interface pointers. Wrapping these interface pointers up in a COMateObject object means that you can easily call methods and properties on this object etc.

On successful completion, the return value is a pointer to a COMateObject object. If the function fails then the return value is zero. A failure would likely be the result of the given COM object not implementing an iDispatch interface (very unlikely in the case of an object returned through an ActiveX event procedure parameter!)

Parameters :

object

A pointer to any interface on the COM object which you are attempting to wrap up in the form of a COMateObject object.

The client application will of course be responsible for calling the Release() method of the newly created COMateObject object when finished working on it. This will most likely be in addition to using the Release() method of the original COM interface - depending on how this interface pointer was obtained in the first place.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical notes.

The first thing this function does is call QueryInterface() on the object passed as a parameter. Success depends on the COM object implementing a iDispatch interface.


COMate_CreateActiveXControl

Creating instances of the COMateObject class.

COMate_CreateActiveXControl()

myCOMateObject = COMate_CreateActiveXControl(x, y, width, height, progID.s [, blnInitCom = #True]])

NOTE that there needs to be a 'current' and active Purebasic gadget-list for this function to succeed! This is because the function creates a 'container' to house the new control.

The first thing to say is that, on successful completion, the return value is a pointer to a COMateObject object. With this pointer you can control the underlying ActiveX object by using the COMateObject class methods etc. These methods are detailed later. If the function fails then the return value is zero. A failure would likely be the result of supplying a progID of an unregistered ActiveX component. Check the spelling in these cases.

The parameters of this function are simple enough. We need only discuss the last two :

progID

This is the friendly-name (programmatic identifier) of the COM component with whom we wish to 'connect to' or the component's GUID (class identifier) in string form, both of which are stored in the registry.

In the case of the friendly-name, whilst these are not guaranteed to be unique, clashes would be very rare indeed because of the naming conventions employed throughout the world of COM.
From a practical standpoint, obtaining valid progID's is best done by some kind of 'interface generator' program which will scan a computer's registry looking for valid progID's etc.

In the case of a component's GUID, you must supply this in string form surrounded by {} braces. E.g. {8B217746-717D-11CE-AB5B-D41203C10000}. Again you can find these identifiers by using some kind of 'interface generator' program.

blnInitCom

An optional boolean parameter which you can set to #False if the Windows COM library has already been initialised. Otherwise COMate will initialise the COM environment itself.

If successful, a Purebasic container gadget is created to house the ActiveX control (whose dimensions are specified in the call to the COMate_CreateActiveXControl() function). The Window's handle (or the Purebasic gadget#) of this container can be retrieved with the GetContainerhWnd() method of the COMateObject class, although it is not necessarily a good thing to start poking around with this gadget.

The most obvious reason for this function failing is of course if the underlying ActiveX library file (common extension ocx) has not been registered with the system. For this, COMate offers up the following two functions :

  1. COMate_RegisterCOMServer()
  2. COMate_UnRegisterCOMServer()

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObjectMethods

COMateObject class methods (general methods).

The following pages detail the general methods exposed by the COMateObject class and excludes only those methods for dealing with events sent from COM objects. A later section deals with COM events.

As mentioned in the page on COM errors, most of these methods will make available an appropriate HRESULT value to signify 'success' or 'failure' and varying degrees of severity etc. They will also make available a suitable text description of any errors which occur (within reason!)

Some methods will return a HRESULT value directly whilst, for example, the GetDateProperty() method will return a date and thus cannot, in the case of an error, return an appropriate HRESULT value etc.

Consequently, error codes and error text are made available through the following functions which have already been discussed :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_CreateEnumeration

COMateObject class - CreateEnumeration() method.

myCOMateEnumObject = myCOMateObject\CreateEnumeration(command.s [, hStatement])

Use this method of the COMateObject class to apply an enumeration to the underlying COM object.

Many COM objects internally create 'collections' (VB speak!) of objects in response to a method invocation and COMate can enumerate these collections through an appropriate instance of a COMateEnumObject class. This is a powerful addition to the COM automation toolkit and can simplify a whole heap of tasks.

The return value, if successful, is a fully initialised instance of the COMateEnumObject class which can be used to enumerate the underlying collection as created by the underlying COM object. If the function fails then zero is returned.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical note.

Collections created by a COM object are in the form of variant structures which is the data-type at the heart of COM automation. Indeed, whilst COMate hides much of the work with these structures from the user, it does nevertheless offer the user the option of accessing some of the underlying variants etc. In the case of enumerating collections, the user can choose to enumerate the 'raw' variants as returned by the COM object.


COMateObject_GetContainerhWnd

COMateObject class - GetContainerhWnd() method.

hWnd = myCOMateObject\GetContainerhWnd([returnCtrlID = #False])

Use this method of the COMateObject class in the case that the underling COM object represents an ActiveX control, in order to obtain either the Window's handle of the control's container or the Purebasic gadget#. A zero return will mean that the underlying COM object is not an ActiveX control. (Gadget#'s are allocated through the use of #PB_Any and so will never be zero unless there was an error!)

Parameters :

returnCtrlID

Set to non-zero if you wish the method to return the Purebasic gadget#. This only makes sense if COMate is being used in source code form and not in the form of a dll library.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetCOMObject

COMateObject class - GetCOMObject() method.

object.i = myCOMateObject\GetCOMObject()

New for COMatePLUS.

Use this method of the COMateObject class to retrieve the underlying COM object in the form of its iDispatch object pointer.

This method is intended for use when modifying certain parameters in COMatePLUS prepared statements (see the section on COMatePLUS statements for details) in which, for reasons of speed and efficiency, the developer is given the means to modify directly without recourse to any more COMatePLUS functions than is strictly necessary. Parameters taking iDispatch object pointers may thus require us to first retrieve the iDispatch object pointer lying at the heart of some COMate object or other.

This is, in a sense at least, the opposite of the COMate_WrapCOMObject() function.

NOTE that, following a successful use of this method, you must eventually release the resulting object with the iDispatch Release() method (not the COMateObject class Release() method).

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetDateProperty

COMateObject class - GetDateProperty() method.

date.l = myCOMateObject\GetDateProperty(command.s [, hStatement])

Use this method of the COMateObject class to execute a property against the underlying COM object and have the value returned as a Purebasic date value.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

Like all property-gets, you should be sure that the property you are retrieving is indeed a date, otherwise the value returned is 'undefined' in the sense that there is no predicting what it might represent?

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical note.

A COM object property known to hold a date value will return that value in the form of a variant date which is not the same format as a Purebasic date. COMate thus performs a conversion between these two formats as appropriate.

It is worth pointing out that the COMateObject class method GetStringProperty() can be used to return a nicely formatted date in many cases.


COMateObject_GetIntegerProperty

COMateObject class - GetIntegerProperty() method.

integer.q = myCOMateObject\GetIntegerProperty(command.s [, hStatement])

Use this method of the COMateObject class to execute a property against the underlying COM object and have the value returned as a signed quad. You can of course use any kind of integer variable to receive the return value. Just make sure that you use an appropriate 'size' of integer; byte, word, long etc.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

Like all property-gets, you should be sure that the property you are retrieving is indeed an integer, otherwise the value returned is 'undefined' in the sense that there is no predicting what it might represent?

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetObjectProperty

COMateObject class - GetObjectProperty() method.

object.i = myCOMateObject\GetObjectProperty(command.s [, hStatement [, objectType ]])

Use this method of the COMateObject class to execute a property against the underlying COM object and have the value returned either as a new instance of a COMateObject class or (advanced users only) as an iUnkown interface pointer. Failure will result in a zero return.

Many COM objects expose methods and properties (VB speak!) which themselves return a new instance of a COM object, related to the original COM object of course. For example, an 'Excel.Application' object exposes a 'Workbooks' property which returns an instance of a 'Workbooks' object.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

objectType

This optional parameter, if omitted will package any COM object returned into a new instance of a COMateObject class - which is the ideal way of using this method.

If, on the other hand, this parameter is specified and does not equal #VT_DISPATCH then any COM object returned by this method will be left as an iUnkown interface pointer.

NOTE that, following a successful use of this method, you must eventually release the resulting object either with the COMateObject class Release() method or with the iUnkown Release() method etc.

Like all property-gets, you should be sure that the property you are retrieving is indeed an object, otherwise the value returned could be 'undefined' in the sense that there is no predicting what it might represent?

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetRealProperty

COMateObject class - GetRealProperty() method.

real.d = myCOMateObject\GetRealProperty(command.s [, hStatement])

Use this method of the COMateObject class to execute a property against the underlying COM object and have the value returned as a double value. You can of course use any kind of 'real' variable to receive the return value. Just make sure that you use an appropriate 'size' of real; float or double.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

Like all property-gets, you should be sure that the property you are retrieving is indeed a real, otherwise the value returned is 'undefined' in the sense that there is no predicting what it might represent?

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetStringProperty

COMateObject class - GetStringProperty() method.

string.s = myCOMateObject\GetStringProperty(command.s [, hStatement])

Use this method of the COMateObject class to execute a property against the underlying COM object and have the value returned as a string (in either Ascii or Unicode depending on the compiler settings). There is no need to free the string returned (as there is with DispHelper).

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

Like all property-gets, you should be sure that the property you are retrieving is indeed suitable to be cast as a string. This method is particularly useful for date properties etc.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetVariantProperty

COMateObject class - GetVariantProperty() method.

*ptr.VARIANT = myCOMateObject\GetVariantProperty(command.s [, hStatement])

Use this method of the COMateObject class to execute a property against the underlying COM object and have the value returned as a variant. The function, if successful, allocates memory for and returns a pointer to a variant structure which the client program must, when the variant is no longer required, free with FreeMemory() after first using VariantClear_() if appropriate (which will normally be the case).

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical note.

In COM automation, all returns from methods/properties are packaged up into variant structures anyhow and so the GetVariantProperty() method is simply returning this variant without any attempt to coerce the value returned into a particular format. Contrast this with the other Get...Property() methods which have to undertake this additional coercion!


COMateObject_Invoke

COMateObject class - Invoke() method.

HRESULT = myCOMateObject\Invoke(command.s [, hStatement])

Use this method of the COMateObject class to execute a method against the underlying COM object. This is for COM methods which do not return any kind of value, although it can still be used in instances when you are happy to ignore any value returned from the COM method.

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_Release

COMateObject class - Release() method.
myCOMateObject\Release()
 | Use this method of the COMateObject class to free not only the underlying COMateObject object, but also to release the underlying COM object contained within. A failure to release all objects created will inevitably lead to memory leaks.
 | NOTE that it is a good idea to release objects in the reverse order to that in which they were created. That is, release the most recently created object first. A failure to do this can lead to particular problems if, for example, using COMate to enumerate a collection etc. You've been warned!


COMateObject_SetProperty

COMateObject class - SetProperty() method.

HRESULT = myCOMateObject\SetProperty(command.s [, hStatement])

Use this method of the COMateObject class to set a property value of the underlying COM object.

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_SetPropertyRef

COMateObject class - SetPropertyRef() method.

HRESULT = myCOMateObject\SetPropertyRef(command.s [, hStatement])

Use this method of the COMateObject class to set a property by reference (rather than by value) of the underlying COM object. This is only valid when the property accepts a reference to an object.

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

hStatement

Optional.

A statement handle returned from the COMate_PrepareStatement() function representing a pre-compiled statement.

If used, this parameter takes absolute precedence and the command parameter is ignored.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_SetDesignTimeMode

COMateObject class - SetDesignTimeMode() method.

HRESULT = myCOMateObject\SetDesignTimeMode(state = #True)

Use this method of the COMateObject class to set (or clear) a design-time mode for an ActiveX control. This will essentially fool an ActiveX control into thinking that it is being used in a design time environment and, for some controls at least, will then free up certain properties which would otherwise remain read-only etc. Not all ActiveX controls support this.

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

state

Leave as #True to attempt to set a 'design-time' mode for the control. Set as #False to clear this mode.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObjectEventMethods

COMateObject class methods (event methods).

Some COM objects (particularly ActiveX controls) will fire off events to a client application to signal a change of 'state' or give notification of user actions etc. but only those controllers which are specifically configured to receive these events will do so!

COMate allows client applications to attach individual handler functions (event handlers) to individual events and/or to attach a 'global' handler to receive all events raised by the control (useful for investigating the full range of events being sent to your application).

In addition, many ActiveX controls make available a means for individual event handlers to return a value back to the ActiveX control. The most common method is for the ActiveX control to simply pass a parameter (or two!) 'by reference', thus allowing the client to modify the parameters etc. COMate can handle this through the IsEventParamPassedByRef() method.

A second (less common) mechanism employed by some controls is for the event handler to explicitly return a value direct to the ActiveX control itself. COMate supports this method as well.

(See the SetEventHandler() method for details).

The following pages detail the methods exposed by the COMateObject class for dealing with and handling events sent by a COM object and, as with most COMate functions/methods already detailed in this guide, all of these methods will make available an appropriate HRESULT value to signify 'success' or 'failure' and varying degrees of severity etc. They will also make available a suitable text description of any errors which occur (within reason!)

Some methods will return a HRESULT value directly whilst, for example, the GetStringEventParam() method will return a string and thus cannot, in the case of an error, return an appropriate HRESULT value etc.

Consequently, error codes and error text are made available through the following functions which have already been discussed :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_SetEventHandler

COMateObject class - SetEventHandler() method.

HRESULT = myCOMateObject\SetEventHandler(eventName$, @Callback() [, returnType = #COMate_NoReturn [, IID]])

Use this method of the COMateObject class to either attach an event handler to an individually named event (ActiveX control only), or to attach a 'global' handler to the COMateObject object itself (which will fire for all events raised). This latter case is useful for investigating the full range of events raised by a COM object etc.

Each handler is in the form of a function residing within the client application which is (indirectly) invoked by the underlying COM object whenever it raises the appropriate event.

The return value (from this method) is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

eventName$

The name of the event to which you wish to attach a handler function. Set this parameter to #COMate_CatchAllEvents (or an empty string) in order to set a 'global' handler to receive all events sent by the COM object.

NOTES :

  1. This name is case sensitive, meaning that, for example, "event" and "Event" will be treated as different events (even if the underlying COM object regards them as the same event).
  2. For any named event (other than #COMate_CatchAllEvents or #COMate_OtherReturn), the SetEventHandler() method will fail (HRESULT error code #E_FAIL) if the underlying COM object is not housed within some kind of window/control. That is, the object needs to be a visual component such as an ActiveX control. This is a limitation of COMate rather than a limitation imposed by COM etc.

@callback()

The address of the event handler function inside the client application.

Set this address to zero to remove any existing named handler, or, in the case of #COMate_CatchAllEvents, to remove ALL event handlers in one foul swoop!

returnType

Optional.

Set to one of the following predefined constants to inform COMate of the event handler's return type (if any) :

#COMate_NoReturn (Default)

No return type (or none of any interest!)

#COMate_IntegerReturn

Returns a signed integer.

#COMate_RealReturn

Returns a floating point (double) value.

#COMate_StringReturn

Returns a string.

**#COMate_OtherReturn (or #COMate_VariantReturn or #COMate_UnknownReturn.)

Use this when you are unsure of the return type or (as is more likely) the return type is not covered by the above list.

In this rather special case, COMate will pass a pointer to a variant structure to your event handler into which you can place your return value. You must of course set the variant's \VT field as appropriate to indicate the type of value being returned etc.

NOTE that in the case of a global #COMate_CatchAllEvents event handler, COMate automatically sets the return type to #COMate_OtherReturn (ignoring the user's setting!) if any return type other than #COMate_NoReturn is specified.

IID

Optional. Advanced users only!

COMatePLUS event handlers are based upon an 'out-going' interface which is 'connected' to the underlying COM object through what is called a 'connection point'. Now, many components support different 'out-going' interfaces offering a different range of events which we can tap into and thus we can, if appropriate, direct COMatePLUS to use a specific out-going interface (providing you happen to have all the relevant documentation to hand of course!)

To direct COMatePLUS to use a specific out-going interface, simply place the address of a buffer holding the IID of the relevant interface into this optional parameter.

Otherwise, if you leave this parameter empty, COMatePLUS will search for the first 'useable' connection point and out-going interface as appropriate and this particular interface may of course not offer the particular events which you may be after!

However, having said that, most applications will run fine if simply leaving the choice of connection point etc. to COMatePLUS!

NOTE that the function COMate_GetIIDFromName() can be used to obtain an interface's IID from its' 'friendly name'.

NOTES :

  1. not all COM objects raise events as such and so you need to check first whether the particular object in question does indeed raise events.
  2. This method may fail to establish a connection between the underlying COM object and your event handler even in cases where the object does indeed raise events. Forming this 'connection' relies upon their being certain entries within the component's type-library and, indeed, their being a registered type-library for the component in the first place!
  3. The same event procedure can be used for multiple objects and/or (where appropriate) multiple events.
  4. In the case that you have defined a global #COMate_CatchAllEvents event handler in addition to an individual handler for some event or other, then when this event is raised, the global handler (if any) will be called first.
  5. Note for 'advanced users'.
    A COM object may expose multiple 'connection points' and each connection point may have multiple out-going interfaces implemented. However, COMatePLUS is limited to using a single connection point (either the first suitable one it encounters or one specified. albeit indirectly, by the developer through the optional IID parameter). You cannot utilise two different connection points (or out-going interfaces) at the same time. If you wish to change connection points then first remove all event handlers by using the SetEventHandler() method with methodName$ = #COMate_CatchAllEvents, and @callback() = 0, and then set a new event handler etc.

The form of an event handler function.

The form of an event handler depends primarily upon the return type, with the COMate_OtherReturn type requiring special attention.

We can list the basic forms as follows :

#COMate_NoReturn

Procedure myEventCallback(object.COMateObject, eventName.s, parameterCount)
......
......
......
EndProcedure

#COMate_IntegerReturn

Procedure.q myEventCallback(object.COMateObject, eventName.s, parameterCount)
Protected result.q
......
......
......
ProcedureReturn result
EndProcedure

#COMate_RealReturn

Procedure.d myEventCallback(object.COMateObject, eventName.s, parameterCount)
Protected result.d
......
......
......
ProcedureReturn result
EndProcedure

#COMate_StringReturn

Procedure.s myEventCallback(object.COMateObject, eventName.s, parameterCount)
Protected result$
......
......
......
ProcedureReturn result$
EndProcedure

**#COMate_OtherReturn

Procedure myEventCallback(object.COMateObject, eventName.s, parameterCount, *result.VARIANT)
......
......
......
EndProcedure

In this case you place any return value into the variant structure pointed to by *result.

You must first check that *result is non-zero.

The parameters of the callback are as follows :

object

The COMateObject object whose underlying COM object raised the event. You can of course use any of the COMateObject methods on this object (including those dealing with event parameters).

This parameter also allows the client application to differentiate between different instances of COMateObject objects in the case that the same event handler is being used for more than one object etc.

eventName

A text representation of the event raised by the underlying COM object. Such names are part of the COM object itself and nothing to do with COMate.

parameterCount

The number of parameters sent by the underlying COM object. You will need to see the documentation accompanying the underlying component for a description of each parameter.

The following COMateObject methods can be used to retrieve individual parameters for a given event :

  1. GetIntegerEventParam()
  2. GetObjectEventParam()
  3. GetRealEventParam()
  4. GetStringEventParam()
  5. IsEventParamPassedByRef()

These methods are only to be called from an event handling procedure.

See the following pages for a detailed description of each of the event parameter retrieval methods.

*result

For event handlers dealing with return types of #COMate_OtherReturn only.

In this case you can place your return value into the variant structure pointed to by this value. This value can be null however and so you will need to check for that.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetIntegerEventParam

COMateObject class - GetIntegerEventParam() method.

integer.q = myCOMateObject\GetIntegerEventParam(index)

Use this method of the COMateObject class only from an event handling procedure to retrieve the specified parameter from the event currently being processed and have the value returned as a signed quad. You can of course use any kind of integer variable to receive the return value. Just make sure that you use an appropriate 'size' of integer; byte, word, long etc.

Parameters :

index

A one-based index specifying which parameter to retrieve.

Like all property-gets, you should be sure that the parameter you are retrieving is indeed an integer, otherwise the value returned is 'undefined' in the sense that there is no predicting what it might represent?

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetObjectEventParam

COMateObject class - GetObjectEventParam() method.

object.i = myCOMateObject\GetObjectEventParam(index [, objectType]))

Use this method of the COMateObject class only from an event handling procedure to retrieve the specified parameter from the event currently being processed and have the value returned either as an iDispatch interface pointer or as an iUnkown interface pointer. Failure will result in a zero return.

Parameters :

index

A one-based index specifying which parameter to retrieve.

objectType

This optional parameter, if omitted, will result in COMate seeking an iDispatch interface pointer. Otherwise, if this parameter is specified and does not equal #VT_DISPATCH, then any COM object returned by this method will be left as an iUnkown interface pointer.

NOTE that, following a successful use of this method, you must eventually release the resulting object with the iDispatch / iUnkown Release() method etc. Note also, that you can attempt to wrap the object returned from this method in the form of a COMateObject object by using the COMate_WrapCOMObject() function, thus allowing for easy access to the underlying COM methods etc. (However, you will still need to use the Release() method on the object returned and also on the new COMateObject object as well!)

Like all property-gets, you should be sure that the parameter you are retrieving is indeed an object, otherwise the value returned could be 'undefined' in the sense that there is no predicting what it might represent?

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetRealEventParam

COMateObject class - GetRealEventParam() method.

real.d = myCOMateObject\GetRealEventParam(index)

Use this method of the COMateObject class only from an event handling procedure to retrieve the specified parameter from the event currently being processed and have the value returned as a double value. You can of course use any kind of floating point variable to receive the return value. Just make sure that you use an appropriate 'size' of real; float or double.

Parameters :

index

A one-based index specifying which parameter to retrieve.

Like all property-gets, you should be sure that the parameter you are retrieving is indeed a real (decimal) value, otherwise the value returned is 'undefined' in the sense that there is no predicting what it might represent?

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetStringEventParam

COMateObject class - GetStringEventParam() method.

string.s = myCOMateObject\GetStringEventParam(index)

Use this method of the COMateObject class only from an event handling procedure to retrieve the specified parameter from the event currently being processed and have the value returned as a string (in either Ascii or Unicode depending on the compiler settings). There is no need to free the string returned (as there is with DispHelper).

Parameters :

index

A one-based index specifying which parameter to retrieve.

Like all property-gets, you should be sure that the parameter you are retrieving is indeed suitable to be cast as a string.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateObject_GetIntegerEventParam

COMateObject class - IsEventParamPassedByRef() method.

integer.q = myCOMateObject\IsEventParamPassedByRef(index [, @Parameter , [@Variant]])

This method is for advanced users only.

Use this method of the COMateObject class only from an event handling procedure to not only determine if the specified parameter passed by the ActiveX control was passed by reference or not, but to also (optionally) obtain the address of the said parameter (if passed by reference) so that you can alter that value as appropriate. This is how many ActiveX controls receive values back from event handlers etc.

Parameters :

index

A one-based index specifying which parameter to query.

Parameter

Optional.

Pass the address of a variable able to hold an integer value if you wish to obtain the address of the parameter in the case that it was passed 'by reference' by the ActiveX control.

Variant

Optional.

Pass the address of a variable able to hold an integer value if you wish to obtain a pointer to the actual variant holding the parameter in question as supplied by the ActiveX control to COMate itself.
This is offered in case some ActiveX control passes parameters to event handlers whose underlying 'types' are not natively handled by COMate, parameters such as SafeArrays etc.

Take care if modifying the fields of this variant and DO NOT free this variant!

A return of zero from this function means that the parameter in question was not passed by reference.

Otherwise, the return value is a variant #VT... type constant (e.g. #VT_BSTR for an OLE string, #VT_I4 for a 4 byte signed integer etc.)

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical notes.

  • The #VT_... return value has had the #VT_BYREF modifier removed; just for convenience.
  • If modifying a 'ByRef' parameter by making use of the address placed in the Parameter parameter, then you should take steps to free (where appropriate) any existing values. For example, if the parameter is of type #VT_BSTR (an OLE string) then you should first use SysFreeString_() before replacing the contents etc.


COMateEnumObjectClass

The COMateEnumObject class.
 | Many COM objects internally create 'collections' (VB speak!) of objects in response to a method invocation and COMate can enumerate these collections through an appropriate instance of a COMateEnumObject class. Creating such an instance is achieved through the CreateEnumeration() method of the appropriate COMateObject object and, armed with this new object, the user can step through the collection and examine each 'item' at will! The methods exposed by COMateEnumObject objects are detailed within the following pages.


COMateEnumObject_GetNextObject

COMateEnumObject class - GetNextObject() method.

nextCOMateObject = myCOMateEnumObject\GetNextObject()

Use this method of the COMateEnumObject class to return the next item in the underlying 'collection' in the form of a newly initialised COMateObject object. This method returns zero in the case of failure or having reached the end of the enumeration.

NOTE that, following a successful use of this method, you must eventually release the resulting COMateObject object by using the COMateObject class Release() method. It is advisable to do this before retrieving the next item in the enumeration etc.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMateEnumObject_GetNextVariant

COMateEnumObject class - GetNextVariant() method.

*ptr.VARIANT = myCOMateEnumObject\GetNextVariant()

Use this method of the COMateEnumObject class to return the next item in the underlying 'collection' in the form of a pointer to a newly initialised variant structure. This method returns zero in the case of failure or having reached the end of the enumeration.

NOTE that, following a successful use of this method, you must eventually free the variant by using FreeMemory() after first using VariantClear_(). It is advisable to do this before retrieving the next item in the enumeration etc.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical note.

In COM automation, all returns from methods/properties are packaged up into variant structures anyhow and so the GetNextVariant() method is simply returning this variant without any attempt to coerce the value returned into a particular format. Contrast this with the GetNextObject() method which has to extract the new object from the variant and create a new instance of the COMateObject class etc.


COMateEnumObject_Release

COMateEnumObject class - Release() method.
myCOMateEnumObject\Release()
 | Use this method of the COMateEnumObject class to free not only the underlying COMateEnumObject object, but also to release the underlying COM object (a IEnumVARIANT object, in case you were wondering!) contained within. A failure to release all objects created will inevitably lead to memory leaks.


COMateEnumObject_Reset

COMateEnumObject class - Reset() method.

HRESULT = myCOMateEnumObject\Reset()

Use this method of the COMateEnumObject class to reset the underlying enumeration back to the beginning (before the first item).

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical note.

There is no guarantee that a second run through the underlying collection will produce the same results. Imagine a collection of files in a folder for example in which some can be deleted between enumerations etc.


COMateCommandStringsAndPreparedStatements

COMatePLUS command strings and prepared statements.

When a COMatePLUS application invokes a COM method or property (or a series of property-gets etc), the application must detail the name(s) of the method/properties in question and make arrangements to pass the necessary parameters etc. This is of course an inherently complex process, and one to which COMatePLUS has gone to great lengths to simplify.

COMatePLUS offers two means for communicating method names and parameter values; namely command strings and prepared statements.

A command string represents the simplest way of invoking a COM method/property and indeed was the only way of proceeding with earlier versions of COMate. Such a string requires some detailed processing by COMatePLUS before the appropriate COM method(s) can be called etc. and so this is a relatively slow process and, some may say, inefficient means of communicating with a COM server. Command strings do, however, make for some very readable code and assist, in my opinion, with an understanding of the basic nature of COM automation etc.

A prepared statement, on the other hand (new for COMatePLUS), allows the developer to invoke COM methods in a much more efficient way providing the underlying application is specifically geared towards this end. Prepared statements are intended for those situations in which an application intends calling upon a COM method/property repeatedly in some kind of loop, altering the method parameters between invocations etc. In these cases, using a prepared statement can lead to very very significant speed increases.

The point is that, with a prepared statement, calling the same method/property say a thousand times in a loop (modifying parameters as appropriate) requires that the underlying command string be processed just once only. With earlier versions of COMate, this same command string would require processing one thousand times! In addition, by using the 'BYREF' type-modifier, you can arrange for prepared statements to effectively use 'bound parameters' which is a very useful facility indeed (for those COM components which recognise such parameters!)

In a way, you can liken the differences between using command strings and prepared statements to that of using a language interpreter (programming) or using a compiler!

Command strings and prepared statements are detailed further within the following pages. This includes the functions added to COMatePLUS to deal with the new prepared statements.

The reader is advised to strongly study the section on command strings before proceeding to prepared statements.


COMateCommandStrings

COMate command strings.

As you will no doubt have noticed by now, COMatePLUS applications communicate with a COM object primarily through what I have termed a command string. For example, when invoking a COM method (through the Invoke() method of the underlying COMateObject object) we can opt to pass a command string detailing the name of the COM method(s) we wish to call, it's parameters (and, where appropriate, the parameter types). When setting COM property values, we pass a command string containing the property name, it's parameters and the new property value etc.

These command strings then are quickly seen to be absolutely crucial to controlling an Automation server. Indeed, such is their importance that there is actually a lot more to them than may initially be apparent!

A couple of simple examples should provide the reader with a 'feel' for these command strings.

  1. The following Purebasic snippet sets a COM property via a command string :
    ExcelObject\SetProperty("Cells(1,1) = 'Hello'")

    The command sting in this case is : "Cells(1,1) = 'Hello'"

    Notice how, for convenience, we surround a string literal with single quotes (double quotes would require our Purebasic programs to utilise string concatenation etc.)

  2. The next snippet uses a predefined constant value of #True :
    ExcelObject\SetProperty("Visible = #True")

    The command sting in this case is : "Visible = #True"

    Note that this predefined constant does not equate to Purebasic's #True constant. It actually equates to the COM equivalent of a #True value (#Variant_TRUE).

  3. The next snippet uses a 'type modifier' to inform COMatePLUS that a certain parameter is of a certain type (a date in this case). Left to it's own devices, COMatePLUS will take its 'best guess' as to a parameter's type when confronted with a parameter without a type modifier. In the case of a date it will generally fail miserably!
    ExcelObject\SetProperty("Cells(1,1) = '03/09/2008' AS DATE")

    The command sting in this case is : "Cells(1,1) = '03/09/2008' AS DATE"

    Without the 'AS DATE' (case is not important) modifier, COMatePLUS would regard this parameter as a simple string literal and then you would be firmly at the mercy of the underlying COM object which may or may not be able to recognise that this string is indeed a date. Even if it does, it may misinterpret the values and, for example, switch the days for months!

  4. Our final snippet in this crash course in COMatePLUS command strings retrieves a property in which the command string used first retrieves a temporary (or intermediate) COM object and then calls a property on this new object before deleting the temporary COM object. All of this is done 'behind the scenes' by COMatePLUS.
    Workbook = ExcelObject\GetObjectProperty("Workbooks\Add")

    The command sting in this case is : "Workbooks\Add"

    Here, a 'Workbooks' object is retrieved by COMatePLUS which then applies the 'Add' method to this object. Whatever is returned is then passed back to the client application and all without the client having to worry about these 'intermediate' objects etc.

    COMatePLUS command strings can 'nest' any number of such intermediate objects when making calls to methods or properties.

With the snippets of code shown above, together with the various demo programs which come with the COMatePLUS package, it is envisaged that the experienced Purebasic user can probably now jump straight in and start using COMatePLUS quite effectively. Indeed, the more complex command strings can be constructed using Purebasic's string commands. For example, passing a numeric variable as a parameter requires the use of one of the Str() functions etc. but it is all something which the user will soon get used to.

However, this is not to say that we have probed every nook-and-cranny of COMatePLUS command strings, because we have not! No sir! It must be appreciated that these command strings are backed up by a potent little 'parser' and as such offer a lot of error checking whilst insisting that the client application adheres to the correct syntax etc.

In the following section, therefore, we take a more formal approach to these command strings and discuss the permissible syntax, the parameter options, type-modifiers, predefined constants, escape sequences, optional parameters...

COMatePLUS command string syntax and features.

A general command string takes the following form :

"object1[(param1 [AS type-modifier] [BYREF] [, param 2 [AS type-modifier] [BYREF]]...)][\object2[(......)][\......] [ = result-param [AS type-modifier] [BYREF]]"

Phew!

And this is without using any escape sequences within string literals!

Essentially then a command string comprises of a sequence of 'intermediate objects' finished with a method / property call etc. and then an optional assignment (in the case of setting a property). Each of these intermediate objects takes the following form :

object[(param1 [AS type-modifier] [BYREF] [, param 2 [AS type-modifier] [BYREF]] [, param 3 [AS type-modifier] [BYREF]]......)]

For example (completely fictitious) : Draw('Ellipse', 20, 30, 6, 8) or, (using variables taken from the client Purebasic program) : Draw('Ellipse', 20, 30, 6, " + Str(radY) + ")" etc.

Command string method/property parameters.
When parsing a command string, COMatePLUS will take each method/property parameter in turn and convert it into a form suitable for passing to an automation server (COM component).

COMatePLUS first checks for certain 'predefined' values which we list here : (case is not important)

#nullstring (an empty string)

#False or #True (boolean values)

#Empty or #Optional or #Opt (an optional parameter which some COM objects demand are signalled in this way so that they can provide certain default values)

#void a null pointer.

With these predefined values, COMatePLUS substitutes the appropriate COM value.

After this, if a given parameter doesn't match one of the predefined values then COMatePLUS takes it's best guess as to the parameter type using the following rules :

  1. If the parameter is surrounded by single quotes then the parameter is packaged up as a string (a COM string takes the form of a BSTR).
  2. If the parameter is not a quoted string, then COMatePLUS checks if it is a decimal number and if so packages it up in the form of a 'double' value.
  3. If the parameter is not a quoted string or a decimal number, then COMatePLUS checks if it is an integer. If so, it packages the parameter up in the form of either a signed byte, or a signed word, or a signed long or a signed quad depending on which type will accommodate the value.

After this, having already packaged the given parameter into a type suitable for a COM automation server, COMatePLUS then looks for the presence of a type-modifier (as demonstrated in the example snippets of code above). Available modifiers are as follows : (case is not important)

  • boolean
  • string or bstr
  • byte (a signed byte)
  • ubyte (an unsigned byte)
  • word (a signed word)
  • uword (an unsigned byte)
  • long or dword (a signed long)
  • ulong or udword (an unsigned long)
  • quad or qword (a signed quad)
  • uquad or uqword (an unsigned quad)
  • integer or int (a signed integer; 4 bytes or 8 bytes depending on the platform)
  • uinteger or uint (an unsigned integer)
  • date
  • object or iDispatch (an iDispatch object pointer - advanced users only!)
  • COMateObject (a COMate object pointer - advanced users only!)
  • iUnknown (an iUnknown object pointer - advanced users only!)
  • float or single (a 4 byte floating point value)
  • double (a 8 byte floating point value)
  • variant (a 16 byte structure - advanced users only)

Finally, COMatePLUS looks for the presence of a BYREF modifier (case not important) which, if present, will result in COMatePLUS packaging the parameter up (if possible - see the notes below) as an explicit pointer. Clearly the parameter being passed will need to be a memory address of some variable or other (and COMatePLUS checks that an integer is passed which of course is suitable for a memory address). NOTE that some components will work with an address without requiring the BYREF modifier to be specified. Others, however, require this modifier. In particular, components created with Visual Basic will almost certainly require the COMatePLUS BYREF modifier when passing addresses of variables to methods created with the VB 'ByRef' modifier.

There are some things to bear in mind with these modifiers :

  1. When using the BYREF modifier, you must supply COMatePLUS with an address of a variable - fullstop!
    This means that, for example, you cannot pass a string literal by reference! To do this the client program must pass the address of a variable which points to a BSTR (Unicode) representation of the string itself and also take charge of freeing this BSTR when finished etc.
    An inconvenience perhaps, but then I have to draw the line somewhere and, besides, strings are effectively passed by reference anyhow and so use of BYREF with strings is a somewhat pointless exercise! Any COM component requiring such a parameter is likely to replace your given BSTR with one of it's own and so COMatePLUS couldn't possibly provide the required pointer anyhow!
    Passing variants by reference also requires a similar step.
  2. Object parameters (advanced users) have their reference counts automatically increased and decreased whilst COMatePLUS is using them. This does NOT apply to objects passed with the BYREF modifier.
    The point here is that if passing an object (iDispatch or iUnknown) by reference, then we must rely upon the COM method being called to handle the reference counting to ensure that the object is not inadvertently destroyed if some thread is monkeying around with that same object! Not a problem if the client application itself keeps it's own references in check! :wink
  3. Variant parameters (if not passed BYREF) are copied by COMatePLUS (a 'deep' copy).
    The point here is that passing variants without the BYREF modifier can be a slow process if the variant in question, for example, contains an array of variants each containing... You get the idea! Pass variants by-value only when strictly necessary!
  4. Do NOT attempt to pass a COMateObject by reference!
    Only COMatePLUS itself understands the nature of a COMate object and so passing such a reference to a COM method would be pointless. Instead you will need to acquire the underlying COM interface stored within the COMateObject and pass a reference to this. You can obtain this interface pointer by using the GetCOMObject() method of the COMateObject class (new for COMatePLUS).

The only thing remanining now are escape sequences for string parameters.

With a quoted string parameter, you can embed any character by using an 'escape sequence'. This is useful, for example, to embed single quotes within a string literal as these characters cannot otherwise be entered directly because they are part of the command string itself.

An escape sequence takes the form $xxxx where xxxx are 4 hexadecimal digits which together yield the relevant character code (Ascii or Unicode depending on the compiler options etc.)

For example, '$0027' would yield a single quote. An example of using an escape sequence is taken from the Excel demo program :

ExcelObject\SetProperty("Cells(1,5) = 'Today$0027s date is'")

This will be passed to the underlying COM object as "Today's date is".

NOTE that an escape sequence must consist of exactly 4 hexadecimal digits.


COMatePreparedStatements

COMate prepared statements (new for COMatePLUS).

As explained elsewhere in this manual, a COMatePLUS prepared statement is a pre-compiled command string ready to be passed directly to a COM automation server without any additional processing by COMatePLUS. Such a statement can be utilised over and over as many times as may be required.

Embedded within such a compiled statement are the parameters of any COM methods/properties which are to be invoked whenever the statement is to be executed and in a form ready for the COM server to receive directly. COMatePLUS allows quick access to these 'compiled' parameters so that they can quickly be modified between calls etc. Alternatively, if you construct your statement from a command string utilising 'BYREF' parameters (whereby you pass the address of a variable and use the 'BYREF' type-modifier) then these parameters will effectively be bound to the aforementioned variables. Change the value of one of these variables and you have effectively altered the associated statement parameter without any effort whatsoever!

Functions for dealing with prepared statements.

There are three free-standing functions for working with COMatePLUS prepared statements. One for creating a prepared statement, one for freeing a resulting statement handle and one for working directly with statement parameters.

The functions are listed here but detailed within the following pages of this manual. Please pay particular attention to the COMate_GetStatementParameter() function as this one is easily abused! I am thinking mainly of you KCC - are you reading this? :)

A prepared statement is obtained through the function : COMate_PrepareStatement().

This function takes a COMatePLUS command string and, if successful, returns a statement handle used to identify the compiled version of the command string. Use this handle in any of the COMateObject class methods dealing with invoking COM methods/properties in place of a command string to have this compiled statement executed in an appropriate manner.

Use the function : COMate_FreeStatementHandle() to free all resources associated with a compiled statement when you are done with it.

Use the function : COMate_GetStatementParameter() to retrieve the address of a specified COM parameter (in VARIANT form) which was a part of the original command string.

This function need be called once only for each parameter you wish to modify at some stage as each parameter address will not change during the lifetime of the underlying statement.

This function requires some care and so the reader is advised to study the appropriate page of this manual very carefully. Mis-using this function can easily result in memory leaks and worse!


COMate_PrepareStatement

Creating a COMatePLUS prepared statement.

COMate_PrepareStatement()

*hStatement = COMate_PrepareStatement(command.s)

If successful, the return value is a statement handle used to identify the compiled statement. Returns zero otherwise.

Parameters :

command

A command string built using Purebasic's string library. This string is designed to offer a Visual Basic like syntax for conversing with the underlying COM component.

See the section on command strings for a detailed discussion of this parameter. Also, the various demo programs nicely illustrate these strings in action!

The returned statement handle (if non-zero) can be used with the following methods of the COMateObject class :

  • Invoke()
  • CreateEnumeration()
  • GetDateProperty()
  • GetIntegerProperty()
  • GetObjectProperty()
  • GetRealProperty()
  • GetStringProperty()
  • GetVariantProperty()
  • SetProperty()
  • SetPropertyRef()

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMate_FreeStatementHandle

Freeing a COMatePLUS prepared statement.

COMate_FreeStatementHandle()

COMate_FreeStatementHandle(*hStatement)

Frees all resources associated with the underlying COMatePLUS prepared statement.

Parameters :

*hStatement

A statement handle returned from the COMate_PrepareStatement() function.

The developer is of course responsible for freeing all such statement handles. COMatePLUS undertakes no automatic 'garbage collection' of prepared statements.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMate_GetStatementParameter

Modifying a COMatePLUS prepared statement parameter.

COMate_GetStatementParameter()

*parameter.VARIANT = COMate_GetStatementParameter(*hStatement, index)

If successful, the return value is a direct pointer to the variant structure housing the specified parameter. Returns zero otherwise.

Parameters :

*hStatement

A statement handle returned from the COMate_PrepareStatement() function.

index

A one-based index into the list of parameters passed to the original command string at the time when the statement was being compiled.

An example of using this function.

Using this function comes with multiple government health warnings; it can seriously stunt your growth and lead to fertility complications in later life! :)

However, our first task it to take a close look at the index parameter. We leave the potential hazards to the sections below.

Imagine you created a statement through the following use of the COMate_PrepareStatement() function (purely fictitious example) :

hStatement = COMate_PrepareStatement("Box(3)\Color('frontColor') = 6")

Then, in this case, the resulting compiled statement utilises 3 parameters with indexes 1, 2 and 3 respectively.

The parameter corresponding to index 1 has an initial value of 3.

The parameter corresponding to index 2 has an initial value of 'frontColor'.

The parameter corresponding to index 3 has an initial value of 6.

The point being that even assignment values count as parameters!

The following command will retrieve the address of the variant structure being used to house the parameter with index 2 (the string parameter) :

*param.VARIANT = COMate_GetStatementParameter(hStatement, 2)

Simple!

COMMENT on why COMatePLUS uses this method for altering parameters?

First I must point out that once the address of a certain parameter has been retrieved, it can be used as many times as required whilst modifying the underlying parameter etc. The point being that this address is fixed and will not change, meaning that you only need retrieve the address once. Imagine a loop in which the prepared statement is being executed a thousand times, but each time you modify the value of one of the parameters. Rather than retrieve the address of the relevant parameter a thousand times, you need only retrieve it once, but make use of this address a thousand times! Much more efficient than any alternative!

Indeed, whilst this method of altering compiled statement parameters may at first seem a little awkward (having to deal directly with variant structures), it comes after much thought and consideration on my part and is, I think, the most sensible option considering the circumstances.

Consider first that COM automation requires ALL parameters passed to methods/properties to be packaged up into variant structures. No 'ifs' or 'buts' or 'bloody hell's bells' it is simply the way of things where COM automation is concerned. COMatePLUS (and the original COMate) expends a lot of energy packaging your command string parameters up into variant structures.

Second, remember that the whole purpose of introducing COMatePLUS prepared statements is to make the process of repeatedly calling the same COM method/property as efficient (and as fast) as possible. Offering some nicely packaged functions for altering method/property parameters would slow things down whilst COMatePLUS retrieved the appropriate parameter address each time a modification was sought. It all adds up if calling the same COM method/property thousands of times!

No, in terms of squeezing the last ounce of juice from our prepared statements, I was essentially led into forcing developers into dealing directly with the underlying variant structures, something which requires a little care and consideration. (You listening KCC ? :) )

How to use this function 'safely' !

Modifying a prepared statement parameter is obviously a simple case of retrieving the address of the appropriate variant structure and modifying the relevant fields of this structure as appropriate.

Except that working with variants in this manner does require some care and an awareness that a 'cavalier' approach to variants will invariably lead to memory leaks and/or crashes!

For those new to variant structures; the first thing to say is that they are nothing more than a 16-byte structure able to hold many different forms of data (integers, decimals, strings, objects, arrays, other variants...)

It is all very simple really, -just a bit fiddly at times! Providing you follow a few 'rules', then memory leaks will be avoided.

The first point of note is the first field of the variant structure, the one which informs us what kind of data is contained within the variant. This field is a 16-bit (Purebasic word) value with a field name of 'vt'.
So, if we have a pointer to a variant structure, say *var, then the value of *var\vt is a 16-bit constant which informs us what kind of value the variant holds. Some possible values of note are :

#VT_I1 (signed 8-bit integer)
#VT_I2 (signed 16-bit integer)
#VT_I4 (signed 32-bit integer)
#VT_BSTR (a pointer to a BSTR, a COM string in Unicode format)
#VT_BOOLEAN (a boolean value holding a true or false value)
#VT_R4 (32-bit decimal value)
#VT_R8 (64-bit decimal value)
#VT_DISPATCH (an iDispatch object pointer)
#VT_UNKNOWN (an iUnknown object pointer)
...
and so on.

A complete list of vt constants can be seen here.

From a COMatePLUS prepared statement vantage, however, this variant type field is of limited interest because, in the ordinary run of things, it is very unlikely (or at least not very desirable) that we would ever need to alter the type of a parameter once it has been set through our original command string. There are exceptions to this which are outlined in the numbered list below.

So, rather than embark upon a tutorial on using variants (which is somewhat outside of the scope of this manual) let us instead just focus upon the most important issues. Those nuggets of wisdom gleamed from many a proverbial cock-up resulting from my own cavalier approach to variants and COM in general! :) An approach I do not recommend!

The following does assume some familiarity with variants, or at least that the reader is prepared to trawl the web looking up any definitions which the following throws up and which he/she does not recognise!

Let us begin with a quick no-nonsense example.

The following simple code compiles a statement (the same one we used above!) and sets about showing how to modify the first parameter in the resulting statement so that it holds a value of 4 :

hStatement = COMate_PrepareStatement("Box(3)\Color('frontColor') = 6")
If hStatement
;Retrieve the address of the first parameter.
*param1.VARIANT = COMate_GetStatementParameter(hStatement, 1)
If *param1
;Change the value to 4.
*param1\lVal = 4
EndIf
EndIf

Of course such modifications would generally reside within a loop etc. but the code serves to illustrate the basic technique for modifying parameters.

Note that the code uses another field of the variant structure, namely 'lVal'. This field is for use for variants holding a 32-bit signed integer (a Purebasic 'long' data-type). Search the web for a discussion on variant fields etc.

So, no problems thus far then. Modifying statement parameters looks easy enough; retrieve the appropriate address and modify the appropriate variant as required!

The problems arise with those data-types which require additional memory and which, consequently, may require us to free up existing memory before modifying a parameter etc. This affects variants holding, in particular, strings and there is a similar problem for variants holding object pointers in that we have to undertake a little extra processing before we can modify the underlying parameter.

At this stage I will abdicate all responsibility for tutoring the reader in the finer points of working with variants (a cop-out basically!) and instead just list some of the more important things to note. At the end of the day, if you are unsure about how to modify a certain type of statement parameter, then just post a question or two in the COMatePLUS thread over at the Purebasic forums. There are enough COM experts floating around those parts who can answer your questions.

Important points to observe.

  1. If modifying a statement parameter which holds a BSTR (string) then use the VariantClear_() api function first to ensure that the existing BSTR is correctly freed (unless of course you manually placed the BSTR within the parameter and require that same string elsewhere).

    Take care though because the VariantClear_() function will clear the variant's vt field which you will then have to reset etc.

    You can free the BSTR manually using the SysFreeString_() api function without impacting upon the variant's vt field.

  2. If modifying a statement parameter which holds a COM object of some description then use the VariantClear_() api function first in order for the aforementioned object to have its Release() method called.
    The exception to this rule is if you placed this object pointer within the underlying variant yourself and did not call the AddRef() method manually (tut-tut, naughty naughty!) In this case, do NOT call VariantClear_().

    Any objects passed 'by reference' (with the #VT_BYREF switch) will not have their Release() methods called by VariantClear_() and so, if appropriate, you will have to invoke this method yourself (if you previously invoked the AddRef() method of course!)

    NOTE that when modifying a parameter to hold some object pointer, COMatePLUS will not call AddRef() for you as it (for reasons already outlined) has no input in the parameter modification process.

    Take care with the VariantClear_() function, as this will clear the variant's vt field which you will then have to reset etc.

  3. Do NOT attempt to modify a statement parameter by placing a COMateObject pointer into the appropriate variant structure etc. No COM server will understand the nature of a COMateObject object and will most likely crash!

    Instead, use the GetCOMObject() method of the COMateObject class and then place the resulting iDispatch object pointer into the variant. NOTE that the GetCOMObject() method automatically calls AddRef() and so you will eventually need a corresponding Release() etc.

  4. A note on parameter types and variant types.
    As stated above, it is not desirable that we alter the variant type of a statement parameter.

    However, there is a potentially huge problem here if we are not careful!

    Consider the following version of the above code which attempts to alter a statement parameter value from 3 to 300 :
    hStatement = COMate_PrepareStatement("Box(3)\Color('frontColor') = 6")
    If hStatement
    ;Retrieve the address of the first parameter.
    *param1.VARIANT = COMate_GetStatementParameter(hStatement, 1)
    If *param1
    ;Change the value to 4.
    *param1\lVal = 300
    EndIf
    EndIf

    Would you be surprised to learn that this code fails? It will not crash, but it will not function as intended!

    If you are not surprised, and if you know the reason why the code fails to perform before reading further, then you most definitely qualify for your own personal entry within the Purebasic book of greatness alongside Fred, Freak, Rings, Bericko, srod, ...... :) :)

    The reason it fails is because the variant structure retrieved by the COMate_GetStatementParameter() function has had it's vt field set to #VT_I1 (8-bit integer) by COMatePLUS' command processor! Consequently, a value of 300 will effectively be truncated to its first 8 bits etc.

    Ouch!

    This all happened when you created the statement through the COMate_PrepareStatement() function. COMatePLUS' command processor will, when it identifies an integer parameter, use the smallest integer data-type which can accommodate the value! In this case the original value of 3 will fit into a single byte etc.

    There are several possible workarounds here. You could set the variant type of the statement parameter to #VT_I4 say, or, a better solution is to use an initial value which will force a 32-bit integer data-type through something like :

    hStatement = COMate_PrepareStatement("Box(100000)\Color('frontColor') = 6")

    The best way, however, is to be clear up front and use a COMatePLUS type-modifier as with the following :

    hStatement = COMate_PrepareStatement("Box(3 as LONG)\Color('frontColor') = 6")

    etc. Yes, much better this way!

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


COMate_RegisterCOMServer

Registering a COM server.

COMate_RegisterCOMServer()

HRESULT = COMate_RegisterCOMServer(dllName.s [, blnInitCom = #True]])

In instances when a particular COM component (particularly an ActiveX control) has not been registered on the target computer, use this function to perform the task of registering the component on the host machine.

A good practice would be to try and create an instance of the component first and, if this fails, then use the COMate_RegisterCOMServer() function to register the component before trying once again to create the object etc. Don't forget to un-register the component at program's end if your program had to register the server itself (use COMate_UnRegisterCOMServer() for this)

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

dllName

The name of the component file containing the component(s) to be registered. Include the full path if the filename is anywhere but the application folder etc.
Note that the file does not have to include the .dll file extension. Most ActiveX library files, for example, will have an extension of .ocx.

blnInitCom

An optional boolean parameter which you can set to #False if the Windows COM library has already been initialised. Otherwise COMate will initialise the COM environment itself.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical note.

A COM component is responsible for registering itself on the host machine by exporting a function called "DllRegisterServer" within it's component file. All the COMate_RegisterCOMServer() function does is call this exported function. It does, however, require that the program is running with administration rights.


COMate_UnRegisterCOMServer

Un-registering a COM server.

COMate_UnRegisterCOMServer()

HRESULT = COMate_UnRegisterCOMServer(dllName.s [, blnInitCom = #True]])

In instances when your client application had to register a particular COM component through the COMate_RegisterCOMServer() function, use the function detailed on this page to un-register the same component at program's end. This will ensure that all registry entries placed on the host machine by the registration process are removed etc.

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

dllName

The name of the component file containing the component(s) to be un-registered. Include the full path if the filename is anywhere but the application folder etc.
Note that the file does not have to include the .dll file extension. Most ActiveX library files, for example, will have an extension of .ocx.

blnInitCom

An optional boolean parameter which you can set to #False if the Windows COM library has already been initialised. Otherwise COMate will initialise the COM environment itself.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()

Technical note.

A COM component is responsible for un-registering itself on the host machine by exporting a function called "DllUnRegisterServer" within it's component file. All the COMate_UnRegisterCOMServer() function does is call this exported function.


COMate_RegisterCOMServer

Retrieving an interface IID.

COMate_GetIIDFromName()

HRESULT = COMate_GetIIDFromName(name.s, @IID)

Advanced users only!

Given the 'friendly name' of a COM interface (not a COM class), this function attempts to locate the 128-bit IID (interface identifier) of the interface in question from your computer's registry. This function is for convenience really and saves having to physically embed the IID within your program's data section etc.

The function is perhaps of particular use with the SetEventHandler() method of the COMateObject class.

The return value is one of the standard COM returns to indicate 'success' or 'failure'.

Parameters :

name

The 'friendly name' of the COM interface whose IID is required.

Examples of such names are "iUnknown", "iDispatch", "DWebBrowserEvents2" and so on.

NOTE that the name is case sensitive.

@IID

The address of a 16-byte buffer which is to receive the IID in question.

NOTE that a detailed description of any resulting HRESULT values (including a text description) is available through the following COMate functions :

  1. COMate_GetLastErrorCode()
  2. COMate_GetLastErrorDescription()


LICENSE

DAYLIKE
(Do As You LIKE with it!)

Nice and simple.

Needless to say though that I will accept no responsibility for any damage or injury (???) caused by the use (or misuse) of this software. I do not even assert that it is 'fit for purpose' in any way shape or form!

An acknowledgement within any application making use of this utility is always nice as would an acknowledgement to the original DispHelper software on which COMatePLUS is based (I do not know the name of the DispHelper author!)