24 May 2011

Passing a Hash to a procedure (III)–Workaround

This part three in a series about the GFA-BASIC 32 Hash data type. In this part I discuss a workaround to use Hash procedure parameters and some peculiarities of the hash data type. However, you might like to read the previous posts first.
The workaround
A compiler bug blocks the the Hash Xxx commands for a Hash data type passed by-reference. On the other hand, the hash [ ] - operator syntax works well. The Hash Xxx commands work correctly for a local Hash data type, the one that is not passed as an argument to the procedure. The logical conclusion is to declare and use a temporary local Hash of the same data type and then somehow 'assign the hash parameter to the local hash'.
GFA-BASIC 32 has a two constructions to do just that:
  • Declaring a variable p As Pointer To data-type and than use Pointer(p) = address command to assign the pointer (<=> variable) an address.
  • Swapping the descriptors of the Hash variables: Swap hs1, hs2.
We can skip the first option. It should work but it doesn't, the compiler refuses the statement:
Dim hs As Pointer Hash Int
The swap method should work as well, but again it doesn't. The compiler doesn't know what to do with Swapping two Hash variables. However there is light at the end of the tunnel, Swap works for all other GFA-BASIC 32 data types including the UDT (Type). And that's the one we are going to use to Swap the hash variables, hence the procedure SwapHash().
Local hsInt As Hash Int
hashtest(HB[])

Proc hashtest(ByRef HS As Hash Int)
  Dim ths As Hash Int   ' temporary Hash
  SwapHash( *HS, *ths ) ' swap descriptors

  ' Now ONLY use ths[]
  Hash Add ths["dd"], 7
  ths ["aa"] = 8
  ' ...

  SwapHash( *HS, *ths ) ' swap back
EndProc

Proc SwapHash(ptrHash1%, ptrHash2%) Naked
  Local hsdesc1 As Pointer HashDesc
  Pointer(hsdesc1) = ptrHash1
  Local hsdesc2 As Pointer HashDesc
  Pointer(hsdesc2) = ptrHash2
  ' Make sure the types are the same:
  Assert hsdesc1.pType == hsdesc2.pType _
    ' SwapHash() - Hash type mismatch

  Swap hsdesc1, hsdesc2

  Type HashDesc         ' The Hash Descriptor
    pHashData As Long   ' pointer to memory
    pType As Long       ' Hash data type
  EndType
EndProc

19 May 2011

Passing a Hash to a subroutine (II) - The Descriptor

In the previous post we discussed the GFA-BASIC 32 compiler bug when a Hash variable is passed to a subroutine. The compiler produces incorrect code for the Hash Xxx commands, but it works well in case hash-operators are used.
The situation is like this:
Sub q(hs As Hash String)
  ' Errorneous:
  Hash Add hs["mykey"], "Entry1"
  Hash Add hs[2], "Entry2"
  ' OK:
  hs[3] = "Entry3"
  Print hs[%]
EndSub
Note that the Hash variable hs is passed by-reference (implicitly due to the use of the keyword Sub).

Hash descriptor
A Hash variable is actually a 8-byte structure (user-defined type) containing two LONG integers. The Hash variable references the address of this hash-type, called the descriptor. The Hash-descriptor is defined as follows:
Type HashDesc
  pHashData As Long
  pType As Long
EndType
After declaring a hash variable (Dim hs As Hash type) the first long integer of the descriptor is Null indicating that the hash didn't allocate any memory. The second long contains a value indicating the GFA-BASIC 32 primary data type used to store values in the hash table. The data type could be Int, Byte, String, Variant, etc. Every GFA-BASIC 32 data type can be used, with the exception of UDT types. As an example the next code declares a Hash to store (long) Integer values and displays the descriptor:
Local HB As Hash Int
Local hs_desc As Pointer To HashDesc
Pointer(hs_desc) = *HB
Debug "HASH DESCRIPTOR"
Trace Hex(*hs_desc)
Trace Hex(hs_desc.pHashData)    ' = 0
Trace Hex(hs_desc.pType)        ' = $4018
The variable HB references the starting address of the 8-byte descriptor. As with any GFA-BASIC 32 variable that uses a descriptor, the address can be obtained using ArrPtr(HB) or *HB. Using the preceding code you can inspect the descriptor.

Hash MUST be declared as ByRef 
When a hash variable is passed to a subroutine, its descriptor address is always passed by reference (whether or not ByRef or ByVal is used in the procedure header). BUT, the Hash parameter MUST be ByRef, otherwise the code generated for the procedure will use a wrong address. A ByVal Hash parameter sets the compiler in error mode without returning to the editor. As a consequence all code following might (will actually) be compiled wrongly.
When the Hash parameter is passed correctly by-reference the compiler still generates incorrect code for the Hash Xxx commands, like Hash Add, Hash Remove,...
The advise remains the same, don't pass Hash tables around, but use them as global variables only.

18 May 2011

Hash passing to a subroutine (I) - The bug

As mentioned in the (English) help file the handling of a Hash type argument is erroneous. The compiler produces incorrect code when the local Hash parameter is used in Hash commands like Hash Add, Hash Remove, etc. As far as I know all Hash Xxx commands suffer from the compiler bug. In contradiction with this behavior is the handling of the Hash–operator code. When a Hash data type is passed to a subroutine the compiler produces correct code for all [ ] hash-operator code. The following code illustrates the situation:
Sub q(hs As Hash String)
  ' Errorneous:
  Hash Add hs["mykey"], "Entry1"
  Hash Add hs[2], "Entry2"
  ' OK:
  hs[3] = "Entry3"
  Print hs[%]
EndSub

What is happening?
Due to the implicit Sub by-reference passing the compiler puts the address of the Hash argument on the stack when invoking q( Hs[] ). This is the first step performed by the compiler when a call to q() must be produced and it turns out to be ok. The second step performed by the compiler is when the subroutine q() is compiled. Due to the by-ref passing the compiler must set the local hs variable to the address put on the stack by the code that invoked the call. That way the local variable hs points to the same data as the hash passed to q().
However, somehow the compiler messes up with the Hash Xxx commands. When inspecting the assembly code for q() it can be seen that for the hash-operator instructions the correct Hash address is passed to the runtime library functions. Also, it is clear that the compiler passes the incorrect address in case the Hash Xxx commands are used.

What is to be done?
At this moment this bug cannot be fixed, so we either need a workaround or we avoid the use of Hash variables as a subroutine argument at all. The second option isn't the worst of both, because due to their nature Hash tables are mostly used globally and don't need to be passed to subroutines at all. Of course, a hash table could be used to store temporary results (regular expression commands), but in that case the Hash is usually declared locally and not passed to any subroutine.
Advise: Don't pass Hash variables to subroutines.
In the next post I will discuss a workaround together with the layout of the GFA-BASIC 32 Hash data type.

14 May 2011

Visual Styles, Ocx Button, and ForeColor

The BUTTON window class is the base windows class for the Command, Frame, Option, and CheckBox Ocx controls. These different OCX controls are created by passing a special BUTTON window style (BS_) in the style parameter of the CreateWindowExA() Windows API.

GFABASIC 32 Ocx BUTTON window Style
Command BS_PUSHBUTTON, BS_DEFPUSHBUTTON
CheckBox BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE, and BS_AUTO3STATE
Option BS_RADIOBUTTON and BS_AUTORADIOBUTTON
Frame BS_GROUPBOX

As you can see the names of the Ocx controls conform to VB control names, which was one of the main changes in the GFA-BASIC 32 development. Another thing adapted from VB is the ability to change the controls behavior and appearance through properties. These are either changed in the Properties sidebar in the form-editor or in source code using the COM properties interfaces. In this case it doesn't matter how the appearance is changed, but how GFA-BASIC 32 implements the VB compatible behavior.

First of all, the implementation is VB compatible, but totally different. In most case (if not all) the GB32 implementation of properties and methods is much faster and cleaner. VB and VB.NET are snakes compared to the fast performance of GB32 applications. Currently, performance doesn't count as strong as in previous decades, but still. Maybe with Windows 7 for Tablets performance and size counts again. Who knows?

BUTTON drawing
The BUTTON control sends the WM_CTLCOLORBTN message to the parent window of a button when the button is about to be drawn. By responding to this message, the parent window can set a button's text and background colors. In GFA-BASIC 32 applications without the Visual Styles enabled this works as documented. However, when an application is accompanied with a manifest the new Visual Styles are applied and take precedence over the GFA-BASIC implementation.

When the Windows OS encounters a manifest file, the OS uses a different version of the common control library and globally subclasses all Windows controls, including the 'old standard' controls like the BUTTON class. GFA-BASIC 32 wasn't developed when the Visual Styles were around and uses a speedy drawing algorithm based on the classic behavior of the BUTTON class drawing. As a result, the Visual Styles improvement is not applied to the text color of the BUTTON class.

ForeColor cannot be set
Thus, the text color of Command, CheckBox, Option, and Frame Ocx cannot be changed by changing their ForeColor property when using a manifest file.
When you really need to change the text color I'm not sure how to proceed, so you are your own there...

10 May 2011

Update Runtime Build 1170

Finally the latest update of the runtime is available. Just click the Download tab at the top of the site.

What is a runtime?

The runtime is a DLL necessary to execute both GFA-BASIC 32 IDE as well as your compiled applications. The runtime is called GfaWin23.Ocx and is located in the Bin directory of GfaWin32.Exe (the IDE/Compiler). The runtime is installed in the same directory as the IDE because the Windows operating system will search the DLL in the same folder as the application. Often you will put a copy of the runtime Ocx next to your compiled application in the same directory. That way you can make sure the correct DLL is loaded.

When you download a newer runtime, like the build 1170, you must replace the GfaWin23.Ocx in your GFA-BASIC 32 directory so that the IDE will start with the newer library. In addition, when you want to use fixed commands and functions in your compiled applications as well, you should replace other copies of the GfaWin23.Ocx as well.

Maybe the GFA-BASIC 32 DLL has an unexpected extension .ocx rather than .dll. Don't let this confuse you. An ocx file is just a dll containing ActiveX controls (OCX-controls) next to other library functions. The GFA-BASIC 32 runtime code for the OCX takes about 500 KByte of the total size. The rest is just a bunch of DLL functions.

Hope this helps ...

ShowFolders Sample

The ShowFolders method of the CommDlg Ocx object keeps raising questions. Therefor an example of an extended browse for folders dialog box.

The API behind the dialog box of the ShowFolders method is the SHBrowseForFolder() Shell function. Actually, there are two styles of dialog box available. The older style is displayed by default and is displayed using the BIF_ constants specified in the previous blog CommDlg.ShowFolders. However, the list of constants is a subset of the total number of flags. To specify a dialog box using the newer style, you should pass the BIF_USENEWUI flag in the ShowFolders method.

image

The newer style provides a number of additional features, including drag-and-drop capability within the dialog box, reordering, deletion, shortcut menus, the ability to create new folders, and other shortcut menu commands. Initially, it is larger than the older dialog box, but can be resized by the user.
The dialog box can be displayed using the following code.

Public Const BIF_RETURNONLYFSDIRS   = 0x0001
Public Const BIF_DONTGOBELOWDOMAIN  = 0x0002
Public Const BIF_STATUSTEXT         = 0x0004
Public Const BIF_RETURNFSANCESTORS  = 0x0008
Public Const BIF_EDITBOX            = 0x0010
Public Const BIF_VALIDATE           = 0x0020
Public Const BIF_NEWDIALOGSTYLE     = 0x0040
Public Const BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX)
Public Const BIF_NONEWFOLDERBUTTON  = 0x0200
Public Const BIF_BROWSEFORCOMPUTER  = 0x1000
Public Const BIF_BROWSEFORPRINTER   = 0x2000
Public Const BIF_BROWSEINCLUDEFILES = 0x4000

Global cd As New CommDlg
cd.Title = "GFA-BASIC32 ShowFolders Demo"
cd.ShowFolders BIF_USENEWUI
If Len(cd.FileName) Then _
  MsgBox "Folder Selected: " & cd.FileName