14 November 2011

GFA-BASIC 16 Bit Revival

The Frenchman Michel Goux has assembled all tools for a proper installation of GFA-BASIC for Windows 16 Bit (also known as Windows 3.1). All tools like IDE, Compiler, Linker, ExtTool, etc are to be downloaded into one new package. The package also includes tips for running the 16-bits version on more recent OS's like Vista-32 and Windows 7-32.

Unfortunately, it will not run on 64-bit systems? Maybe some have a solution for that? 

Go to the Michel's GfaWin16 download page.

You can also check-out my GFA-BASIC 16 page for downloading additional stuff.

08 November 2011

Apply XP Visual Styles

 

Running GFA-BASIC 32 under XP or better (Vista/7) it applies the Windows XP Visual Styles. The controls are drawn using a new look using a newer common control library. The IDE uses the newer look because of the presence of manifest file in the same directory as the GfaWin32.exe. When you create an EXE for your own GFA-BASIC application you must not forget to include a manifest file using the same name as your application.

Go to the CodeProject site for a brief introduction on Applying Windows XP Visual Styles.

For an implementation of a GFA-BASIC 32 project go to Peter Heinzig site for an example http://www.peterheinzig.de

02 November 2011

Dim ... As New Interface?

What does New mean when used in a declaration statement? Why is New limited to only 8 so called OCX types? What are OCX types anyway? Confused? Let us continue the story on COM.

OCX Objects
By now you know that in GFA-BASIC 32 OCX windows are, in fact, normal  windows and controls wrapped up in a COM Object. For instance, the very familiar OpenW # command still creates an overlapped application window, but it now also creates a COM wrapper of data type Form. Not only creates OpenW a window using the CreateWindowEx() API, but also allocates an additional block of memory  (using malloc) to store all COM related information for the Form object type.

A GFA-BASIC OCX object is a window wrapped in a COM object. An OCX object requires a window handle.

Information about the OCX 
An OCX object is also called an ActiveX control (hence the window handle). GFA-BASIC OCX objects must behave as proper ActiveX objects as described in the OLE/COM documentation. (Actually, OLE is an old name. On the way it changed to COM.)
To use/control a COM object it must somewhere describe (publish) its functions, called properties and methods. This is done by including a type library with a COM object. The type library also specifies whether the COM object supports the creation of an instance of that object. This literally means that the COM object provides a class (function) doing a malloc() to allocate the memory to store the object.

A type library specifies the properties and methods of a COM object (interface), and whether it can be created on behalf of the client (coclass).

Declaring a COM Object
If you have ever used OleView.exe to examine GFA-BASIC's COM objects, you may have noticed the term interface. The interface specifies the name of the COM type and the functions used to access it. The interface name always starts with a capital I - like IForm, ICollection, etc. When used in code the capital I is lost (VB convention, duplicated by GB32).

All the interfaces defined in the type library from the GFA-BASIC Ocx runtime DLL  are added to the GFA-BASIC 32 internal list of data types. (This list is accessible through the Gfa_Types collection.) The list of data types contains all primary types like Bool, Byte, Card, Int32, Long, Currency, String, etc. All names stored in the type-list can be used in a declaration command like Dim, Global, and Local.

There is small problem though. GFA-BASIC 32 doesn't add the COM types by loading them from the type library. The addition of COM types like Collection, DisAsm, TextBox, etc. is hard-coded into the IDE code. Unfortunately! Otherwise we could have easily added third-party COM references to our applications.

To understand how GFA-BASIC handles a declared COM data type we can compare a COM object type to a String data type. For instance

Dim MyName As String
Dim frm As Form

These declarations don't do much. MyName and frm are variable-names and both are an alias for a 4-byte memory location. Initially, the value stored at the addresses is zero (pointer is Null), meaning no additional memory has been allocated for these variables. The pointers remain Null until something is assigned to the variables. Because MyName is of type String, GFA-BASIC knows how to do that and how to interpret the pointer at the location MyName.

The same is true for COM data types. Declaring a COM data type does not create the memory for it. The frm variable references some memory-address, but it does not actually create a window on the screen. First the window must be created and then the variable can be assigned an object of type Form. For COM objects a special command is invented to assign one COM object to another Set:

OpenW 1
Set frm = Win_1

Most GFA-BASIC 32 COM objects are created implicitly. OpenW creates a Form object, OCX TextBox creates a TextBox object, and so on. In addition, OCX controls that require multiple subitems create collections. A ToolBar OCX creates a Buttons collection of Button items.
For implicitly created objects GFA-BASIC 32 is responsible for memory allocation, initialization, and releasing memory.

Some COM types are created explicitly when some data is loaded. The LoadCursor() function creates a MouseCursor object and LoadPicture() creates a Picture object. These functions can be seen as 'a kind of New'.

The New keyword
The New keyword is used with some COM types that are available at your request. These types are independent of any OCX type. The New keyword allows you to create an instance of the object while declaring it. In GFA-BASIC 32 there are only 8 of these COM types allowed.

Dim f   As New Font       ' IFont2
Dim sf  As New StdFont    ' IStdFont
Dim p   As New Picture    ' IPicture2
Dim sp  As New StdPicture ' IStdPicture
Dim cd  As New CommDlg    
Dim col As New Collection
Dim dis As New DisAsm
Dim iml As New ImageList
  • Font (IFont2) and Picture (IPicture2) are GFA-BASIC implementations of the standard COM types.
  • StdFont and StdPicture are the types from the standard OLE libraries. Font and StdFont are 'compatible'. A variable of type Font can be set to an instance of StdFont and vice versa. The same is true for Picture and StdPicture.
  • The CommDlg and ImageList objects can be created using the OCX command as well.
  • Once the object created with New goes out of scope the object is automatically released.
  • Objects can be released explicitly by setting the variable to Nothing.

Other objects are single-instance objects created at runtime. GFA-BASIC 32 prohibits the creation of additional, new objects of these types. There is only one instance of App, Screen, Printer, ClipBoard, Code4, Debug, and Err. The single-instance objects are released when the program is terminated.

23 October 2011

Cleanup after an exception

In the previous post Don't use END to terminate your application I discussed why End messes up the memory available to an application. The same is true for Assert and Stop (when you use to terminate the application).

Main exception handler
These commands raise an exception caught by the nearest exception handler. This might be a custom Try/Catch handler in your program, but most often it passed on to the applications main exception handler. The main part of a GB32 application is surrounded with a GB32 generated application-exception-handler that releases file handles and memory associated with GB32 data types. The OCX forms/controls are not part of the cleaning process. To properly release all COM object memory all forms have to be closed by an internal function Destroy_Form(). The forms in turn are responsible for releasing the OCX objects of the child windows (controls).

Custom handler
When you use a custom structured exception handling (Try/Catch) in your application the exceptions might be caught by your application's defined custom handler. Now the exception is not passed on to the main application-handler and no memory and file handles are released. When you are put in this situation, you can select the clean-up command from the IDE's menu to close all forms and file handles.

Cleanup opened Forms and files
GB32 includes a library function that can cleanup non-closed forms and files after an application terminates abruptly. The DLL-function is located in the runtime and is exported by ordinal #49. The function is automatically invoked when you RUN a program inside the IDE. But you can also invoke it by selecting 'Clean up' (shortcut Ctrl-L) from the IDE's menubar. The menuitem  executes the Gfa_CleanUp command that invokes the DLL cleanup function.

Why would you use Gfa_CleanUp when the same function is executed at the next RUN of your code? Well, programs that are terminated improperly (END, ASSERT, or any exception the program causes) leave with invalid memory-pointers and Gfa_CleanUp might fail (mostly likely it will).

Gfa_CleanUp executes within a exception handler itself and it first calls DLL function #49 to close the forms. If it returns properly it calls exported function #119 which cleans the file handlers. In short, this is what Gfa_CleanUp looks like:

Try
  G49_CleanUpForms()
  G119_CleanUpFiles()
Catch
EndCatch

When the G49_CleanUpForms() DLL function causes an exception the G119_CleanUpFiles() is never executed!

The G49_CleanUpForms() simply enumerates over the Forms collection to call each Destroy_Form() internal function. This function is responsible for deleting form-specific COM objects for Fonts, MouseCursors, Pictures, Icons, AutoRedraw bitmaps, Menus, controls, graphic settings, end other related COM objects. This is a very large function responsible for properly clearing (in the right order) all created COM objects. When something didn't work correctly in your application and it was terminated in an unstable state, it is most likely this function might fail. This might leave behind all kinds of allocated COM memory. In addition, the function might have generated an exception, most likely of an invalid memory pointer, and from the pseudo code above you then see that the file handles aren't released at all.

In conclusion,

  1. When you set up a new application use a proper message loop and try to created one main-form that owns other forms (set the Form.Owned property for the second and later forms in the Properties window). Closing the main-form will close all other forms.
  2. Don't use END and be careful with ASSERT. When an application terminates improperly try using invoke Gfa_CleanUp.
  3. Fix an exception error and make sure it won't happen again. Then restart the IDE to get rid of memory left-overs.

16 October 2011

Don't use END to terminate your application

Using the End statement is like waking up from a nightmare. After multiple 'runs' in the IDE the GFA-BASIC 32 IDE might stop working at all and crashes eventually. End does not behave as it did in previous versions of GFA-BASIC where it was safe to use it as the application termination command. In GB32 you should never use End to terminate a program. Here is why.

End is defined as to terminate the execution of an application immediately. End is never required by itself but may be placed anywhere in a procedure to end code execution, close files opened with the Open statement and to clear variables.

The End statement stops code execution abruptly, without invoking the any window events, or any other code. Code you have placed in the form_Destroy, form_Close, etc, is not executed. GB32 data types referencing allocated memory at runtime (strings, arrays, hash tables) are properly destroyed, and files opened using the Open statement are closed. Object references held by other programs are invalidated.

The End statement provides a way to force your program to halt. For normal termination of a GB32 program, you should close all forms. Then when you use the 'Do : Sleep : Until Me Is Nothing' loop your program closes as soon as there are no more forms open. This is the only proper way to run a program!

Note The same is true for Quit, Assert, and Stop. These commands do not terminate your application properly.

It is an exception dude
For the curious. When GB32 encounters the End, Assert, and Stop commands GB32 it inserts code that raises an exception event: GB32 inserts a call to the RaiseException() API. An “exception” is an event that is unexpected or disrupts the ability of the process to proceed normally. However, GB32 supports structured exception handling (Try/Catch) the exception is caught by the nearest exception handler. In most cases, and certainly when End is used to terminate an application, the exception is caught by GB32's main-program exception handler. This handler is implicitly inserted around the code in the main program part of a GB32 program. Each unhandled exception is eventually handled by this main exception handler. The handler does nothing more than clearing variables and closing file handles.

So, when GB32 suddenly stops working, make sure your code let the OCX forms and controls properly close their windows in a natural order.

Also Known As bugs
NOTE – Don't blame GB32 too soon! Their are little or no bugs in the memory handling of GB32. Without pointing any fingers..., it turns out that most GB32 blamers are frustrated programmers that can't get their program(s) to work. Often the program structure is wrong or the programmer is under-qualified. This then leads to mysterious mistakes, AKA GB32 bugs.

05 September 2011

CreateObject peculiarities

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

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

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

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

Syntax: Set object = CreateObject(class)

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

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

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

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

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

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

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

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

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

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

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

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

Set TLInfo = CreateObject("TLI.TypeLibInfo")

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

15 July 2011

COM (OOP) in GB32 - IUnknown (1)

In this part I'll show you the layout of a COM class as it could be implemented in GFA-BASIC 32. Note that we restrict ourselves to a minimalist COM object that supports the IUnknown interface only. We will not yet create properties and methods.
The GFA-BASIC 32 approach is loosely based on the article series COM in plain C by Jeff Glatt, specifically Part 1 and Part 2. However, in the first step we are not going to use type libraries, we are not going to store the COM object in a DLL, and we are not going to define another COM object that creates the one we define. COM is merely a binary standard; it dictates how to layout an array of function pointers and where to put the address of this array (of function pointers). It also dictates how to add reference counting. To comply to this standard a COM object must at least contain three pointers to pre-defined functions, also known as the IUnknown interface. In GFA-BASIC 32 this could look like this:
// IUnknownBlog.g32 17.07.2011
Debug.Show

// Our COM object: an implementation of IUnknown
GUID IID_IUnknownImpl = 9578fdab-97cb-4322-99e4-699abd26be1d
Type IUnknownImpl
  lpVtbl As Pointer IUnknownImplVtbl
  RefCount As Int
EndType

// VTABLE (an array of function pointers)
Type IUnknownImplVtbl
  QueryInterface As Long
  AddRef As Long
  Release As Long
EndType
Static IUnknownImplVtbl As IUnknownImplVtbl
With IUnknownImplVtbl
  .QueryInterface = ProcAddr(IUnknownImplVtbl_QueryInterface)
  .AddRef = ProcAddr(IUnknownImplVtbl_AddRef)
  .Release = ProcAddr(IUnknownImplVtbl_Release)
EndWith

// First create a heap allocated instance
// of our implementation of IUnknownImpl
Dim pIUnk As Pointer IUnknownImpl
Pointer pIUnk        = mAlloc(SizeOf(IUnknownImpl))
Pointer pIUnk.lpVtbl = *IUnknownImplVtbl
pIUnk.RefCount = 1

Dim obIUnk As Object
{V:obIUnk} = V:pIUnk
Trace obIUnk


Dim o As Object // assign to other
Set o = obIUnk  // AddRef() call
// two calls to Release
/*** END ***/

GUID IID_IUnknown      = 00000000-0000-0000-c000-000000000046
Global Const E_NOTIMPL = 0x80004001
Global Const E_NOINTERFACE = 0x80004002
Declare Function IsEqualGUID Lib "ole32" (ByVal prguid1 As Long, ByVal prguid2 As Long) As Bool

Function IUnknownImplVtbl_QueryInterface( _
  ByRef this As IUnknownImpl, riid%, ppv%) As Long Naked
  Trace Hex(*this)
  {ppv} = Null
  If IsEqualGUID(riid, IID_IUnknownImpl) || _
    IsEqualGUID(riid, IID_IUnknown)
    {ppv} = *this
    IUnknownImplVtbl_AddRef(this)
    Return S_OK
  EndIf
  Return E_NOINTERFACE
EndFunc

Function IUnknownImplVtbl_AddRef(ByRef this As IUnknownImpl) As Long Naked
  Trace Hex(*this)
  this.RefCount++
  Return this.RefCount
EndFunc

Function IUnknownImplVtbl_Release(ByRef this As IUnknownImpl) As Long Naked
  Trace Hex(*this)
  this.RefCount--
  If this.RefCount == 0 Then ~mFree(*this)
  Return this.RefCount
EndFunc
Copy it to a new GFA-BASIC 32 application and save as IUnknownBlog.g32. Try to run it; it should compile and run flawlessly. Our first minimalist COM object/class has been defined and is up and running. Of course it doesn't do anything, but the we have an object that integrates with GFA-BASIC 32 and fully complies to the COM binary standard.
Assign to Object
Let us take a brief look at the code. Since this project isn't meant for the beginner, I'll walk you through it in big steps.
The Object data type holds a pointer to a piece of memory of at least 4 (lpVtbl) bytes. Mostly it contains another integer for a reference count. In GFA-BASIC 32 the Ocx variables always define their count in the second slot, we will use that as well.
To put a memory address in an Object variable we usually use Set obj2 = obj2. GFA-BASIC 32 checks for two proper COM object types at compile-time. Since we have mAlloc-ed address only this syntax wouldn't work. We must write it to Object directly. For the same reason we set the RefCount to 1 by hand.
The array of functions (VTABLE) is stored in a Type and shared with all instances of our custom COM object. Therefor a static (global) variable is used and initialized once. Each new COM object should hold the vtable-address in its lpVtbl member.
_QueryInterface, _AddRef, and _Release
The application COM functions _QueryInterface, _AddRef, and _Release are never called directly, but always by GFA or another COM object. They clutter up our application code and are in fact just boiler plate code. We must do something about that, for instance put them in a $Library. Also note the Naked attribute. These functions are never executed in the context of the application and need no TRACE code and Try/Catch-exception handler. They should be as naked as possible to gain the best performance.
The Trace *this commands in the code are to verify the address passed. Also note the type of the this pointer. COM passes the address of the COM object by reference and by adding the correct type into the function declaration we can access its members directly. All other parameters are simple placeholders for addresses we don't use or pass on.
In the next part we will implement an IDispatch object and try to integrate the COM code more into GFA-BASIC 32.

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

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

21 April 2011

CommDlg.ShowFolders

The ShowFolders method of the CommDlg Ocx isn't documented properly. The method is included to provide an easy way to display the Shell's SHBrowseForFolders dialog box allowing the user to select a folder rather than a file. It is - of course - not part of the the Common Dialog Box Library and should  have been implemented as a separately function. Or shouldn't it?

The FileName and Title properties
The CommDlg.ShowFolders(Optional flag As Variant) method shares two properties with other CommDlg methods that show dialog boxes: FileName and Title. In case these are used with ShowFolders their meaning is as follows:

- CommDlg.Title  (R/W)
Sets the string that is displayed above the tree view control in the dialog box. This string can be used to specify instructions to the user. Returns the same value as was put into it.

- CommDlg.FileName  (R/W)
Sets the browse dialog box's initial selection folder (has nothing to do with filenames!). Note, by default, the SHBrowseForFolder API lets the user start at the desktop to browse the shell's namespace and pick a folder. GFA-BASIC 32 overrides this behavior by starting the browse dialog box at the folder specified in the .FileName property. To let the dialog box start at the desktop set the FileName property to an empty string. To bring up the browse dialog box with the current directory selected, set FileName = CurDir.

After closing the ShowFolders dialog box the FileName property contains the name of the selected folder. That is if you select the Ok button; after Cancel the FileName is undefined.

CommDlg code was/is buggy
The GFA-BASIC 32 library code that implements CommDlg Ocx and specifically the Show Folder function, has always been buggy and I've been fixing it many times in the past. I was confident I fixed all bugs in Build 1169. At the time of writing this blog I tried it again and I was shocked to see that it still has a bug. When you press the browse dialog box's Cancel button the FileName property is undefined and present an Access Violation error. I will fix it as soon as possible and release Build 1170, which also contains fixes for the Dlg Open/Save commands.

The optional parameter
The ShowFolders(flag) method accepts one optional argument that specifies the options for the dialog box. When you omit the argument, the default behavior is BIF_RETURNONLYFSDIRS. The flag can include a combination of the following values:

BIF_BROWSEFORCOMPUTER (0x1000) Only return computers. If the user selects anything other than a computer, the OK button is grayed.
BIF_BROWSEFORPRINTER (0x2000) Only return printers. If the user selects anything other than a printer, the OK button is grayed.
BIF_BROWSEINCLUDEFILES (0x4000) The browse dialog will display files as well as folders.
BIF_DONTGOBELOWDOMAIN (0x2) Do not include network folders below the domain level in the tree view control.
BIF_EDITBOX (0x10) Version 4.71. The browse dialog includes an edit control in which the user can type the name of an item.
BIF_RETURNFSANCESTORS (0x08) Only return file system ancestors. If the user selects anything other than a file system ancestor, the OK button is grayed.
BIF_RETURNONLYFSDIRS (0x01) Only return file system directories. If the user selects folders that are not part of the file system, the OK button is grayed.
BIF_STATUSTEXT (0x04) Include a status area in the dialog box. The callback function can set the status text by sending messages to the dialog box.
BIF_VALIDATE (0x20) Version 4.71. If the user types an invalid name into the edit box, the browse dialog will call the application's BrowseCallBackproc with the BFFM_VALIDATEFAILED message. This flag is ignored if BIF_EDITBOX is not specified.

The BrowseCallbackProc
Although the ShowFolders method will pass most of the flags without questions asked, it doesn't honor them all. Some of the flags require an application-defined callback function to handle application defined behavior. The underlying SHBrowseForFolder API function calls BrowseCallbackProc to notify it about events. The address of the callback function is passed in the BROWSEINFO structure that contains information used to display the dialog box. The GFA-BASIC 32 ShowFolders method uses the callback proc to select the folder set with CommDlg.FileName in response of the BFFM_INITIALIZED message. GFA-BASIC 32 does not provide a way to interact with its BrowseCallbackProc, making ShowFolders a simple folder selection mechanism.

10 April 2011

An Array of Pointer To

The "Pointer To" data type isn't a key feature of (GFA-)BASIC. A BASIC programmer should not be bothered with pointers, at least that is the consensus. If you like to know more about pointers in GFA-BASIC check out ByRef arguments are Pointer To variables.

Introduction
One of the many variants of "Pointer To" I never considered; a declaration of an array of type "As Pointer To type" . For example, what does this do?

Dim a(1) As Pointer Large

It turns out that (with Option Base 0) the array contains two elements (0,1) of int32 data type. Two Longs are allocated and not two Large variables (int64). The same is true of all other types. Due to the Pointer keyword the elements are only 32-bit wide and are initialized to Null.

Code
This time I don't present a smoking explanation but sample code only. It contains 3 examples of 3 different array types, but they are all of Pointer To. See what happens.

' Essentially, it is an array of 32-bit integers
' to hold addresses of memory locations.
' The array's data type determines how to use
' the address stored in the elements.

' Example 1 - Array as a Pointer To Large

Dim a(1) As Pointer Large
Trace ArraySize(a())    ' 8 bytes, thus 2 * 4 bytes

' Assign memory locations to the elements:
Dim il As Large = 9, jl As Large = 10
Pointer(a(0)) = V:il     ' set array-elem to address of i
Pointer(a(1)) = V:jl

' Due to "As Pointer To Large" GFA performs
' an indirect access using the addresses stored in
' array elements. It does NOT directly return the
' values stored in a(0) or a(1) (which are memory locations).
Trace a(0)              ' = 9
Trace a(1)              ' = 10

' How does GFA do this?
' Well, let us see what is actually stored in a(0) and a(1).
' We use ArrayAddr() to obtain the addresses  of the array elements and then Lpeek the address stored there and then use Large{adr} to obtain the value.
Dim adr As Int
adr = Int{ArrayAddr(a()) + 0}   ' a(0)
Trace Large{adr}                ' = 9
adr = Int{ArrayAddr(a()) + 4}   ' a(1)
Trace Large{adr}                ' = 10

' Example 2 - Array as a Pointer To String
' Again an array of 32-bit integers, but now they must
' be set to point to the string's descriptor.
Dim s(1) As Pointer String, st As String = "Hello"
Trace ArraySize(s())    ' still 8 bytes
Pointer(s(0)) = *st     ' assign string descriptors
Pointer(s(1)) = *st     ' assign string descriptors

' See how GFA correctly uses the addresses to
' obtain the strings:
Trace s(0)              ' = Hello
Trace s(1)              ' = Hello

' Internally handled as:
Trace Char{{{ArrayAddr(s()) + 0 }}}
Trace Char{{{ArrayAddr(s()) + 4 }}}


' Example 3 - Array as a Pointer To UDT
' Again an array of 32-bit integers, but now they must
' be set to point to instances of ArrayDesc type.
Dim ad(1) As Pointer ArrayDesc
Trace ArraySize(ad())    ' still 8 bytes

' Use ArrayDesc of previously declared arrays
Pointer(ad(0)) = *a()    ' assign addr of ArrayDesc a()
Pointer(ad(1)) = *s()    ' assign addr of ArrayDesc s()

' See how GFA correctly uses the addresses
Trace ad(0).ptype              ' 37 ($24 + $1)
Trace ad(1).ptype              ' 73 ($48 + $1)

' Internally handled as:
Trace Int{{ArrayAddr(ad()) + 0 } + 4}
Trace Int{{ArrayAddr(ad()) + 4 } + 4}

Type ArrayDesc
  -Int    Magic         ' 4 Byte ASCII
  -Int    ptype         ' vtType
  -Int    size          ' size of the datatype
  -Int    dimCnt        ' number of dimensions
  -Int    dimCnt2       ' Erase and $ArrayChk
  -Int    paddr         ' ArrayAddr()
  -Int    corr          ' correction value
  -Int    paddrCorr     ' void*addrCorr;
  -Int    anzElem       ' number of elements
  -Int    sizeArr       ' size in bytes ArraySize()
  -Int    Idx(3)  '[3][1];      //Open Array
EndType

Note - A declaration of an array creates an array descriptor which the array variable references. Operations on the variable are performed using the content of the descriptor. This is not about the array descriptor, but it illustrates what is allocated with Dim. The UDT ArrayDesc is used as an example for an array of Pointer To UDT.

Conclusion
GFA-BASIC 32 returns the content of array elements correctly, but this only works when the array elements are assigned the addresses of memory locations that need to be read using the arrays data type.

09 April 2011

Status of GFA-BASIC 32

It has been a while, but I certainly didn't give up on GFA-BASIC 32. In fact, I've done some fundamental research this winter to figure out a (new) direction for GFA-BASIC 32.

COM is a must
Each new Windows OS provides more COM based interfaces, rather than new DLL function APIs. A modern language must support the use of COM interfaces, and I've done a lot of research in that area. The first step in the process should be the use of non-ActiveX COM libraries. This requires adding COM references to a GFA-BASIC 32 project. Once this is completed, the language should have the ability to provide COM classes as well. So, in the next stage, GFA-BASIC 32 must support object-oriented structures like VB's Class/End Class. Finally, research must be done to evaluate the opportunity to add third party ActiveX controls.

Maintaining GFA-BASIC
This is not the only thing that is on my mind. I often get questions or receive bug reports that require a lot of time to investigate. I only have a heavily commented disassembly, I still don't understand all of it. Once I found a bug or improvement it needs to be implemented. Well, patching is an art in itself and often can be done in multiple ways. Sometimes I need to acquaint myself with entirely new ways of hacking. This costs too much time, so I need to figure out how to proceed with that. My first priority is to implement COM support.

Assemble in GFA-BASIC?
But, only recently (!) I realized I could maybe copy the disassembly to GFA-BASIC 32 and use GFA-BASIC's assembler to re-create the executable (GfaWin32,exe). I made some trials and it might actually work! That would mean I could edit the (assembly) code of the IDE directly inside GFA and then recompile (F5). Wow, maybe I give this my number one priority ...

Next blog entry
One of the questions I received contained a statement I wasn't familiar with. The code contained a declaration of an array of type "Pointer To Int". I had no idea what that meant. Do you? If not check out the next blog.