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

01 October 2024

DEP and GB32

From MS:

"Data Execution Prevention (DEP) is a system-level memory protection feature that is built into the operating system starting with Windows XP and Windows Server 2003. DEP enables the system to mark one or more pages of memory as non-executable. Marking memory regions as non-executable means that code cannot be run from that region of memory, which makes it harder for the exploitation of buffer overruns."

I underlined the sentence that is important to GB32 developers. When a program is RUN (F5), the GB32 inline compiler creates an executable in memory and runs it from there. The DEP setting of your system can prevent the running of the code. This happened to a user who bought a new PC with a factory DEP setting of 3. Not only couldn't GB32 run the code, other nasty things happened as well, for instance programs couldn't be saved anymore.

To obtain the your system's DEP setting, you will need to follow the following steps:

  • Go to This PC -> right click and select Properties.Then select Advanced Settings and choose the Advanced tab, now click the Settings button of the Performance section. Here you can select the Data Execution Prevention tab. Normally, the option to protect Windows programs and services is selected. This conforms to DEP setting = 2.

You could also try this small GB32 program to obtain the system's DEP setting:

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

Declare Function GetSystemDEPPolicy Lib "kernel32" ()
Debug "DEP-Setting: "; GetSystemDEPPolicy()

The fact that you can Run the code tells you that the DEP setting isn't 3. A setting of 3 wouldn't allow the execution of the code stored in memory by the GB32 compiler.

It isn't a problem solely for the GB32 developer, but the final EXEs created with GB32 will also suffer from a DEP setting of 3. The EXE is a stand-alone program and its code can be executed, but the UpdateRuntime function 'hacks' the GfaWin23.ocx runtime and reroute some code to new code in memory, and executing new code in memory is not allowed with DEP = 3.

Conclusion
To be able to execute a GB32 produced EXE the DEP system setting of the user must be 2 or lower.

03 July 2024

Function returning UDT

Last time we discussed why you better not use a Variant in a user-defined type. This time we look at functions that return a user-defined type. Let's look at a function - called MakeRect - that creates and returns a RECT (a built-in type). Here is non-optimized, but often used, version:

Dim rc As RECT
rc = MakeRect(0, 0, 100, 200)
Function MakeRect(ByVal x%, ByVal y%, ByVal w%, ByVal h%) As RECT
  Local rc As RECT
  rc.Left = x%
  rc.Top = y%
  rc.Right = x% + w%
  rc.Bottom = y% + h%
  Return rc
EndFunc

The function declares a temporary local variable rc and fills it's members with the passed values. Before the function ends the Return statement returns the local rc RECT variable to the caller. What actually happens is: the Return statement copies the contents of rc to a special local variable before it exits the function. Functions use a temporary local variable with the same name and type as the function declaration. In this case, the local variable is called MakeRect. Normally, the contents of the function-local variable is returned through the stack, meaning it is copied from the function-local variable to the stack.

In short, a Return value statement first copies the value to the function-local variable, it is then copied to the stack, and then the function returns. A two step process. Therefor, it is advised not to use the Return statement, but instead assign the return value to the function-local variable directly. When we change MakeRect function, it looks like this:

Function MakeRect(ByVal x%, ByVal y%, ByVal w%, ByVal h%) As RECT
  MakeRect.Left = x%
  MakeRect.Top = y%
  MakeRect.Right = x% + w%
  MakeRect.Bottom = y% + h%
EndFunc

As you can see, there is no local variable rc anymore, and no Return statement. The function is optimized for speed (and size, because there is less copying to do).

Last, but not least, GFA-BASIC 32 has another performance optimization. In case of UDT return values, GB32 passes the UDT variable as a byref parameter to the function. So, MakeRect actually receives 5 parameters. When GB32 creates the function-local variable MakeRect, it sets a reference to the hidden byref UDT parameter. So, when assigning values to the MakeRect function-local variable, we are writing directly to the global variable rc that is secretly passed to the function.

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.