Showing posts with label COM. Show all posts
Showing posts with label COM. Show all posts

07 December 2024

CreateObject peculiarities (2)

Lately I received some COM-automation related questions which I answered by referring to this blog post: CreateObject Peculiarities (part 1). It advised to use the CoCreateInstance() API with the IID_IUnknown parameter to connect to an automation server. However, after re-reading I realized I could have demonstrated how to use CoCreateInstance. Below is an example that replaces the GB32 CreateObject() function with the custom made CreateObject2() function. It provides the same functionality as CreateObject and more. Where CreateObject only returns an object if it supports the IDispatch inteface, the CreateObject2() function has an optional parameter that takes any interface you want to request from the server. By default, it returns an object that  supports the IDispatch interface, and if that fails it returns the IUnknown interface.

The code is heavily commented, so I hope you will be able to understand it. As an example, CreateObject2 is used to obtain the IDispatch interface of the Scripting's FileSystemObject.

$Library "gfawinx"
$Library "UpdateRT"
UpdateRuntime      ' Patches GfaWin23.Ocx

Dim FSO As Object
' CreateObject2(ClsID [, IID]) replaces CreateObject(ClsID).
Set FSO = CreateObject2("Scripting.FileSystemObject", IID_IDispatch)
MsgBox0("Successfully created object.")
' FSO object is released when it goes out-of-scope.

Function CreateObject2(ClassID As String, Optional IID_Interface As Long) As Object
  '-------------------------------------------------------------------
  '  Like CreateObject creates an Instance of an OLE Server.
  '  The ClassID argument creates a class & specifies the OLE Server.
  '  The IID_Interface argument specifies the interface to create.
  '  There are two formats for the ClassID class argument:
  '   1. PROGID: "Excel.Application"
  '   2. CLSID: "{00000010-0000-0010-8000-00AA006D2EA4}"
  ' If a ProgID is used, the client's registry is used to get the CLSID
  ' If the optional IID_Interface parameter isn't used, a reference
  ' to the IDispatch interface is created. If that fails, a reference
  ' to the IUnknown interface is created.
  ' The IID_Interface is pointer to GUID type, created by GUID command.
  '-------------------------------------------------------------------

  Dim wProgId As Variant = ClassID          ' to Unicode
  Dim Ptr_ProgID As Long = {V:wProgId + 8}  ' string address
  Dim HResult As Long           ' COM error code
  Dim ClsID_ProgID As GUID      ' GUID is built-in type
  Dim fAskDispatch              ' set if II_IDispatch is asked

  ' Get CLSID (GUID) type from either GUID$ or ProgID$
  If Left(ClassID) = "{" && Right(ClassID) = "}" && Len(ClassID) = 38
    HResult = CLSIDFromString(Ptr_ProgID, ClsID_ProgID)
  Else
    HResult = CLSIDFromProgID(Ptr_ProgID, ClsID_ProgID)
  End If
  If HResult != S_OK Then _
    Err.Raise HResult, "CreateObject2", "Wrong CLSID"

  ' The default is to ask for IDispatch (like CreateObject)
  If IID_Interface = 0 Then IID_Interface = IID_IDispatch
  ' Remember whether IID_IDispatch is asked
  fAskDispatch = IsEqualGUID(IID_Interface, IID_IDispatch)

  ' Create a single instance of an object (ClsID_ProgID) on
  ' the local machine that supports the requested interface.
  ' Store the instance in the local function-return variable.
  HResult = CoCreateInstance(ClsID_ProgID, Null, CLSCTX_ALL, _
    IID_Interface, V:CreateObject2)

  // Only if asked for IID_IDispatch and it failed, try IID_IUnknown
  If HResult == E_NOINTERFACE && fAskDispatch
    IID_Interface = IID_IUnknown
    HResult = CoCreateInstance(ClsID_ProgID, Null, CLSCTX_ALL, _
      IID_Interface, V:CreateObject2)
  EndIf

  // If all requests for an interface have failed raise error
  If HResult != S_OK Then _
    Err.Raise HResult, "CreateObject2", "No interface"

  ' ------------------------------------------------------
  ' Global declarations section
  ' ------------------------------------------------------
  Global Const E_NOTIMPL = 0x80004001
  Global Const E_NOINTERFACE = 0x80004002

  ' -------------------------------------------------------
  ' GUID Identifier = value    (global declaration command)
  ' Generates a pointer to a 128 bit memory block containing
  ' a GUID value. So, IID_IUnknown is a Long holding the
  ' address of the GUID value.
  ' -------------------------------------------------------
  GUID IID_IUnknown  = 00000000-0000-0000-c000-000000000046
  GUID IID_IDispatch = 00020400-0000-0000-c000-000000000046

  Global Enum CLSCTX, CLSCTX_INPROC_SERVER, CLSCTX_INPROC_HANDLER = 2, _
    CLSCTX_LOCAL_SERVER = 4, CLSCTX_REMOTE_SERVER = 16, _
    CLSCTX_SERVER = CLSCTX_INPROC_SERVER + CLSCTX_LOCAL_SERVER + _
    CLSCTX_REMOTE_SERVER, _
    CLSCTX_ALL = CLSCTX_INPROC_SERVER + CLSCTX_INPROC_HANDLER + _
    CLSCTX_LOCAL_SERVER + CLSCTX_REMOTE_SERVER

  ' -------------------------------------------------------
  ' CoCreateInstance note.
  ' The GB GUID is a pointer to a GUID type in memory.
  ' To make it possible to use a GB-GUID, we should adjust
  ' the Declare to receive a Long holding the address.
  ' -------------------------------------------------------
  Declare Function CoCreateInstance Lib "OLE32" _
    (ByRef rclsid As GUID, ByVal pUnkOuter As Long, _
    ByVal dwContent As Long, ByVal pIID As Long, _
    ByVal ppv As Long) As Long
  Declare Function CLSIDFromString Lib "OLE32" _
    (ByVal lpszCLSID As Long, pclsid As GUID) As Long
  Declare Function CLSIDFromProgID Lib "OLE32" _
    (ByVal lpszProgID As Long, pclsid As GUID) As Long
  Declare IsEqualGUID Lib "ole32" (ByVal prguid1 As Long, ByVal prguid2 As Long) As Bool
EndFunc

03 April 2024

Variant in an UDT?

Can you put a Variant in an user-defined type? It depends.

User defined type
A user-defined type is a data structure that can store multiple related variables of different types. To create a user-defined type, use the Type…End Type statement. Within the Type…End Type statement, list each element that is to contain a value, along with its data type.

Type SomeType
  Left As Long
  FirstName As String * 5
  Ages (1 To 5) As Byte
End Type

This code fragment shows how to define an user-defined type, it contains a 32-bit integer, a fixed string occupying 5 characters (bytes) and a 1-dimensional array of type Byte, also occupying 5 bytes. In total (theoretically) the user-defined type occupies 14 bytes. To get the actual size of the UDT you would use the SizeOf(SomeType) function, which reports a size of 16 bytes. The member Left is located at offset 0, FirstName at offset 4, and Ages starts at offset 9. Because the size of the structure is a multiple of 4, due to the default Packed 4 setting, the type is padded with empty bytes to fill it up until it is a multiple of 4. (A Packed 1 setting would return a size of 17.)

It is important to note that all data is stored inside the UDT. GB32 does not allow data to be stored outside the boundaries of the UDT. So, any Ocx type, Object type, or String type are forbidden, because they are pointer types that store their data somewhere else. (If it would be allowed, a Type with only one string member would have a size of 4, because only the 32-bits pointer to the string data would be reserved.) Because pointer types aren't allowed as Type members, GB32 does not call an 'UDT-destructor' when the UDT variable goes out of scope as it does with a String or Object variable as showed in the following sample:

Proc LocalStr()
  Local String s = "Hello"
  // ...
EndProc         // Destroys the string s

Now, suppose a dynamic String would be allowed in an UDT. When the UDT goes out of scope, GB32 would need to free the memory allocated by the string, which it cannot. In fact, GB32 does nothing at all when an UDT goes out of scope. A local UDT variable is located on the stack and GB32 simply restores the stack to its state it was before invoking the procedure.

Variant in UDT
Because of these restrictions, people are tempted to store a string in a Variant Type member, simply because GB32 does allow this. Then, when the Variant is then assigned a string (or an object) GB32 does not complain. However, when you assign a string to a Variant, the string is converted to a BSTR type and stored somewhere in COM-system memory (using SysAllocString*() APIs). Because GB32 does not release the Variant in the UDT, this memory is never freed. In addition, since the string memory isn't located on the program's heap, it won't even be released when the program is ended. It remains in COM-memory forever.

Conclusion
You can use a Variant in and UDT, but only to store simple numeric types. Dynamic data types (String and Object) are never released.

09 February 2024

What is the purpose of New?

The New keyword is used to create a COM object instance that cannot be created otherwise. In GB32 the most objects are created using the Ocx command, however not all COM types (Collection, Font, DisAsm, StdFont, and StdPicture) can be created this way.

The Ocx command is defined as:

Ocx comtype name[(idx)] [[= text$] [,ID][, x, y, w, h]

It performs two things:

  1. Declares a global variable name of the specified and predefined comtype
  2. Creates an object instance and assigns it to name.

The comtype is one of the GB32 provided COM types: a window based objecttype like Form, Command, but also Timer. Creating concrete objects of these types require initialization parameters, sometimes optionally, but the syntax must be able to accept additional parameters.

To create objects of non-window based types GB32 provides the New keyword to be used in a variable declaration statement. Here also, the two step process must be followed: declare a variable and assign the object instance.

Dim name As [New] comtype

Without the New keyword only a variable is declared and initialized to Nothing (0). New forces GB32 to create an instance (allocate memory for it) and assign that memory address to the variable name. In contrast with the Ocx command which creates global variables, these objecttypes can be declared and created locally. When the procedure is left the object is released from memory.

When declaring a COM type (Ocx or Dim) GB32 only reserves a 32-bits integer for the variable. The variable is a pointer to the allocated memory of the object and initially zero (Nothing). For instance:

Dim c As Collection

The variable c is a 32-bits pointer that can be assigned another instance of Collection by using the Set command.

Passing a COM object
An often asked question is whether an object variable should be passed ByRef or ByVal to a procedure. Since the variable is a pointer a ByVal parameter accepts a copy of the pointer and ByRef the address of the 32-bits pointer variable. In both cases GB32 knows how to handle the passed value, although a ByVal passed pointer outperforms a ByRef parameter. To access a ByRef parameter there is always an extra level of indirection.

Passing an object does not increment the reference count on the object.

18 January 2020

High resolution timer wrapped in a COM object

Only recently I needed a timer with a shorter interval than that the Ocx Timer can provide. The Ocx Timer smallest interval is 15.625 ms – 64 ticks per second - where I needed an interval of 10 ms to receive 100 timer-events per second. After some research I decided to use the API function CreateTimerQueueTimer() as a high-resolution timer. For a discussion on available timers see this Code Project article. I didn’t use the multi-media timers because MS advises against it, because these timers increase the system clock’s frequency which leads to a drain of battery-power on mobile devices. Nevertheless, the CreateTimerQueueTimer() API only produces shorter intervals than 15.625 ms if the the application’s system clock is adjusted as well using the multimedia function timeBeginPeriod(). This function cannot be used to increase the resolution of the SetTimer API which is used by the Ocx Timer.

A resource must be deleted
As with most Windows resources the queued timer comes with a create- and a release function. The created timer is released using the Windows API DeleteTimerQueueTimer() and is (usually) invoked when the program terminates. In addition, at the very end of the application, the system timer must be reset using timeEndPeriod(). The most common scenario for a GB program is outlined in this simple program:

$Library "mmsystem.inc"
OpenW 1
Global timerHandle As Handle, param As Large
' Create a 10 ms timer with ID=1 for Me
param = MakeLargeHiLo(Me.hWnd, 1)   ' assemble handle and ID
~timeBeginPeriod(1)
CreateTimerQueueTimer(timerHandle, Null, _
  ProcAddr(TimerQProc), V:param, 0, 10, WT_EXECUTEINTIMERTHREAD)
Do
  Sleep
Until Me Is Nothing
DeleteTimerQueueTimer(Null, timerHandle, Null)
~timeEndPeriod(1)

Proc TimerQProc(ByVal pParameter As Long, ByVal TimerOrWaitFired As Long) Naked
  ' Process timer event
  Dim pL As Pointer Large, hWnd As Handle, ID As Long
  Pointer pL = pParameter
  hWnd = HiLarge(pL), ID = LoLarge(pL)
EndProc

This sample only shows the general structure of a program that uses a Windows timer resource, the structure of the program is the same if it uses some other Windows resource. In this scenario a Windows resource is allocated before entering the message loop and released after the message loop has finished and the last Form has closed. However, when the program unexpectedly stops with a runtime-error the code below the message loop is never executed! This leads to unreleased Windows resources, something you don’t want. When GB raises a runtime error it stops at the line the error occurred and halts further execution of the program. The program’s windows (Forms) remain on the screen waiting to be closed or ‘cleaned up’ by using the wipe-window button in the IDE’s toolbar. Closing the remaining windows this way does not trigger any event subs like - for instance - the Form_Destroy event sub. Consequently, it is  pointless to move the resource delete function to this event sub, because it is not executed once the program stopped with a runtime error.

Each time the program is run within the IDE and stops with a runtime error it does not release the allocated resources. But, this is also true for the GB function mAlloc calls that require a call to mFree to release the memory. We need a way to release allocated resources under all circumstances. 

Using a COM wrapper
When GB stops executing after a runtime error it still releases GB resources, it closes I/O channels and deletes any TempFileName files, and finally it clears all the program’s global variables. For dynamic variables types (String, Object, arrays, hashes) the allocated memory is freed as well. (Therefore, it is sometimes better to use a string to allocate memory than to use mAlloc, strings are freed automatically.) For global variables that hold a COM object GB calls the Release vtable function of the IUnknown interface that each COM object implements. So, if we could wrap the resource handling in a (minimal) COM wrapper and store it in an Object type we are assured the Release function is called and we can properly delete the resource in the object’s Release function. This way we’re able to free the resources under all conditions.

If you’re not familiar with COM objects and the IUnknown implementation you might read a previous post first: COM in GB32 – IUnknown. The rest of this post discusses how to create a minimal COM wrapper for the queued timer APIs.

The minimal COM wrapper
The following full working sample creates a queued timer in the QueTimer function which returns an Object that holds a reference to the minimal COM object it creates. A COM object must at least implement the IUnknown interface that consists of the QueryInterface, AddRef and Release functions. Since this COM object doesn’t support any other interfaces (except IUnknown) we simply return with E_NOTIMPL from the QueryInterface function. The COM object is built manually in code and cannot be created by a function like CreateObject(). As a result QueryInterface is never called. The AddRef and Release functions require a proper implementation since these vtable functions are called by GB’s Set command.
The vtable functions must have the Naked attribute, or at least a $StepOff command, to prevent the GB compiler from inserting Tron code which can result in nasty and hard to find bugs. This is also true for any callback function Windows calls; the QueTimer callback procedure needs the Naked attribute as well.

$Library "mmsystem.inc"

OpenW 1, 0, 0, 300, 300, 48
PrintScroll = 1 : PrintWrap = 1

Global Object tmrQ1, tmrQ2
Set tmrQ1 = QueTimer(Me, 1, 10)   ' ID=1, 10 msec
Set tmrQ2 = QueTimer(Me, 2, 1000) ' ID=2, 1000 msec

Global Long Count, CountToErr
Do
  Sleep
Until Me Is Nothing

Sub Win_1_Message(hWnd%, Mess%, wParam%, lParam%)
  ' Process the WM_TIMER
  Static Long CountToErr
  If Mess% = WM_TIMER
    If wParam% == 1
      Count++
      Print ".";      // do something
    ElseIf wParam% == 2
      TitleW 1, "Timer Events/s:" + Str(Count) : Count = 0
      ' Interrupt GB with a runtime error after 10 sec
      CountToErr++ : If CountToErr = 10 Then Error 3
    EndIf
  EndIf
EndSub

Proc QueTimerProc(ByVal pParameter As Long, ByVal TimerOrWaitFired As Long) Naked
  ' Callback function
  Local hWnd As Handle, id As Long, pObj As Pointer IQueTimer
  Pointer pObj = pParameter             ' holds address of a IQueTimer object
  ~PostMessage(pObj.hWndTarget, WM_TIMER, pObj.TimerID, 0)
EndProc


Function QueTimer(frm As Form, id As Long, mSec As Long) As Object
  ' Create high-resolution timer wrapped in a minimal COM object.
  Global Long g_IQueTimerCnt

  Type IQueTimer         ' definition of object
    lpVtbl As Long
    refcount As Long
    Handle As Handle
    hWndTarget As Handle
    TimerID As Long
  EndType

  ' Set up the IUnknown vtable, same for each object
  Static vTable(0 .. 2) As Long    ' must remain in memory
  If vTable(0) == 0                ' do this only once
    vTable(0) = ProcAddr(IQueTimerVtbl_QueryInterface)
    vTable(1) = ProcAddr(IQueTimerVtbl_AddRef)
    vTable(2) = ProcAddr(IQueTimerVtbl_Release)
  EndIf

  ' Alloc and clear an IQueTimer object (Type) and
  ' assign it to an IQueTimer pointer.
  Local pObj As Pointer IQueTimer
  Pointer pObj = cAlloc(1, SizeOf(IQueTimer))

  ' Initialize the IQueTimer object
  pObj.lpVtbl = ArrayAddr(vTable())     ' set vtable
  pObj.refcount = 1                     ' set refcount
  pObj.hWndTarget = frm.hWnd            ' target window
  pObj.TimerID = id                     ' timer ID

  ' Create the API timerqueue resource and
  ' if succesfull finish the COM object, otherwise
  ' free the allocated memory.
  Local timerHandle As Handle
  If CreateTimerQueueTimer(timerHandle, Null, _
    ProcAddr(QueTimerProc), Pointer(pObj), 0, mSec, WT_EXECUTEINTIMERTHREAD)
    '  store the resource handle in the object
    pObj.Handle = timerHandle

    ' Set the system's clock resolution to 1 ms,
    ' do this only once per application.
    If g_IQueTimerCnt == 0 Then ~timeBeginPeriod(1)
    g_IQueTimerCnt++            ' count the number of instances

    ' Return COM object as Object
    {V:QueTimer} = Pointer(pObj)

  Else    ' something went wrong, release already allocated resource(s)
    ~mFree(Pointer(pObj))     ' free the alloced memory

    ' Do not set returnvalue to return Nothing
  EndIf
EndFunc

Function IQueTimerVtbl_QueryInterface(ByRef This As IQueTimer, _
  ByVal riid As Long, ByVal ppvObject As Long) As Long Naked
  Return E_NOTIMPL
EndFunc

Function IQueTimerVtbl_AddRef(ByRef this As IQueTimer) As Long Naked
  this.refcount++
  Return this.refcount
EndFunc

Function IQueTimerVtbl_Release(ByRef this As IQueTimer) As Long Naked
  this.refcount--
  If this.refcount == 0
    MsgBox "terminating" ' remove comment to see that Release is called
    DeleteTimerQueueTimer(Null, this.Handle, Null)
    g_IQueTimerCnt--     ' decrease instance counter
    ' If all instances are released, reset the system clock
    If g_IQueTimerCnt == 0 Then ~timeEndPeriod(1)
    ~mFree(*this)
  EndIf
  Return this.refcount
EndFunc

' Declares and Constants
Declare Function CreateTimerQueueTimer Lib "kernel32" (ByRef hNewTimer As Handle, _
  ByVal hTimer As Handle, ByVal Callbck As Long, ByVal Parameter As Long, _
  ByVal DueTime As Long, ByVal Period As Long, ByVal Flags As Long) As Long

Declare Function DeleteTimerQueueTimer Lib "kernel32" (ByVal hTimer As Handle, _
  ByVal Timer As Handle, ByVal CompletionEvent As Handle) As Long

Global Const E_NOTIMPL = 0x80004001
Global Const WT_EXECUTEINTIMERTHREAD = 0x00000020

The program creates two high-resolution timers and stores the minimal COM wrappers in the Object variables tmrQ1 and tmrQ2. The second timer is used to display the number of timer events per second produced by timer 1. There is nothing you can do with the Object variables, the minimal COM wrapper does not support any properties or methods. The Set command is the only command that can be used on these objects. The only reason for the existence of these Object variables is to sit and wait to be released so that the resources can be deleted properly. In fact, you could collect all globally used resources into the creation function – here QueTimer() - and release them in the Release vtable function.
To demonstrate the proper calling of the object’s Release the program raises an error after 10 seconds. A message box pops up to show you that Release is invoked after a runtime error.

Finally
If you’re not familiar with the binary layout of a COM object and maybe having trouble understanding how the COM object is build, don’t worry. You can copy paste this code to create your own minimal COM wrapper, simply replace the string ‘QueTimer’ with a name of your own (do not select Whole Word in the Replace dialog box). Then replace the code that creates and deletes the Windows resource with the functions you require. Of course you will need to edit the IQueTimer type that holds the information for a particular COM object.

18 March 2019

StdFont and StdPicture

GFA-BASIC 32 provides two COM objects for use with fonts and two for use with pictures, Font and StdFont and Picture and StdPicture. The Font and Picture objects are GB specific, they are used with other objects like Form.Font and Form.Picture, etc. So, why would do you need StdFont and StdPicture?

StdFont and StdPicture are standard automation OLE objects, so maybe you can use them with an automation server like Office? In theory it should. VB/VBA uses StdFont and StdPicture for its font and picture objects and they should be compatible with GB’s StdFont and StdPicture. However, when you try to assign a Font object from an Excel cell to a variable of the StdFont datatype, GB complains about incompatible data types.
There are a few situations where you might stumble upon a Std* COM type, for instance when you are converting VB/VBA code. Another use can be found for StdFont: it allows you to create a font object on its own. StdPicture is less useful in this respect.

StdFont and StdPicture are coclasses
StdFont and StdPicture are for use in a GB program mainly. Both types allow the New clause in a Dim statement, because both StdFont and StdPicture are coclasses from which you can create an object instance. (You cannot use New on a Font or Picture object.) The New keyword in the declaration inserts object-creation code into the program. The result of New is a new instance of the StdFont or StdPicture class provided by olepro32.dll. That’s one of the reasons GB requires the presence of this DLL. After an object has been created it has a pointer to an interface - located in olepro32.dll as well – which holds the address of the array of functions (properties and methods). These interfaces are called IFontDisp and IPictureDisp. In fact, IFontDisp and IPictureDisp only expose the IDispatch functions, there is no way to directly access the properties and methods. When you use a StdFont or StdPicture property the compiler inserts late binding code, it cannot early bind to the properties.
For a discussion on IDispatch see CreateObject Caching.

Normally, as with all IDispatch objects (Object type), you can only tell at runtime whether a property is accessed correctly. However, this is not true for the StdFont and StdPicture objects. GFA-BASIC 32 checks the syntax at compile time because it knows quite a lot of the properties that can be accessed through IDispatch. Therefor, the compiler can perform a syntax check on the names and arguments. In addition, the compiler can optimize the late binding code for the properties of these Std* types. The properties’ disp-ids are documented and the compiler can hard-code them into the executable code. This prevents the compiler from inserting code to obtain the disp-id before calling IDispatch.Invoke. Although the compiler can optimize a disadvantage of using the IDispatch interface is the use of Variants when passing arguments to and from properties.

Using StdFont and StdPicture
So there are only disadvantages in using StdFont and StdPicture, it seems. This is certainly true for the StdPicture; it doesn’t provide any useful functionality to actually create a picture. The only way to create a picture object is when you use CreatePicture or LoadPicture. You can assign such a picture to either a StdPicture or Picture data type. However, why would you want to assign it to a (New) StdPicture type? Let’s see how that should work.

Dim p As New StdPicture ' creates a new instance
Set p = LoadPicture(f$) ' assign new instance

The Set command assigns a new object to a variable. When that variable currently holds a reference to another object that object is released first. So, the StdPicture object instance created with New is released before the new picture is assigned.
The New keyword caused the creation of an ‘empty’ StdPicture object. Since all properties of StdPicture are read-only there is no way to manipulate the data of the StdPicture object (same is true for a Picture object). Consequently, the statement Dim p As New StdPicture is not very useful. It doesn’t provide any other functionality as the Picture object and it causes the compiler to insert (slower) late binding code.

The use of a StdFont is more useful. A New StdFont creates a new font that can be assigned to anything with a Font property. (In GB StdFont and Font are compatible types.) This feature is more useful than applying New on a StdPicture as the example shows:

Dim f As New StdFont    ' new instance
f.Bold = True           ' set properties
f.Name = "Arial"
Set frm1.Font = f       ' assign

In contrast with StdPicture the StdFont properties are also writeable and makes the StdFont a very useful object.

Conclusion
StdFont and StdPicture are IDispatch objects. Using New creates a new instance, but this isn’t very useful for a StdPicture object.

28 September 2017

CreateObject caching (.NET example)

Thanks to a little experiment using .NET classes from GB, I discovered a bug in the caching of dispids for IDispatch objects created using the CreateObject() function. (An update of the OCX runtime is available.) For demonstration purposes I’ll show you code accessing .NET classes I used to test caching. Caching is only available for objects created by CreateObject. I discussed the peculiarities of CreateObject in an earlier blog and if you need to freshen your memory you might want to check it out.

CreateObject and caching
The CreateObject function not only differs from VB/VBScript in creating a COM class and connecting to the IID_IDISPATCH interface, but it also sets up a Hash table to store the dispatch identifiers of the properties and method names. Each(!) object created from CreateObject is encapsulated in another hidden IDispatch object. The Object returned by CreateObject is a pointer to this hidden GB-provided COM object. The hidden object implements all 6 IDispatch functions, but only provides custom functions for GetIDsOfNames and Release. The other four (QueryInterface, Add, GetTypeInfoCount, and GetTypeInfo) are simply routed to the automation object directly. The hidden object provides the caching.
Only objects created with CreateObject are affected, that is if the class is not a GFABASIC32 Ocx, StdFont, or StdPicture type. When these terms are met, the CreateObject function creates an empty hash table and stores it in the encapsulating IDispatch object. When a property or method is executed for the first time, the identifier of the name is looked up using IDispatch.GetIDsOfNames function and saved in the hash table for future use.

Why caching is useful
The compiler converts the execution of a  property or method into a call to a runtime-library function which performs the actual execution of the property/method. The property/method is directed to the IDispatch.Invoke function. However Invoke does not accept named properties/methods, but only numeric values identifying the property or method. This integer value is called dispid. (You can retrieve a dispid yourself using the _DispId() function.) Before Invoke is executed the dispid value associated with the property/method name must the obtained by calling IDispatch.GetIDsOfNames function. Calling GetIDsOfNames each time before calling Invoke, decreases performance. This is especially true for out-of-process servers; non-DLL servers, DLLs are in-process servers, loaded into the process memory. Communicating with an out-of-process server is time consuming. Even more when the communication requires two steps, obtaining the dispid and executing the function. Once a property or method is used and its disp-ID value is known, it is logical to store the ID for future use in an easy accessible Hash table.

The encapsulating object
All IDispatch properties and methods are executed by the same runtime-library function, which is unaware of cached IDs. The GetIDsOfNames function is executed always before calling Invoke. However, the hidden Object that encapsulates the automation object, supports a custom GetIDsOfNames and first searches the hash table for the name. If it finds the name it has the ID value available directly. If the hash element isn’t found, it calls GetIDsOfNames on the external COM object and then stores the ID in the hash. When the Object goes out of scope, the custom Release function erases the hash table and the encapsulating object is freed from memory.

Example using .NET
The following code creates a .NET classes that support an IDispatch interface. The classes used are located in the mscorlib.dll, which is loaded into the process-space of the GB-application. The performance gain measured is about 15%. It will be better for out-of-process servers, the example is simply as a test for caching.

' ArrayList coclass supports multiple dual intefaces:
'  IList, ICloneable, _Object, ICollection, IEnumerable
' Togeher their exposed properties/methods make-up
' the ArrayList's class interface.
Dim oArrList As Object, v As Variant, i As Int
Set oArrList = CreateObject("System.Collections.Arraylist")
' IList
oArrList.add 3                ' IList.Add
oArrList.add 4
oArrList.add 4
Trace oArrList(0)             ' IList.Item(index)
Trace oArrList.Contains(3)    ' ILIst.Contains
' ICloneable
v = oArrList.Clone            ' ICloneable.Clone
' v is copy of ArrayList:
Trace v
' _Object interface
Trace v.ToString              ' _Object.ToString
Trace v.GetHashCode           ' _Object.GetHashCode
Set v = Nothing
' ICollection
Trace oArrList.Count          ' ICollection.Count
Trace oArrList.SyncRoot       ' ICollection.SyncRoot
' IEnumerable
For Each v In oArrList
  Trace v
Next

' Queue collection
Dim myQ As Object
Set myQ = CreateObject("System.Collections.Queue")
myQ.Enqueue "Hello"
myQ.Enqueue "World"
myQ.Enqueue "!"

12 May 2017

ANSI, UNICODE, BSTR and converting

Update 28-06-2017 - Conversion from Ansi to Unicode: WStr() function.
For some reason the WStr() routine contained a stupid bug (that I now have fixed).

More info: The number of bytes to read from a BSTR-address was wrong. GFA-BASIC always uses the SysAllocStringLen(Null, lenbytes) when allocating COM String memory. The BSTR returned is preceded by a 32-bits value specifying the BSTR's number of bytes, not the number of characters! This is exactly the value needed when reading the BSTR-bytes into a String datatype using StrPeek(). So, the function should have been: StrPeek(BSTR, {BSTR-4}), see the updated function below.

Another point of confusion was about the number of terminating null-bytes that WStr() returned. The StrPeek() function in WStr() only copies the UNICODE characters from the BSTR to a String, without the two null-bytes that secretly follow a BSTR string. As a result, the UNICODE characters copied to the String datatype are followed by only one (1) null byte; the terminating null byte that each String secretly gets.
When a String of UNICODE characters is to be passed to a Wide API function, two null-bytes must be added 'manually'.
w$ = WStr("GFABASIC") + #0#0 ' assign two nullbytes

The post as it was:
In the previous post I discussed UNICODE versus ANSI in the ANSI-based GFA-BASIC. Basically, GB doesn’t support UNICODE because it expects 1-byte characters where strings are used. In UNICODE each character occupies 2 bytes and allows more than 256 characters. Conversion ANSI to UNICODE is ok, but conversion from UNICODE to ANSI might lead to a loss of characters with a value above 256. But there is more: Variants and BSTRs.
The introduction of COM in GB required the provision of a new data type, the Variant. The Variant is a 16-byte data type that holds data and a value that specifies the type of that data(LONG, CARD, DOUBLE, etc). A Variant can also be used to store (safe-) arrays, a specific COM array type, and BSTRs, special UNICODE strings. So to understand the String and BSTR/Variant in detail ….

How a String is stored
Because a BSTR is much like a GFA-BASIC String data type, I’ll first tell how a GB String is stored. You could skip this part if you already know.
Declaring (Dim) a String-variable introduces a name for a location. The String-variable itself requires four bytes to store a pointer to dynamically allocated memory for the characters. The declaration and assigning a location is handled by the compiler, the rest happens at runtime: assigning or initializing. When the String-variable is initialized a call to malloc() reserves memory for all its characters with an additional 5 bytes. The first 4-bytes are reserved to store the length of the string and the last byte for the null-byte (not included in the length value). After allocating and copying the characters, the address of the first character of the string is stored at the variable’s location, a 32-bits address or pointer.
Global a$       ' 32-bits location(=0) in data or stack
a$ = "GFABASIC" ' assign pointer (address) to location
l = Len(a$)     ' address <> 0 return length {address-4}
Clr a$ : a$= "" ' free memory, set locations to 0
- String in memory: [xxxx|cccccc…c|0]
- Initially, the variable is a null pointer, the contents of the variable’s location is 0.
- String variable points the address of the first character c.
- Length is stored in position address – 4, and does not include the terminating zero.

Obtaining the string’s length is a 2-step process. First the variable is tested for a non-null pointer and than the value of the preceding 4 bytes (string-address – 4) is returned.
- Clearing a string (or assigning an empty string “”) will free the allocated memory and reset the variable’s contents to 0.

BSTR in GB
GB does not provide a data type BSTR, but it provides limited support of hidden BSTRs to pass and obtain BSTR-strings to and from COM objects. GB handles the conversion and memory allocation for BSTRs, but it does not provide string-manipulation functions for BSTRs, or even BSTRs in Variants. More on this below.
BSTR variables are always temporary, hidden local variables used to communicate with COM properties/methods that take or return BSTR arguments. These hidden BSTR variables are always destroyed when leaving a subroutine. Even the Naked attribute won’t prevent the inclusion of the termination code.
BSTR strings are COM based strings. They are allocated from COM-memory and consequently the memory can be managed by both the provider of the COM-object provider and the client. That is the first difference. Next a BSTR contains UTF-16 coded wide characters, which I discussed in ANSI and UNICODE. The way COM stores a BSTR is much the same as GB stores a String variable. In fact, a BSTR is 32-bits location that stores a pointer to dynamically allocated memory with UNICODE formatted characters. The length of the BSTR is stored In front of the BSTR, again like GB’s String data type.

Use Variant for BSTR
Although, GB provides hidden support for BSTRs, the only way to get access to a BSTR is by using a Variant. The following example assigns a GB-String to a Variant. At runtime the code allocates a BSTR by calling SysAllocStringLen(0, Len(GB-String)) followed by copying the converted GB-String to the returned address. The address of the BSTR together with its data type is stored in the Variant. When the Variant variable goes out of scope, the BSTR from the Variant is released through a call to SysFreeString(address).
Dim vnt1 = "Hello"
Now it gets interesting. After GB invoked the SysAllocStringLen() COM API, it converts the ANSI string to UNICODE using a private conversion routine interspersing zero’s between the characters see ANSI and UNICODE. GB does not turn to the MultiByte*() APIs Windows provides, because GB supports ANSI characters only. In the conversion process to UNICODE no characters will be lost and the private function is extremely fast.
An optimized UNICODE conversion function
This knowledge makes it possible to obtain a UNICODE-string (not a BSTR) from a String argument through our own optimized conversion routine. Note
  • A UNICODE string is required if you want to use the Wide version APIs.
  • A UNICODE string does not have a length field in front of it. It is not a BSTR. It only specifies how much bytes a character occupies (2).
  • It’s memory is managed by the program through malloc() – no COM memory - and it ends with two null-bytes (although it seems 1 is ok as well).
  • The converted ANSI argument is placed in a String only because it is a convenient data type to store consecutive data.
The function makes use of the BSTR allocation and conversion functionality of the Variant.
(The $Export is there because it comes from a .lg32 file).
Function WStr(vnt As Variant) As String Naked ' Return UNICODEd string
  $Export Function WStr "(AnsiString) As String-UNICODE Naked"
  Dim BSTR As Register Long
  BSTR = {V:vnt + 8} ' BSTR address at offset 8
  Return StrPeek(BSTR, {BSTR - 4}) ' <- 28-06-2017="" font="" updated="">
EndFunc
1. A function very well suited for the Naked attribute, because it does not contain local variables that contain dynamically allocated memory that would otherwise require explicit release code.
2. The argument of the function is ByVal As Variant. This forces the caller (calling code) to create a Variant and than pass it by value by pushing 16-bytes (4 DWords) on the stack. Whether the Variant is passed by value or by reference, the calling subroutine is responsible for freeing the BSTR stored in the Variant. However, ByVal is interesting because …
3. The GFABASIC-compiler provides a hidden optimization when you pass a literal string to a ByVal As Variant. A ByVal Variant requires16 bytes to push on the stack, but the UNICODE characters the Variant points to are already converted at compile time. Therefor the following call is extremely efficient:
Dim t$ = WStr("GFABASIC")
The GFA-BASIC compiler stores the literal string “GFABASIC” as a UNICODE sequence of bytes (2 per character) and does not need to allocate (COM) memory and convert at runtime. This also relieves the caller from releasing the BSTR-COM-memory, so the calling function doesn’t need to execute Variant destruction code.
Assigning a UNICODE formatted string this way, is almost as efficient as initializing a String with an ANSI literal string. It only takes a few cycles to call and execute the WStr() function.
4. The caller provides the String variable to store the return value of the function. That is the function’s ‘local variable’ WStr is silently declared in the calling subroutine. The hidden string is passed as a ByRef variable to the function. The return value (String) is directly assigned to the hidden variable. If an exception would occur in function Wstr() the termination code of the caller will release the hidden WStr string variable. (Therefor Naked is perfect for this function: it doesnot need to provide explicit release code.)
5. Inside the function you can see two more optimizations. First the local Long variable that stores the address of BSTR is a register variable; no stack memory and copying required. The other optimization is the Shl 1 expression that multiplies the length of the BSTR by 2. This results in an integer asm add eax, eax instruction, rather than a floating point multiplication. Also a significant optimization.
6. Other mathematic operations like V:vnt+8 and BSTR-4 are relative address operations and are properly compiled into indirect addressing instructions. So, no chance here to optimize.
I went in some detail to explain the function hoping you’ll find it useful. I hope to tell more about the way the compiler constructs subroutines and performs optimizations.

18 October 2015

The Non-existing MenuItems Collection

MenuItems is a collection representing all the menu items contained in a Form’s menu. According to COM/VB rules the next phrase should be true:
calling Add on this collection adds an item to the menu and returns a MenuItem object”.
However, there is no actual MenuItems collection you can use in GB32. The MenuItems data type does exist, but contains nothing more than empty stub functions.

Logically, the MenuItems dependent collection (it exists only when a menu-bar is present) should be used to manage the menu items. But in Dependent Collections I showed you why GB32 considers an in-between collection redundant and overkill. Generally, the collection’s implementation does nothing more than reroute all the collection’s properties and methods to the required item object (by key or index). In the case of the MenuItems collection, well GB32 skips the collection entirely.

Where do they come from
GB32 creates a MenuItem object for a menu-entry when it parses the one-dimensional string array containing the menu-entries for a window.

Menu menuEntries$()

Since the beginning, this how a GFA-BASIC menu-bar is created. Fill an array with string elements and let GB create and assign a menu-bar to the current active window. When the window is destroyed, the MenuItem objects are destroyed as well. The only other way to release the MenuItem-objects associated with a menu-bar of a Form is by using Menu Kill.

Now let’s see how to manage the menu-bar entries without the MenuItems collection. This process is not VB-compatible, as VB prescribes the use of a collection. As you probably know, dependent collections are used to iterate over the items they contain, to add new items, to remove items, etcetera. To provide these facilities, the MenuItems collection would provide the following properties and methods:

Property or method Description
Add method Add menu-items to the collection.
Count property Return the number of menu-items in the collection. Read-only.
Item method Return a menu item, by index (or by key).
Remove method Delete a menu.item from the collection, by index (or by key).
Clear Remove and delete all menu-items

Since GB32 doesn’t implement the MenuItems collection, these properties and methods aren’t available. GFA-BASIC 32 does not provide a collection to manage menu-items, but it does however provide MenuItem objects to allow access to single menu-entries. The Form supports a property that returns a MenuItem object. The property takes one parameter, a Long MenuID-value. The value must represent the menu-ID of the menu-entry, which is either the index of an element of the string-array or a designated ordinal (specified in the string element that specifies the menu-item text).

Dim mnuItem As MenuItem
Set mnuItem = frm1.MenuItem(1)

The MenuItem-objects are created at run-time when GFA-BASIC executes the command Menu $(). The ordinal ID-value is assigned both to the MenuItem object as well as to the Windows API functions. The ID-value can not be changed later on. Also, any changes made to a menu-entry using API functions will not be reflected in the MenuItem object. So, all changes of the menu-entries are to be done using a MenuItem object.

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.

Debug.Show
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.

Conclusion
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.

30 November 2013

Using OLE/COM Object Viewer

Ever wondered how to get information about all properties and methods of the GFA-BASIC 32 OCX COM-objects?

To view GFA-BASIC OCX type library information, you can use the Microsoft OLE/COM Object Viewer utility that is included with the Microsoft Windows SDK kits.

image

The figure displays information from the GfaWin23.Ocx type library, a resource found in this DLL. More specifically, it shows the details for the Form Ocx and the CurrentX Get Property. You use the OLE-COM Object Viewer to view compiled type library information. The object viewer presents the COM classes in C/C++ syntax.

To view a type library with the OLE/COM Object Viewer:

  1. Find the OLE/COM Object Viewer application in the bin directory of the installation path.
  2. On the File menu, click View TypeLib, and browse to locate the type library you want to view (GfaWin23.Ocx)

Like the Object Browser that VB and Microsoft VBA use, the OLE/COM Object Viewer lists all the classes exposed in a type library, along with the methods and properties those classes support. For the programmer, the OLE/COM Object Viewer provides important information, such as function return types, argument types, and DISPIDs (which will be discussed shortly). You can use the information that the viewer provides in conjunction with the GB32 object model documentation.

 

Download SDK
The latest Windows Software Development Kit for Windows 8 (Windows SDK) contains headers, libraries, and a selection of tools that you can use when you create apps that run on Windows operating systems.
Download:
The Windows Development Kit (SDK)

I also advise to read Automating Microsoft Office 97 and Microsoft Office 2000 to refresh your memory on the binary layout of COM objects. You may take a look at the section “How an object can exposes its methods and properties”. It does so in two ways: by means of a dispatch interface and in its vtable. Also take a look at my blogs Build a COM object in GB32 (1) and    CreateObject.

A COM object exposing its functions through a vtable only, provide clients with early binding only. The compiler binds methods and property invocations to the vtable-function-pointers directly for more direct access and faster execution. An object that exposes its methods through both a dispinterface and its vtable supports a dual interface.

The DispID
You can examine type libraries in the OLE/COM Object Viewer to determine if an object provides a dual interface. An object that provides a dual interface specifies the dual attribute in its interface declaration. All GB32 OCX objects implement the IDispatch interface. It is this IDispatch interface that provides Automation clients with access to an object's content and functionality. An object's methods and properties collectively make up its dispatch interface (or dispinterface). Within the dispinterface, each method and property is identified by a unique member. This member is the function's dispatch identifier (or DispID).

A COM object can provide clients access to its functions using a dispinterface, an array of function names, and an array of function pointers that are indexed by DispIDs. In GB32 the execution of methods and properties through the dispatch interface is hidden behind the COM dot-operator on an Object data type. First see how GB32 executes properties and methods through early binding.

' Create new instance of the ImageList OCX.
' The Iml variable is of COM type (I)ImageList.
Dim Iml As New ImageList
' Access through early bindings; call the
' ImageHeight and ImageWidth directly through
' a function pointer.
Iml.ImageHeight = 16
Iml.ImageWidth = 16
Iml.Add  , "comp", LoadPicture(":MyComp")

See also: Dim As New

As you can see from OLE-VIEW, the IImageList interface is declared dual. So, the properties and methods are available at runtime as well (late binding). The GB32 compiler doesn’t put a reference to a function address, but stores the name of the method or property and the values of the parameters that must be passed. The compiler than inserts a call to Invoke() to have the program call the method or property at runtime by looking up the name of the function through GetIDsOfNames(). You may want to read the section on Binding in the same article Automating Microsoft Office 97 and Microsoft Office 2000

Note It is a VB/VBA convention to use the name of an interface a a new user-defined type and than skip the capital I from an interface name. So, IImageList in OLEVIEW becomes ImageList type in BASIC.

Now let us see how the method and properties of the ImageList type can be executed at runtime through late binding. The general BASIC Object data-type is an object consisting only of the IDispatch functions. The Object variable oIml doesn’t know about other interface functions ImageList supports. The compiler inserts code to execute the function .Count at runtime through the IImageList functions GetIDsOfNames and Invoke.

Dim oIml As Object      // An IDispatch object
Set oIml = Iml          // Assign to Object
Print oIml.Count        // late binding

To execute the Count property of the oIml object must call GetIDsOfNames to "look up" the DispID for the property. Subsequently, oIml calls Invoke to execute the property by using the DispID to locate the Count function address in the IImageList’s array of function pointers.

Next blog: GFA-BASIC 32 supports syntax and functions to look up the DispID and use Invoke.

25 June 2013

What to do with an OLE_COLOR?

An OCX/ActiveX control color property can be assigned any valid Win32 color reference. However, the OCX/COM libraries provide a special data type for color properties, the OLE_COLOR data type. This new type was invented to give VB-like programs an opportunity to differentiate between a normal Win32 color reference and system colors. These programs could then present a special color-selection dialog box that allowed the selection of system colors. The snapshot of the GB32 Ole-color selection is shown below.

image

RGB colors are of type COLORREF
Like an RGB-color value, an OLE_COLOR data type is internally represented as a 32-bit long integer. An RGB-color is referred to as a COLORREF value and is used with GDI device context functions. A COLORREF is simply defined as a 32-bit value used to specify an red, green, and blue components of a color requiring only 3 bytes. When specifying an explicit RGB color, the COLORREF value has the following hexadecimal form.

0x00bbggrr

The low-order byte contains a value for the relative intensity of red, the second byte contains a value for green, and the third byte contains a value for blue. The high-order byte must be zero. The maximum value for a single byte is 0xFF.
To create a COLORREF color value, you can use the GB32 RGB() function. To extract the individual values for the red, green, and blue components of a color value, use the GetRValue, GetGValue, and GetBValue functions, respectively. For performance reasons these GFA-BASIC 32 functions are directly translated to assembly code; they are not implemented as function calls.

The OLE_COLOR type
When a COM object property (most often .ForeColor and .BackColor only) requires an OLE_COLOR color, you can either pass an RGB  value (COLORREF) or a COM specific formatted system color index value. A COM specific format has it’s high-byte set 0x80. An OLE_COLOR can have two formats only:

  • 0x00bbggrr (COLORREF value)
  • 0x800000xx, where xx is the index used by GetSysColor().

The object doesn’t do much with the OLE_COLOR value, it is more a kind of a storage type. For instance, you cannot pass an OLE_COLOR to a GDI device context handle. GDI doesn’t know what to do with a COM formatted system index color (0x800000xx). The OLE_COLOR value is simply saved in the private data of the COM object and remains unchanged until the application explicitly changes the value again. This way, the Get-color-property variant of the Put-color-property will return exactly the same value as was first put in.

How the OLE_COLOR is used
Since the format of the OLE_COLOR data type isn’t known to Win32 drawing functions, they must be converted to RGB colors first. Usually, the OLE_COLOR is converted on a just-in-time basis. Just before a painting operation is performed the OLE_COLOR (32-bit long integer) is tested for a high-byte value of 0x80. When the high-byte is indeed 0x80, the low-byte value is used as a parameter to the GetSysColor() API function, which in turn returns the RGB color value of the specified display element. See the code below.

Aside: Identifying display elements
Display elements are the parts of a window and the display that appear on the system display screen. These elements are identified using the API COLOR_* constants, like COLOR_BTNFACE, COLOR_WINDOW, etc. These API constants may also be used in the GFA-BASIC 32 function SysCol(idx), which is slightly easier to use and a bit faster due to caching.

Dim rgb% = SysCol(COLOR_WINDOW)

The GFA-BASIC32 OLE_COLOR Constants
GFA-BASIC 32 also defines a set of OLE_COLOR constants to identify the system display elements. These constants are very much like the Windows API COLOR_* constants. However, instead starting their names with ‘COLOR_’ they start with ‘col’, like colBtnFace and colWindow. These GB32 OLE_COLOR constants have the format of 0x800000xx, where xx is the index for the display element. See the code below.

Convert an OLE_COLOR
When you want to use an OLE_COLOR value, make sure to convert the OLE_COLOR to a COLORREF (RGB) value first. This is the only color format Win32 understands. In GB32 you get hold of an OLE_COLOR when you use one the col* constants or retrieve a color from one of the Ocx-color properties. For instance, when you want to use the same color as the form’s background, you might very well end up with an OLE_COLOR with the high-byte set to 0x80.

Now, there are two possible ways to convert an OLE_COLOR to a COLORREF value.

1. Manually, in your code. For example this converts to an RGB-value:

Trace Hex(SysCol(colBtnFace))   ' =0 (SysCol: what is a colBtnFace?)
Trace Hex(colBtnFace)           ' =8000000F (an OLE_COLOR)
Trace Hex(OleToRGB(colBtnFace)) ' =F0F0F0 (the RGB value)
Trace Hex(OleToRGB(0x667788))   ' =667788 (no conversion)

Function OleToRGB(olecolor As Long) As Long Naked
  If (olecolor And 0xFF000000) == 0x80000000
    Return SysCol(olecolor And 0xFF)  ' LoByte
  Else
    Return olecolor                   ' RGB!
  EndIf
EndFunc

2. Use the system OLE function OleTranslateColor()

Declare Function OleTranslateColor Lib "oleaut32.dll" ( _
  ByVal OleColor As Long, _
  ByVal hPal As Handle, _
  ByRef ColorRef As Long) As Long

Dim rgb As Long Long

If OleTranslateColor(colBtnFace, Null, rgb) == S_OK Then _ Trace Hex(rgb)

The second option using OleTranlateColor() might be preferable because it can also return other GDI color formats, like a palette-index value. In addition in case of an error, it let you investigate the type of the error. See the SDK for more info.

More flexibility
You can use OLE_COLOR types with the GB32 graphic commands, though. All drawing statements, font statements, and color statements accept an OLE_COLOR type. GB32 converts the OLE_COLOR to a COLORREF before it invokes the corresponding GDI function.

12 April 2012

Ocx background color

Not all OCX controls support a .BackColor property to change the background color, although the control itself allows changing it. Here GB32 conforms to VB too much. Rather than adding a property to the implementation – which would require another patch in executable code – we can use a different approach.

All OCX controls share a user-defined structure to store the current settings of the wrapped control. Setting and getting property values is sometimes nothing more than changing and setting a member of the structure. Setting and obtaining the background value is one of them. The BackColor property stores its value in the member at offset $B8 of the start of object’s data block. The following code changes the background of a StatusBar Ocx.

Form frm1
Ocx StatusBar sb1
Trace SetBkColorControl(sb1, RGB(23, 56, 229))
Do
  Sleep
Until Me Is Nothing

Function SetBkColorControl(Ctrl As Control, Color As Long) As Long
  Const OffBackColor = $B8
  Local Long ObjPtr  = Long{*Ctrl}
  SetBkColorControl  = Long{ObjPtr + OffBackColor}
  {ObjPtr + OffBackColor} = Color
  ~InvalidateRect(Ctrl.hwnd, 0, 1)

  ' Notes
  ' 1 Using the IDispatch interface does not work
  '   when the GetTypeInfo doesn't support the
  '   the .BackColor property.
  ' BackColor is not a property of StatusBar
  If 0 Then Ctrl.BackColor = Color

  ' 2 When using XP Visual Themes the Visual Style
  '   theme takes over and gets precedence over
  '   GB OCX propery settings.
EndFunc

Note – Not all controls let their background set this way. Just experiment.