02 December 2013

Using methods and properties through IDispatch

This is all about two – more or less – undocumented features; the GFA-BASIC _DispID() function and a special dot-curly operator, the .{dispID} syntax.

An object’s layout in memory is, when using a BASIC Object variable data type:

MemObj vtable (array of function pointers)
AddrOf vtable AddrOf QueryInterface()
RefCount% AddrOf AddRef()
…. AddrOf Release()
…. AddrOf GetTypeInfoCount()
… data AddrOf GetTypeInfo()
  AddrOf GetIDsOfNames()
  AddrOf Invoke()
  AddrOf ComPropertyMethod1()
  AddrOf ComPropertyMethod2()

After the following commands the BASIC Object variable oIE holds a reference to an instance of Internet Explorer, an automation server and therefor guaranteed to support a dual interface.

Dim oIE As Object
Set oIE = CreateObject("InternetExplorer.Application")
Trace Hex(Long{V:oIE})
Set oIE = Nothing

An Object variable is actually an Int32 data type and holds the address returned by CreateObject.
Initially, the Object variable is zero (or Null) and is interpreted as Nothing. The following statement does nothing more then checking if the variable oIE holds a value (is not zero):

If oIE Is Nothing Then _
  Debug.Print "No Object assigned"

To view the contents of the integer oIE we first needs its address. In GB32 you can obtain an address of a variable in several ways. You may use VarPtr(oIE), or V:oIE, or *oIE. After obtaining the variable address it must be read, or peeked, to get the contents. Reading a Long or Int32 from some address is done using Long{addr}. The Trace statement uses the Hex function to convert the value to a hexadecimal format, the usual format for a memory address.

From the previous blog post Using OLEVIEW we learned how to process the calling of a property or method through late binding. To summarize, to execute a method or a property of the object an automation client can:

  1. Call GetIDsOfNames to "look up" the DispID for the method or property.
  2. Call Invoke to execute the method or property by using the DispID to index the array of function pointers.

All BASIC languages support this behavior behind the curtains. They provide the object dot-operator to call properties and methods through late binding. For instance, when you want to check to see if Internet Explorer is visible you may call the Visible property:

If oIE.Visible == True Then _
  Debug.Print "Is visible indeed"

After compiling this code the program will execute oIE’s interface-function GetIDsOfNames to "look up" the DispID for the property. It will then generate code to call Invoke to execute the method or property by using the DispID to index the array of function pointers (vtable). Calling Invoke is a bit of a hassle and is best left to the compiler.

Now what when the server gets new functions and new names. Unfortunately, you would need to recompile and redistribute the client application before it would be able to use the new properties and methods. In order to avoid this, you could use a ‘CallByName’ function to pass the new property and method names as strings, without changing the application.

In contrast with other programming languages GFA-BASIC features a function called _DispID(). This function allows you to call the objects GetIDsOfNames function to "look up" the DispID for the method or property. When you remember the blog post on Using OLEVIEW you might have noticed that every property and method is assigned a unique ID (integer value). Using _DispId(0bject,Name$) we can obtain exactly that unique value. For instance, this will display the ID value 402 in the Debug output window.

Trace _DispID(oIE, "Visible")

Obtaining the dispId of a method or property is only useful when you can use the integer value to call the IDispatch function Invoke(). Specifically for this purpose GFA-BASIC 32 provides us with a might piece of equipment; the dot-curly operator. To call Invoke using the dispId you can simply replace the name of the property or method with ‘{dispId}’.

Global Long dispIdVisible
dispIdVisible =  _DispID(oIE, "Visible")
Trace oIE.{dispIdVisible}
Trace oIE.{402}

How does VB6 do it?
If you’re interested, you should compare this elegance to the VB6 function CallByName. This function allows you to use a string to specify a property or method at run time. The signature for the CallByName function looks like this:

Result = CallByName(Object, ProcedureName, CallType, Arguments())

The first argument to CallByName takes the name of the object that you want to act upon. The second argument, ProcedureName, takes a string containing the name of the method or property procedure to be invoked. The CallType argument takes a constant representing the type of procedure to invoke: a method (vbMethod), a property let (vbLet), a property get (vbGet), or a property set (vbSet). The final argument is optional, it takes a variant array containing any arguments to the procedure.

Due to the elegant syntax, GB detects how to invoke the method or property. The parameters of a property or method don’t go in a Variant array. Due to the dot-curly syntax the parameters are specified as any other property or method call. The only thing you need to do yourself is retrieving the dispID, but this is of great advantage since now you are able to store the ID to use it over and over. The CallByName() function each time has to obtain dispID for the name passed.

Dlg 3D On is for Dialogs only

GFA-BASIC 16 (for Windows 3.1) offered the command Dlg 3D On. to get more appealing 3D effects for dialog boxes and controls. This command was invoked before a dialog box was defined and/or created, which was the same thing in GB16. Usually the command Dlg 3D On was placed at the start of the program so that all dialogs would benefit from the 3D effect. In the background the 16-bit system CTL3D.DLL was loaded, was responsible for the painting.

What is CTL3D?
The 16-bit CTL3D.DLL hooks into dialog creation and control creation and "subclasses" the standard windows controls to give them a more appealing 3D effect. To draw 3D effects the background of the dialog boxes was painted gray, or more precisely, painted using the COLOR_BTNFACE color. That made the 3D effects possible in the first place.
This technique was first used by Microsoft Excel version 4.0. The Excel team shared the technology with the rest of the industry. Since then, the use of CTL3D has become a de facto standard and is commonly used by professional Windows applications. GFA-BASIC 16 supported the use of this DLL directly using Dlg 3D On/Off.

Still present in GB32
Starting with Windows 95 a much more pleasing 3D look is provided by default. This made CTL3D unnecessary. Despite its absence in Windows 32-bits GFA-BASIC 32 still supports the the Dlg 3D command, for backwards compatibility. However, its implementation is quite different now. The 3D effect is not accomplished through a system DLL, but by setting the default background color of the dialog box to COLOR_BTNFACE.

Dlg 3D On only works for Dlg form Ocx objects, these are the forms created using the Dialog # / End Dialog commands and accessed with the object variable name Dlg_n, where n is an ordinal value between 0 and 31.

Starting with GB32 most attributes of windows and controls are managed through OCX properties and methods. Internally, all window management is routed through the COM wrappers of the system OCX controls. This is also true for the creation of Dialog # forms. The runtime runs exactly the same code as when you create an Ocx Form. Other (16-bits compatible) window commands like OpenW, ChildW, and ParentW are created by this code as well. However when a Form is created using the Dialog command, the runtime code converts the Dlg 3D and  DlgBase Xxx commands to the appropriate Form-Properties.

Dialog # id, x, y, w, h, tit$ [,flag [,height,font$] ]

The attributes of a dialog form may be set beforehand using the following commands: 

Command Meaning
Dlg 3D On Fills and sets background with COLOR_BTNFACE
Dlg 3D Off Use default Form background-color (default)
DlgBase Pixel Use x, y, w, h as pixels (default)
DlgBase Unit Use x, y, w, h as dialog base units
DlgBase InSide Use x, y, w, h as client size
DlgBase OutSide Use x, y, w, h as window outside (default)
DlgBase Font font$ Use font$ for all controls
DlgBase Bold Use a Bold version of font$
DlgBase Bold Off Use a Normal version of font$
Dlg Fill Fills and sets new background color

The font$ parameter overrules the DlgBase Font setting.
Afterwards the background color can be changed by using properties or by using the Dlg Fill command.

These Dlg commands are used in conjunction with Dialogs only.