05 September 2011

CreateObject peculiarities

Why CreateObject isn't fully compatible to VB(Script).

Introduction
The GFA-BASIC 32 function CreateObject() creates and then references a COM automation object. Usually the function is used to control a target application (or documents or other objects supported by the target application). Controlling a target application through COM (or OLE) is called OLE Automation.

Understanding OLE Automation
OLE Automation is an OLE service for integrating applications. It enables an application to expose its functionality (MS Word), or to control the functionality of other applications on the same computer or across networks. As a result, applications can be automated and integrated with programming code, creating virtually endless possibilities. OLE Automation is the umbrella term for the process by which an OLE Automation controller sends instructions to an OLE Automation server (using the functionality exposed by the OLE Automation server), where they are run.

CreateObject returns a reference to an object determined by the class argument.

Syntax: Set object = CreateObject(class)

Depending on class, the reference may be to an existing object or to a newly created object. The object reference returned by CreateObject is (usually) assigned to an object variable of type Object. Assigning a COM object to a variable differs from assigning a value or variable to another variable. It always requires the Set statement (as opposed to the Let statement that is implicitly used by other assignments).

Note - Using CreateObject (or GetObject) to set the Object-variable to the target application, or some object that the target application supports, establishes a reference to the target application's Application object. Then the object variable in the sourcecode can use the methods and properties of the corresponding object in the target application.

The ProgID of an automation object
To create an automation object the class argument of CreateObject(class) can be a string of the form "appName.objectType". A string using this format is called a ProgID.
A ProgID is a string that gets converted to a CLSID (a 128-bits GUID value) by looking up the string in the register. Note that the automation object can only be found when it has registered itself using the same string format "appName.objectType". This happen,s for instance, with MS Excel which registers itself as "Excel.Application". And Internet Explorer as "InternetExplorer.Application". These applications can be referenced through automation as follows:  

Dim obj As Object
Set obj = CreateObject("Excel.Application")

The class argument
In GFA-BASIC 32 the class argument may also specify CLSID either as a string (1) or as GUID constant (2). For instance:

Dim obj As Object
Set obj = CreateObject("7b55b57b-cd72-449d-82eb-a02e0964e3eb")

GUID gd = 7b55b57b-cd72-449d-82eb-a02e0964e3eb Set obj = CreateObject(gd)

Remarkable features of GFA-BASIC's CreateObject() are:

  • The class$ argument can specify a GUID value as a string.
  • The parameter can specify the address of GUID user-defined type. (The GFA-BASIC statement GUID name = xxx..-..-.._..-...x defines a constant (Long) as the address to a GUID value.
  • The syntax does not support the second optional parameter server as is described in Help documentation.
  • The language locale of the target application can be set prior to invoking CreateObject() using Mode(Lang) = .
  • The CreateObject() function is not 100% VB(Script) compatible.

CreateObject 's compatibility
In COM terms CreateObject() connects to a coclass at run time using the IDispatch interface (late binding). The Object variable type performs property and method operations using the IDispatch interface functions. Once you’ve created a new object and returned a reference to it, you can work with the object in GFA-BASIC in the same way you would work with any other object. That is, you can set and retrieve the object’s properties and apply its methods. To be an automation object the object must support IUnknown and IDispatch. All COM objects support IUnknown, because that defines them as a COM object. See this post on COM interfaces.
The IDispatch interface defines late binding and allows a client to call the methods and properties on the server. Consequently, without supporting an IDispatch interface a server cannot be called an automation controller. GFA-BASIC 32 consequently asks the automation object for an IDispatch interface and only connects to the coclass when it acknowledges that it supports an IDispatch interface. Otherwise it generates a runtime error.

VB(Script) are wrong
However VB and VBScript implement CreateObject differently, more flexible maybe, but essentially wrong. At the heart of creating an object is the OLE function CoCreateInstance(). It takes a pointer to the interface the program wants to use to communicate with the object. Since 'OLE automation' is required, it seems logical to pass the GUID for the IDispatch interface (as GFA-BASIC 32 does). But VB(Script) doesn't. The VB-CreateObject passes the GUID for IUnknown which every COM object supports, because IUnknown is what an object makes a COM object in the first place! So in VB CreateObject always works, even when it shouldn't. But the object reference returned by VB's CreateObject is not guaranteed to support IDispatch.

Porting VB to GB
This causes trouble when porting a VB program to GB. Implicitly CreateObject calls the object's QueryInterface(), asking it if it supports IDispatch. A correct behavior for QueryInterface(0 would be to reply for each interface it supports. Badly written OLE automation objects sometimes forget to reply for all interfaces it supports (especially IDispatch). One of them is Microsoft's type-library-reader-tool  "TLI.TypeLibInfo". CreateObject fails with GB where VB does return a reference:

Set TLInfo = CreateObject("TLI.TypeLibInfo")

When GB's CreateObject doesn't work where VB(Script) works, don't blame GFA-BASIC 32, but the COM programmer instead. BTW in these cases you must use the CoCreateInstance() API.