13 May 2010

DefMouse

DefMouse is an hold over from previous GFA-BASIC versions, but is still – with a little twist - supported in GFA-BASIC 32. In GFA-BASIC 16 bit DefMouse was used to set the mouse shape for the entire screen. DefMouse changed the mouse globally, for each GB window and control, as well as for the desktop. With GB32 the mouse-cursor shape is set for each OCX object individually, which is much more flexible. Now, the general way of setting a mouse-cursor is by setting the control's MousePointer, MouseCursor, or MouseIcon property, either at design-time or at runtime.

DefMouse 0 is the secret
GFA-BASIC 32 tries to be as compatible as can be with previous versions, so the DefMouse command had to be implemented as well. It works, however the region is now limited to GFA-BASIC windows (and controls) only. An improvement, wouldn't you agree? How does that work? How does GFABASIC prevent corrupting mouse pointers? The solution to the problem is the DefMouse value. When DefMouse == 0 (or basDefault) the individual mouse settings of the Forms and controls are used. However, when DefMouse n (where n <>0) is used to select a new mouse shape the individual OCX mouse settings are ignored. On the plus side, you are guaranteed with the new mouse shape over every(!) OCX control/form. To revert to the individual settings you must invoke DefMouse 0.

A Popup window
A Popup selection menu is a system window executed in the context of the GFA-BASIC 32 application. It isn't an Ocx object (it should have been!) and has no private mouse settings. Especially when a Popup is used from within a TrayIcon Ocx GFA-BASIC 32 has to revert to the DefMouse setting. However when DefMouse = 0 GFA-BASIC does nothing, because it expects to handle the individual Ocx mouse settings. The WM_SETCURSOR message isn't handled properly an a random cursor value is used to tell the system (Windows) which cursor to use. The mouse cursor is drawn incorrectly when showing the popup menu. Consequently, the global mouse must be set to a non-zero value before invoking Popup, for instance DefMouse basArrow. When Popup returns the global mouse setting must revert to zero: DefMouse 0.

Screen Ocx
The Screen Ocx supports the MousePointer, MouseCursor, or MouseIcon properties as well. These operate exactly the same as DefMouse, eg on the global mouse setting. Thus DefMouse can be replaced by Screen.MousePointer = n, where n is a basXX mouse value.
The DefMouse command can also be passed a string containing a bit-pattern to create a custom (monochrome) mouse.
The Screen.MouseCursor on the other hand accepts a MouseCursor object (a real Windows cursor resource in an external file). In addition, Screen.MouseIcon accepts a Picture object. So, there many different options to create a custom – global – mouse shape.

24 April 2010

Replace a Toolbar's button image

Back in 2000 a bug report was posted describing how GFA-BASIC 32 failed to properly remove a Toolbar Ocx button. I say properly, because the button was removed, but the button count remained the same. Now in 2010 another bug was reported. The button's image couldn't be easily replaced, because the button's property .Image isn't implemented. So, the Ocx Toolbar has some flaws ....

To address the new bug, we need to find a work-around. This is easy, we must invoke Toolbar.Remove index to remove the button and than Add it again with the new image index. But for this to work properly we also need to know why Toolbar.Remove index failed in the first place. Well, the cause for the failure was quickly found; the .Remove method failed because the button wasn't assigned a key. The Toolbar's Add method takes the following (optional) arguments. Each argument is a Variant, GFA-BASIC converts the argument to a proper data-type internally.

ToolBar.Add index, key, caption, style, image

index - defines the position of the button.
key – specifies a character string used to store and retrieve the button by name.
caption – defines the string to display next to the button.
style – specifies the type of button.
image – is a key specifying the image to use from the ImageList control.

ToolBar.Remove index requires the index (== position) of the button to remove. Subsequently, most of us don't specify a key for the button, we don't need it later, do we?
Well, as a matter of fact, GFA-BASIC 32 uses the key to remove the button from the underlying Buttons collection, if it isn't specified it cannot be removed. Despite the fact that Remove requires the index (or position) only, it requires the accompanying key to actually remove. So, add a key argument to ToolBar.Add and you are good.

Now we know how to remove a button we can insert a new one. To accomplish this we must use the index argument of Add to insert a new button with a new image. The sample code looks like this:

' Add button #1
tb.Add 1, "but1" , , 0, 1
' Replace button #1
Tb.Remove 1
Tb.Add 1, "but1" , , 0, 2

26 March 2010

A Dlg_ Form

Any form with a name staring with ‘Dlg_’ follows the dialog rules of GFA-BASIC 32. When a form is created using the Dialog #n command the form automatically gets a name starting with Dlg_0 .. Dlg_31, where 0 <= n <= 31. However, you are not restricted to the Dialog # command to create dialog-forms. Any form, either created at design time using the form-editor or at runtime, can ‘benefit’ from the dialog settings. The only requirement is a name starting with ‘Dlg_’. For instance Dlg_32, Dlg_InputName, Dlg_Msg, etc.

The most obvious change is the adjustment to the DlgBase settings. At the time of creation a form with a name staring with Dlg_ uses the DlgBase settings rather than specified settings. In addition, a Dlg_ form’s keyboard events are handled a bit differently. The PeekEvent command invokes a special GFA-BASIC IsDialogMessage() function to handle the navigation keys (Tab, arrow keys).

I only discovered this recently and didn’t get a chance to figure out how exactly the navigation differs from a normal Form.

Note that in contrast with the remark in the help-file DlgBase Bold has been repaired.

Sleep, DoEvents, PeekEvent, or GetEvent?

In contrast with VB a GB32 application needs a global message loop to retrieve pending messages from the application’s message queue. In GFA-BASIC 32 the message loop is most often placed in the ‘main’ section of the program and often looks like this:
Do
Sleep
Loop Until Me Is Nothing

Me is a Form object variable holding the current active window of the GFA-BASIC 32 program. The Me variable always represents the current active window should a program open multiple windows. Me always represents a window (Form) unless the last window is closed, then Me is equal to Nothing. That’s the moment the Do loop is ends and the program stops.

GFA-BASIC 32 offers 4 different commands to read the message loop and dispatch the message to the appropriate window procedure. Essentially, all these commands utilize PeekMessage(), TranslateMessage(), and DispatchMessage() - like any other message loop under Windows. They only slightly differ which I will discuss here. Let’s start with PeekEvent because it is the heart of all four commands.

PeekEvent invokes the PeekMessage(V:msg,0,0,0,PM_REMOVE) function, which checks a thread message queue for a message and places the message (if any) in the specified structure. If no messages are available, the return value is zero. The PM_REMOVE flag indicates that messages are removed from the queue after processing by PeekMessage. The following GB32 listing shows pseudo-code for the PeekEvent command.

Procedure PeekEvent()
Dim msg As MESSAGE
// Clear MENU() event array
MemZero(MENU(0), 17)
// Obtain message if available
If PeekMessage(V:msg, 0, 0, 0, PM_REMOVE)
// Copy msg members to MENU() array
MENU(2) = msg.pt.x
MENU(3) = msg.pt.y
MENU(16)= msg.time
// Handle key input messages
If msg.message >= WM_KEYFIRST && _
msg.message <= WM_KEYLAST
// Handle keyboard events at
// the application level.
Exit Sub If Screen_KeyPreview(V:msg)
// If Name starts with 'Dlg_' use
// special IsDialogMessage().
If Me.IsDialog Then
IsDlg_XX_Message(Me.hWnd, V:msg)
Else
~TranslateMessage(V:msg)
EndIf
EndIf
// Send to appropriate window proc
~DispatchMessage(V:msg)
EndIf    // PeekMessage()
EndProc

This pseudo code is rather accurate. I omitted the check for reentrance while executing PeekEvent. Only this, once PeekEvent is executing and is dispatching messages, it cannot be reentered again.

- GetEvent is a simple variation on PeekEvent. Like PeekEvent it only retrieves one message a time and then returns to the application, in fact GetEvent invokes PeekEvent as follows:

Procedure GetEvent()
If Not GetQueueStatus(QS_ALLEVENTS) Then _
~WaitMessage()
@PeekEvent()
EndProc

GetEvent waits for a message when - on entrance - no messages are current in the queue. When a message is available it calls PeekEvent to handle the message and then returns to the application.

-DoEvents is a VB compatible command (used seldom in VB because the message loop is hidden and executed automatically in VB programs). In GB32 it is implement as a loop calling PeekEvent until the message queue is empty.

Function DoEvents%()
Dim Count% = 0
While @PeekEvent()
Count% ++
Wend
~Sleep(Count%)
Return Count%
EndFunc

DoEvents yields execution for a small amount of time after retrieving all messages form the queue. It returns the number of messages obtained. The yielding time depends on the number of handled messages.

- Sleep starts with calling DoEvents. When DoEvents returns 0 then Sleep goes in a wait state and waits for new messages and when they arrive it executes DoEvents again.

Procedure Sleep()
If @DoEvents%() = 0 Then
~WaitMessage()
@DoEvents%()
EndIf
EndProc

Sleep is the preferred command for the main message loop of course.

12 February 2010

Kind messages

Every week I’m happily surprised with a kind message of someone stating his fun with GFA-BASIC 32. Some excerpts:

From a Dutch user:

“Dear Frank, I started my own company in 1982 in plastic things.
After a few years I bought a Atari 520. Later a 1024 and a 20 Mb!! harddisk.
I was interrested and bought a Bookkeeper software thing.
That thing was very slow, so I decided to write my own program.
GFA was the option and what kind of an option !!
I bought the manuals and compiler and didn't sleep many nights because I was concentraded, involved and full power.
I had to develop windows, cursor behavior, and I made myself because of all the stuff a professional bookkeeper.
I've used this program for many years and friends with a own company also, because it was able to do the depreciations automatically.
The taxman never had problems with it. It was clear and understandable.
I've used it until 2001 (with Gemulator) and after that I've sold my company.
Now there are such programs with the same functions of 60Mb, mine (yours) was 93K !!!!
Love you, Henk Reinders (Holland)”
From a German user:

“Hi Sjouke,

I am still alive, no time, hard working. I am now 62 years old and may gain more time for my hobbys in a few years. I started to built up a small homepage for private use only. By the way, I will send you some stuff, step by step, from earlier programming. The prog was coded by my son on a Atari 800XL, we had a lot of fun together with this machine. I presented the gfa32 version to my son on one of his birthdays. It has been very easy to transform the code. All the best for you and all gfa-fans in the future.”

Do you want to share something with the GFA-BASIC community? Leave a comment!

07 February 2010

GFA-BASIC Update Build 1166

The internal linker error caused a "File write error" when creating a stand-alone EXE or a Gfa Editor Extension GLL. The bug occured when one or more $Libraries are linked to the compiled code of the program currently editted. When none of the Lg32 libraries contained subroutines (Sub, Proc, or Func) the linker misses a pointer and reads from address NULL. The entire process of creating the output file is guarded in a Try-Catch exception handler that only reports a "File write error in filename". It doesn't specify what went wrong.
Anyway, this bug is solved in Build 1166, which you can download at http://gfabasic32.googlepages.com/update

03 February 2010

Debug.Print to OutputDebugString

To me the Debug Output Window is the most valuable tool of GFA-BASIC 32. The Debug Output Window is controlled (wrapped) by the COM interface IDebug. I always have the debug output window open just below the main window of the IDE.

There is only one instance of the Debug object (just like Printer, Me, Win_, Dlg_, Error, App, Screen, ClipBoard, Forms, and Code4). It is not up to you to create a Debug object. As soon as you use one of the properties or methods of the Debug object, the GfaWin23.Ocx runtime checks for its existence and creates the thread global object for you.

Both Trace and Print display text in the EDITTEXT control that occupies the client area of the Debug Output Window. The central routine responsible simply sends the WM_SETTEXT message to the control. The subclass procedure of this edit control checks for an overrun of the text limit.

In addition, the central text output routine invokes the OutputDebugString() API with the same text string. The OutputDebugString function sends a string to the debugger for the current application. However, GFA-BASIC 32 doesn’t have a Win32 application debugger as meant in this context. Therefore, if the application has no debugger, the system debugger displays the string. If the application has no debugger and the system debugger is not active, OutputDebugString does nothing.

However, when you are debugging GFA-BASIC 32 in a Win32 debugger (OllyDbg, IDA Pro Disassembler, etc) you’ll see the Debug.Print text in their output windows as well.

19 January 2010

ListView Subitems Custom Draw

Just before Christmas 2009 Peter Heinzig mailed me a copy of his "listView_CellColor" program. He was experimenting with Custom Draw for common controls to provide cell coloring for subitems in a ListView in report-mode. To be honest, I never used custom draw, so I had some catching up to do. After reading the SDK I checked the GB32 disassembly to check if GB32 uses custom draw. Bingo! It does use it with the ListView Ocx control to implement the ListItem's properties ForeColor, BackColor, and Font. Anyone trying to implement custom draw for a ListItem object could get in problems with GB32's implementation, so it is time to implement a custom draw routine for ListView controls taking the GB32 implementation into account.

We step right into the drawing stage of custom draw Ocx controls. I discussed the message handling of custom draw for Ocx controls in a previous entry Custom Draw for Ocx controls. Note this is GFABASIC 32 specific implementation, other languages might use a different method.

Using the correct Ocx type
We left off with the function that invokes a specific subroutine when the NM_CUSTOMDRAW message has come from a ListView, TreeView, ToolBar, or Slider Ocx control. This function, which I called OcxCustomDraw() to emphasize we are processing GB32-Ocx specific custom drawing, passes the general Control variable as the first parameter, by value. In the process of passing, the type of COM variable is changed to the required COM interface.
Note - In VB/GB32 the interface name is the name of a COM object without the leading I. Here we have an Control variable, which is a pointer (4-bytes) addressing the memory occupying a structure for an Control type. When we pass the variable by value, we put the contents of the Control variable on the stack and interpret it in the called subroutine as an address of a (I)ListView type (interface ). Any actions performed on the local Ocx variable will instruct the compiler to use COM syntax for the type (I)ListView. Confusing? Probably worth a new blog entry ...

The drawing process
I'll be short about interpreting the NM_CUSTOMDRAW draw message data passed in the lParam argument. You'll find it in the SDK or MSDN. First we need to cast the pointer in lParam to the custom draw structure for a ListView control.

' ListView custom drawing
Function CustomDrawListView(Lv As ListView _
, lParam%, ByRef retval%, ByRef ValidRet?) As Bool
Dim nmcdr As Pointer NMLVCUSTOMDRAW
Pointer nmcdr = lParam

Now we can access the structure-members using the dot-operator syntax. For a ListView's subitem to draw, we must respond to three draw stages; CDDS_PREPAINT, CDDS_ITEMPREPAINT, and CDDS_ITEMPREPAINT | CDDS_SUBITEM. That is it.

1 CDDS_PREPAINT
Let us start with CDDS_PREPAINT. This notification is sent at the beginning of the drawing of the row. Each row sends it once only. You must response with the correct value to specify what you want next. Well, both GB32 as ourselves want more (sub)item draw messages, so we must respond with CDRF_NOTIFYITEMDRAW. Since GB32 already returns this value, our custom draw function may skip this message (although it wouldn't hurt if you didn't).

Switch nmcdr.nmcd.dwDrawStage
Case CDDS_PREPAINT
'retval = CDRF_NOTIFYITEMDRAW
'ValidRet? = True
Return True

We return from this function with True to let the Form's event sub _MessageProc() know that we handled this message and that it can leave the event sub without further processing.

2 CDDS_ITEMPREPAINT
The next drawing stage is CDDS_ITEMPREPAINT. When GB32 receives this message it fills in the appropriate NMLVCUSTOMDRAW  members on return. The common control library than uses the selected colors and fonts for drawing. After it processes this message, GB32 returns with a value indicating it is ready with this list item (row). We trap this message before GB32 handles it an specify that we want additional custom draw messages for each subitem of the row.

Case CDDS_ITEMPREPAINT
retval = CDRF_NOTIFYSUBITEMDRAW
ValidRet? = True
Return True

Because CDDS_ITEMPREPAINT returned CDRF_NOTIFYSUBITEM the common library will start sending notification messages, in the form CDDS_ITEMPREPAINT,  before it starts drawing each of the subitems.

3 CDDS_ITEMPREPAINT + CDDS_SUBITEM
To indicate subitem drawing the control library adds CDDS_SUBITEM to the value. So, in the next stage we must process, we must fill in the members of NMLVCUSTOMDRAW that the control library will use to draw. More precisely, we can fill in the .clrText and .clrTextBk fields for the colors and select a new font in the hDC. This is exactly the behavior of GB32. So, we will let GB32 process the message first.

Case CDDS_ITEMPREPAINT | CDDS_SUBITEM
' Set back to the ListView control default colors for each subitem.
nmcdr.clrText   = CLR_NONE
nmcdr.clrTextBk = CLR_NONE

  ' Let GB32 copy the ListItem properties of the row (color _and_ font)
' Go through the ListView window procedure, so forward the WM_NOTIFY message
' (as OCM_NOTIFY) to the ListView's window procedure.
nmcdr.nmcd.dwDrawStage = CDDS_ITEMPREPAINT
retval = SendMessage(Lv.hWnd, WM_NOTIFY + $2000, 0, *nmcdr)
ValidRet? = True

  ' Finally, we get a chance to overrule the color settings:
Local Int Row = nmcdr.nmcd.dwItemSpec + 1   ' 0 to 1 -based
Local Int Column = nmcdr.iSubItem + 1       ' 0 to 1 -based
If lv4_Colors(Row, Column).Fore <> CLR_NONE
nmcdr.clrText = lv4_Colors(Row, Column).Fore
EndIf
If lv4_Colors(Row, Column).Back <> CLR_NONE
nmcdr.clrTextBk = lv4_Colors(Row, Column).Back
EndIf

Return True     ' Notify the Form_MessageProc
EndSwitch  ' dwDrawStage

After overruling the colors the subitem is drawn. Unfortunately, the function uses a global array named lv4_Colors() of type SubItemColors. The following is from the frm1_Load() sub event, lv4 is the Ocx ListView in report mode.

Type SubItemColors
- Int Fore, Back
EndType
Local Int Rows = lv4.ListItems.Count
Local Int Columns = lv4.ColumnHeaders.Count

Global lv4_Colors(Rows, Columns) As SubItemColors

' The array MUST be initialized with the default ListView system colors CLR_NONE (= -1).
' Whenever a subitem cell color must be reset to the default value,
' simply set the Fore and Back members to CLR_NONE.
MemLFill ArrayAddr(lv4_Colors()), ArraySize(lv4_Colors()), CLR_NONE

' Cell (1,1) is left-top-most cell
lv4_Colors(1, 1).Fore = $23FFFF
lv4_Colors(1, 1).Back = $777777
lv4_Colors(2, 1).Fore = $FF
lv4_Colors(2, 1).Back = RGB(0, 255, 0)

The CLR_NONE value indicates the use of the ListView default colors. It is the clue to all drawing.

The integration in GB32 custom drawing might be complicated. If so ask me.

13 January 2010

Swap Arrays by ArrPtr()

Didn't GFABASIC for Atari ST support swapping (or passing) arrays by pointer? I picked up my copy of the book 'GFABASIC' by Frank Ostrowski (1987) to check if I remembered correctly. Indeed, some kind of swapping by pointer is supported. The QuickSort program FO presented on page 22 passes a string array to the sort routine by using *a$(), which is equal to ArrPtr(a$()). To access the array locally in the procedure, the array descriptor passed is swapped with a the descriptor of array a$(). Given a pointer to an array descriptor the Atari ST Swap command supported the following syntax:

Swap *ardescriptor%, a$()   ! Atari ST GFABASIC

Unfortunately, the Swap command of GB32 isn't that flexible. The arguments of the Swap command need to be of the same kind. For instance:

Swap a%(), b%()
Swap f#, g#
Swap udt1, udt2

Even a user-defined type can be swapped! Swap works well as long as the data type of both operands is the same. But I have a need to store a pointer (Long Integer) to an array of data and later on use that pointer to access the array's data. So, I want to store an ArrPtr() and swap that pointer with an (empty) array. For instance:

Swap arrdesc%, d()    // not supported

After disassembling the code GB32 generates for Swap a(), b() it is easy to see, that GB32 implements the array swap by passing the pointers of the descriptors of these arrays. Internally, a Swap a(), b() is actually a Swap ArrPtr(a()), ArrPtr(b()).

Also, the disassembly reveals the Asm scall name for the library function. Strangely, the name is pgMinBottom, obviously an error in the naming. It is now easy to create a custom routine to swap two arrays by descriptor.

Proc ArrPtrSwap(ArrPtr1%, ArrPtr2%)
  . push [ArrPtr1%], [ArrPtr2%]
  . scall pgMinBottom   ' GB32 naming error
EndProc

Given some arrays, the procedure might be invoked like this:

Dim g%(2), a%(5)
Dim ptrToa% = *a()
Dim ptrTog% = ArrPtr(g())

ArrPtrSwap(ptrToa%, *g%())
ArrPtrSwap *a%(), *g%()
ArrPtrSwap *a%(), ptrTog%

Now you can initialize an array, store a pointer to it in the .WhatsThisHelpID property of an OCX control, and use it later on by swapping it with an empty array. I now can finally conclude my series on custom drawing of single SubItems of a ListView.Listitem object.

11 January 2010

The .Tag property

In COM all strings are UNOCODE BSTRs. GB32 converts a string to UNICODE before it calls the set_Tag() function. When the property .Tag is set with a literal string constant, the compiler optimizes string assignment by converting the literal string to an UNICODE string before hand. For instance, the assignment Ocx.Tag = "Hello" is stored as:

Ocx.Tag = "H" #0 "e" #0 "l" #0 "l" #0 "0" #0 #0

When the .Tag property assignment is actually executed during runtime, the UNICODE string is passed to the set_Tag() function of the Ocx/COM object, and the BSTR is stored with the COM object for later access. But something is breaking the process ...

GB32 stores ANSI code
The rationale behind GB32's implementation of UNICODE vs. ANSI isn't very clear. IMHO the most simple, effective, and fastest procedure would be to store the pointer to the UNICODE string as it is passed to the Tag property. But GB32 converts it (back) to ANSI and stores a pointer to the ANSI-string memory. Of course, GB32 strings might be as large as physical memory and storing a UNICODE string doubles the memory necessary. But that couldn't be the reason for this behavior, could it?

Anyway, so far so good. It is a fact of life. If you like you can store binary data in a string and  assign it to a .Tag property. But can you get it out of the Tag property? The answer is No! When the BSTR passed to the set_Tag() function is copied to ANSI, GB32 only stores the pointer to the converted ANSI string (a char* in C/C++). It doesn't save the length of the passed BSTR. So, how does GB32 know how many characters to return from get_Tag()? It doesn't and simply invokes a call to the C-library function strlen(char*), which returns at first occurrence of '\0'.

Binary data cannot be stored in a .Tag property.

08 January 2010

The OCX() return type

What exactly does the OCX() function return? It got me thinking when I was developing the custom draw program. Since GB32 seems to name all COM objects Ocx objects, I expected it to return a general Object type (which isn't exactly wrong). But it doesn't return an Object type. Remember that a DisAsm or Collection COM object aren't created using the Ocx command, but using a New clause in the Dim command. These are not Ocx objects; Ocx objects are COM wrappers for controls only, that is anything with a window handle. Consequently, strictly spoken, the OCX() function returns a Control object.

Any COM type can be cast back to an Object. The only actions performed on an Object are late bound properties and methods. An Object is an IDispatch interface only.
Next in hierarchy is a Control object. It embeds the IUnknown and IDispatch interfaces, but it also includes data for GB32 to maintain the windows/controls. Every Ocx ( = control) includes (embeds) a Control object. Since there is no interface type-info for a Control object (use OleView.exe to inspect COM types), the properties and methods are invoked through IDispatch, like with the Object type.
Finally, there is an interface definition for an Ocx control. The Ocx object is based on the Control object. In addition, each Ocx object has its own control specific data. A Control object is an OCX without the specific control information and without an interface.

Object -> Control -> Ocx
Control = OCX(hWnd)

As you can see, there is nothing wrong to store the return value of OCX() in an Object variable, but strictly speaking it should be a Control type variable.

For a non-Ocx COM object the hierarchy is

Object -> GB32 COM Object type (Collection, DisAsm, etc).

For these types there is no OCX() function (because it depends on a window handle) and should not be cast to a Control object.

06 January 2010

Custom Drawing Ocx Controls

Together with the TreeView common control, the ListView common control is one of the most important control used to present organized data. Since displaying data in columns is probably the most used features of the ListView control, I will discuss how to implement subitem coloring of cells in a report-view mode ListView Ocx control. In this installment I will discuss the necessary steps to start custom drawing a GB32 Ocx control. The ListView cell coloring is discussed in another entry.
Custom drawing
The common control library supports a technique called 'custom draw' to change the appearance of the elements of some of the controls. The custom draw technique was developed to eliminate the need to fully owner-draw entire controls. Custom draw allows trapping only a small set of the drawing operations, rather than perform all the drawing.
The common controls send custom draw messages as a WM_NOTIFY messages to the parent window. The WM_NOTIFY message specifies  a pointer to a NMHDR structure in the lParam argument. The NMHDR structure specifies the (notification) code, the window handle and ID of the control from which the message originates.
Type NMHDR
  hwndFrom As Long
  idfrom As Long
  code As Long
End Type
The _MessageProc event
In GB32 a parent-window is usually a Form. The WM_NOTIFY message is a sent message and can only be handled in Form's event sub Form_MessageProc(). The _MessageProc is invoked from inside the Form's window procedure (defined in the GfaWin23.Ocx) before  GB32 processes the message itself. The two additional arguments of the _MessageProc (retval% and ValidRet?) determine whether GB32 will actually continue processing this message in its own window procedure once it returned from _MessageProc, which could look like this:
Sub frm1_MessageProc(hWnd%, Mess%, wParam%, _
 lParam%, retval%, ValidRet?)

  Switch Mess
  Case WM_NOTIFY
    ' use Pointer to cast a lParam to a type!
    Dim nmhdr As Pointer NMHDR
    Pointer nmhdr = lParam
    ' Custom draw message.
    If nmhdr.code == NM_CUSTOMDRAW
      ' (do something)
    EndIf
  EndSwitch
EndSub
Noticeable is the elegant way GB32 allows you to cast a pointer to a structure. I explained Pointers in the past.
It is important to realize that NM_CUSTOMDRAW is sent always, for every common control that supports it. As soon as a common control starts it painting process it starts sending this message. It is up to the developer to tell the common control library to send follow-up messages for each stage in the painting process, or to stop sending notifications after the first one arrived. For performance reasons, the library only sends the message that signals the start of the paint-cycle (CDDS_PREPAINT) and then only sends more messages when you respond with a value "send-me-more".
Custom drawing Ocx controls
What to do once you intercepted the NM_CUSTOMDRAW message is described in the MSDN. However, the documentation always discusses C-style handling and provides sample code in case one control wants custom drawing. What if the control is a GB32 Ocx and multiple Ocxs want custom drawing? The question arises how to differentiate between the various Ocx controls when we process NM_CUSTOMDRAW?
Option #1 is to store the window handles of the Ocx-controls in global variables and compare these in a (possibly long) If-Else statement with nmhdr.hwndFrom. Once you find a match you know what to do. This option is certainly valid, but it is not my intention to show you how to solve this problem the API-way, but to show you the GB32- COM way.
The GB32-COM way
This is option #2. We will use nmhdr.hwndFrom to get an Ocx object and then use the object to perform general custom drawing for that object type. Since GB32 supports COM interfaces for ListView, TreeView, ToolBar, and Slider, not all common controls (Header, ToolTip, and ReBar) that support custom drawing can be handled this way. The ReBar common control is not implemented, unfortunately, and Header and ToolTip controls are part of other controls and are not support on their own. So, we need a function that separates the good from the bad.
Function OcxCustomDraw(hWndFrom%, lParam%, _
  ByRef retval%, ByRef ValidRet?) As Bool
  ' Non Ocx controls are rejected immediately, Only OCX() is invoked.

  Dim Ob As Object
  Set Ob = OCX(hWndFrom)

  If IsNothing(Ob)
    Return False        ' Not an OCX control
ElseIf TypeOf(Ob) Is ListView ' Most often used in custom drawing
    Return CustomDrawListView(Ob, lParam%, retval%, ValidRet?)    ' Big chance we have a hit.
  ElseIf TypeOf(Ob) Is TreeView
    Return CustomDrawTreeView(Ob, lParam%, retval%, ValidRet?)
  ElseIf TypeOf(Ob) Is ToolBar
    Return CustomDrawToolBar(Ob, lParam%, retval%, ValidRet?)
  ElseIf TypeOf(Ob) Is Slider
    Return CustomDrawSlider(Ob, lParam%, retval%, ValidRet?)
  EndIf

  ' Not a custom draw Ocx control, 
' continue processing
  ' NM_CUSTOMDRAW in Form_MessageProc()
  Return False
EndFunc
The window handle in nmhdr.hwndFrom identifies the common control, so we pass this value in the first parameter hwndFrom of  the OcxCustomDraw() function. The second parameter is the lParam value as it is passed to _MessageProc, holding a pointer to NMHDR. The out-parameters retval% and ValidRet? are passed as well, since they must be set once we processed the message. In addition, we make this a Boolean function to signal the caller (_MessageProc()) to continue processing WM_NOTIFY or to exit the event sub immediately. (The _MessageProc should not perform any action when the message is handled in OcxCustomDraw(), but It might do more processing when the hwndFrom passed in isn't an Ocx object or when NM_CUSTOMDRAW originates from a GB32 Ocx that doesn't support custom drawing. In these cases OcxCustomDraw() returns False.)
To check the Ocx type for a given window handle we need a two step process. First, obtain the address of the object that wraps the control using the OCX(hWnd) function. This is a simple addrObject = GetWindowLong(hWnd, GWL_USERDATA) call. When no object is attached to the window handle Ob Is Nothing and we can skip the rest of the function. When the Ob contains a valid Ocx type we test whether it is of type ListView, TreeView, ToolBar, or Slider using the TypeOf(O) Is Interfacetype construction. When True, we execute either one of four Ocx custom draw operations. These functions are defined as follows:
' ListView custom drawing
Function CustomDrawListView(Lv As ListView _
  , lParam%, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMLVCUSTOMDRAW
  Pointer nmcdr = lParam
Return False
EndFunc
' TreeView custom drawing
Function CustomDrawTreeView(Treeview As TreeView, _
  lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMTVCUSTOMDRAW
  Pointer nmcdr = lParam
  Return False
EndFunc
' ToolBar custom drawing
Function CustomDrawToolBar(Toolbar As ToolBar, _
  lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMTBCUSTOMDRAW
  Pointer nmcdr = lParam
  Return False
EndFunc
' Slider custom drawing
Function CustomDrawSlider(Slider As Slider, _
  lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMCUSTOMDRAW
  Pointer nmcdr = lParam
  Return False
EndFunc
For a ListView control Ocx the function calls CustomDrawListView(). When invoking the function the general Ob variable of type Object is cast to a ListView type implicitly. Each of these CustomDrawType() functions casts the Object variable Ob to the appropriate COM type. Inside each function the lParam is cast to the correct API custom-draw structure (see SDK).
Note - The types are all defined in the commctrl.inc.lg32 included in the GB32 package. You can either load this library using $Library or copy-paste the relevant Types.
Final note
The TypeOf(O) Is Interfacetype construction is a library function that checks the object against the IID value of InterfaceType. Therefore it calls the QueryInterface() for that type. This decreases performance of course, although the GB32 implementation of Is TypeOf is pretty fast.
Next time I discuss how to color subitems in report ListView.

08 December 2009

Why you should use the latest update

Bugs and notes come to me by electronic messages. I check them to see if I can reproduce them and if so I make a note to investigate them. Sometimes, the notes lay around my desk for a long time, because 1) I don't have time, or 2) this is more often the reason, the bug is hard to locate. Because of the complexity, after solving I find it sometimes hard to describe the problem and the resolution. You know: "Doctor, when I push here, it hurts there...".
 
The Vista bug has to do with the memory allocation of the string memory for the Ocx.Name property. When you rename the Ocx (in the Form editor) the memory is released before new (string) memory is allocated to store the new name. GFA bombs! Why? Because GFA mixes memory heaps. The problem is located both in the runtime as in the IDE, but I managed to solve it by a Try/Catch structure in the IDE. For that to happen I had to teach myself code insertion in a binary and writing Try/catch handlers in machine language. Well, its my hobby, so it is no problem, but after so much effort I mostly limit myself to a short note: this bug has been solved. But mostly I give an example in the readme or on my site.
 
The Double bug is bad! When a string (LPCHR) contains the maximum number of decimal fraction digits in a certain sequence the algorithm FO used is simply wrong. Copy and paste this in your editor:
 
Const a =  -1.79769313486232e+308
 
Results of floating-point calculations are often stored in strings (for instance a representation in a control). Than used for a calculation the string is converted back using Val or ValDbl. In certain situation this fails and crashes GB, for the exact same reason. The problem: GFA-BASIC uses a general exported DLL function for these conversions (both at editing, compiling and runtime). How do you know when your conversion is wrong or right?? You cannot. Therefore you MUST use the latest update.

13 November 2009

Is GFABASIC 32 compatible to GFABASIC 16?

Yes it is. As a matter of fact I discovered when disassembling the runtime that GFABASIC 32 is much more compatible to GFABASIC 16 than was claimed. The question is; what does compatibility means? Does it mean that GFA-BASIC 16 programs should run without any change? No. Running in a 32-bits environment requires other prerequisites for data handling. An integer is now 32-bits, memory is flat, DLLs have no shared memory, API calls have become obsolete and many new ones are added. So, in this respect any language updated to 32-bits has evolved.

Is GFA-BASIC 32 compatible to the 'On Menu' event handling of GFA-BASIC 32? And compatible to the window and dialog handling? Yes. This is completely implemented, except for 16-bits HandleMessage (if you like to read more about it I might tell you why). There is no holding back for using GetEvent and using Menu(1) !!! It is perfectly well implemented. So, what else do you need?

29 October 2009

Modify the Windows style

For a project I needed to change many window style bits in one session. For this I developed two procedures, one to change the GWL_STYLE and one to change the GWL_EXSTYLE window styles.
$Export
$Export Proc Modify*

Procedure ModifyStyle(hWnd As Handle, lRemove As Long, lAdd As Long)
Local Long Style = GetWindowLong(hWnd, GWL_STYLE)
Style &= ~lRemove
Style |= lAdd
~SetWindowLong(hWnd, GWL_STYLE, Style)
~SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME)
EndProc

Procedure ModifyExStyle(hWnd As Handle, lRemove As Long, lAdd As Long)
Local Long ExStyle = GetWindowLong(hWnd, GWL_EXSTYLE)
ExStyle &= ~lRemove
ExStyle |= lAdd
~SetWindowLong(hWnd, GWL_EXSTYLE, ExStyle)
~SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME)
EndProc

As you can see, I collected these in a $Library (.lg32). I'm a big fan of libraries and whenever possible I put general subroutines in a compiled module.

I'm aware of some problems with loading of LG32 files, I had them in the past as well. However, and I don't know why, I don't have them anymore ...

26 October 2009

LabelAddr in a GLL

In two recent posts (here and here) I discussed the problems with subclassing the Gfa_hWnd window in a GLL. In fact, it turned out to be impossible using 'normal' Windows API functions. Another solution was required.
The final solution I developed is based on the MS Detours technique from MS Research Labs. However, not even this highly tricky technique worked for me. The GLL still couldn't be unloaded using the Extension Manager Dialog box. Then, I adapted the Detour technique and configured it in such a way that unloading the GLL isn't a problem anymore. Almost two years ago I first started to study the Detours technique, and finally I came up with a solution to full fill my needs.
The adapted method is based on a GFA-BASIC 32 assembly routine, which is injected into the main window procedure of Gfa_hWnd inside the code of the IDE. To complete my mission I had to figure out the address of a label inside a GFA-BASIC subroutine. The injected code has to jump (jmp) to the label's address. However, the addresses of labels in GLLs (GFA Editor Extensions) are not known in advance.
Both the ProcAddr() and LabelAddr() functions are filled in by the compiler at compile time. In addition, a relocation-table is stored with the compiled program. In contrast with loading an EXE, which is performed by the OS, the addresses in a GLL are relocated by the GFA-BASIC 32 IDE when the GLL is loaded. The IDE is capable of adjusting the ProcAddr addresses inside the GLL, but it doesn't adjust the hardcoded LabelAddr values.
It takes a trick to obtain the relocated address of a label. The GLL is loaded into memory and addresses are calculated using an offset. When we know the offset value the hardcoded label addresses can be calculated. The function Gfa_LabelOffset in the next code, does just this. Since the value is constant for the entire GLL, it is stored in a static variable.
Function Gfa_LabelOffset() As Long
Static LabelOffset% = 0
If LabelOffset% == 0
GetRegs : 1
LabelOffset% = _EIP - LabelAddr(1)
EndIf
Return LabelOffset%
EndFunc


The function can be used to obtain the real location of a label as follows:


Local stubAddr% = LabelAddr(stub) + Gfa_LabelOffset
stub:


The LabelAddr(stub) returns the hardcoded value set by the compiler. The real location is then adjusted by the offset value used to relocate all addresses in the GLL.

20 October 2009

EXE behaves different

"My program runs fine in the IDE, but it freezes when the compiled EXE is started." What is happening?
In the past ten years this message pops-up once and a while. At the same time this message is never repeated. The developer reviews his or her program and recognizes a programming bug. Then after the bug is solved, the program runs without any problem. Until now I never (!) encountered a compiler bug that resulted in an erroneous EXE.
Then, how is it possible that the EXE behaves different than the IDE-program? Some things to consider.
  1. The most logical error is a programming error (90% chance).
    I noticed a problem in user-defined types, for instance a member array isn't properly dimensioned, or a Boolean member is used (see the Help File for notes on using Booleans in a Type). Check your Types, but even better review the initialization code of your program.
  2. In Mysterious Errors I described a theory about errors that cannot be reproduced. But again, the programmer is to blame.
  3. The compiler inserts Trace-code between each code line in the IDE. You can disable this with $StepOff and get the same compiler result as an EXE.
  4. An EXE might behave different because the Compiler setting  Full Branch Optimization for EXE is enabled. This is the only option that produces a different result. (1% chance).
  5. Make sure you are aware of the ByRef Bug for Sub procedures. Insert ByVal and ByRef in the procedure headings. Better: use Sub only for event subs.
All in all, look for the problem in your code, not in the compiler. If an EXE works with one OS, but doesn't with another, than we have a possible bug!

14 October 2009

Difference between _Message and _MessageProc

There are two somewhat strange, certainly badly documented, event subs for forms: the Form_Message and Form_MessageProc event. Although they sound much alike, they are quite different. Both events are invoked from inside the window procedure of the form. However _Message() is called only for a handful of messages, but _MessageProc() is called for all messages, regardless of their origin. These subs are defined as:

Sub frm1_Message(hWnd%, Mess%, wParam%, lParam%)
Sub frm1_MessageProc(hWnd%, Mess%, wParam%, 
  lParam%, retval%, ValidRet?)
The _MessageProc event sub With the _MessageProc Sub you can actually filter or influence the behavior of the GB32 window procedure for the Form window class. The _MessageProc is called before GB32 handles the message itself (more or less, maybe later more on this). The sub obtains six message parameters. The first four are the same as defined for a Windows API window procedure. Every message, whether it is obtained from the message queue (the posted messages) or by a direct call from any other source (for instance through SendMessage), is handled in the window procedure. The hWnd parameter is the window to which the message is sent (we already know the objects name: frm and we could obtain the window handle from the object, that would have lowered the number of parameters by one). The Msg parameter is the message number—which is usually a constant such as WM_ACTIVATEAPP or WM_PAINT. The wParam and lParam parameters differ for each message, as does the return value; you must look up the specific message to see what they mean. Often, wParam or the return value is ignored, but not always. The _MessageProc() event sub has two additional parameters (ByRef) that allow you to return a value. For instance, when you want GB32 not to handle a certain message you can set the ValidRet? Boolean variable to True and provide a return value by setting RetVal%. What value RetVal must have is defined in the Windows API SDK. It often says something like: "If you handle this message return zero (or..)". Now let us look at an example. Suppose you want to store the window coordinates of OpenW #1 in the register so the application can use these values to open at the same place. In GB32 you must then handle the sub events Form_ReSize and Form_Moved to store the coordinates. As an alternative you could use Form_MessageProc and handle the WM_EXITSIZEMOVE message as follows:
Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, _

      lParam%, Retval%, ValidRet?)

 Local Int x, y, w, h

 Switch Mess

 Case WM_EXITSIZEMOVE

   GetWinRect hwnd, x, y, w, h

   SaveSetting "MyComp", "ThisApp", "Position",

        Mkl$(x, y, w, h)

   ValiRet? = True : RetVal = 0

 EndSwitch

EndSub
Note the Form_MessageProc() actually _is_ the subclass window procedure for the GB32 windows (Form, Dialog, OpenW, ChildW, ParentW). Subclassing is a built-in feature of GFA-BASIC 32. As such the the OCX control Form is perfectly suited to write custom controls.
The _Message event sub The Form_Message is different: you cannot filter messages and return values, you can only respond to a handful of messages, to WM_SIZE, or WM_PAINT for instance. Why these? Because these are posted messages, messages that are retrieved from the message queue. Note that most posted messages have a accompanying sub event. For instance, a WM_SIZE message results in calling the Form_ReSize event sub. You can use the _Message sub to handle many messages that are otherwise handled in these event subs. All you need to know is which messages are posted. These are all input messages like key and mouse messages, window management messages like moved, sized and wm_paint. You must then, just as in _MessageProc, create a Switch/Case structure to respond to the message. The main disadvantage is that you must interpret the wParam and lParam parameters yourself... The order in which the sub events are called You can easily test in which order the sub events are called. For posted messages, those that are retrieved from the message queue using Sleep, the _Message() event is called before any other sub. Then the message is 'dispatched' to the window procedure and the _MessageProc is called. And at last, the event sub is invoked. For a WM_SIZE message the sequence is: Win_1_Message() Win_1_MessageProc()
Win_1_ReSize (This article was previously posted on the GFA-BASIC Google Pages. It has been edited to remove errors.)

The Sub-ByRef flaw

A call to a Sub procedure using by reference arguments might not update the correct variable. To understand this behavior be sure to understand the differences between ByVal and ByRef arguments. This is explained in the next tip 'Passing values ByRef or ByVal'. Here you have learned that a Sub by default takes arguments as ByRef, making ByRef optional. Unfortunately this is not always true.

When an argument is passed by reference, the procedure is passed the address of the argument variable. When the argument is a global variable the address of the variable is passed as expected. In the following situation the ByRef clause is omitted and the global variable is updated to 100 correctly.

Global a% = 50
MySub(a%)
Print a%   ' 100
Sub MySub(b%)
  b% = 100
EndSub

When a second Sub is called from within MySub passing b% by reference, b% is  not updated as expected. The variable b% is passed by value, rather than by reference!

Sub MySub(b%)
  b% = 100 : NextSub(b%) : Print b%  // still 100
EndSub
Sub NextSub(c%)
  c% = 200    // unexpected: c% is ByVal argument
EndSub

Why this is happening is unclear, but it is easily repaired by using ByRef explicitly in Sub headings.

Sub NextSub(ByRef c%)

Loading a library: fail or success?

When the $Library directive is parsed (triggered when the line is changed) the specified library is loaded and the exported items are added to the Imports tab in the sidebar. When the file is indeed located and opened a message of success is written to Debug Output Window saying "Loading lg32filename".
Any errors caused by the $Library are then due to a corrupt lg32 file.

The "Load lg32 error" message
Programs that contain references to libraries using the $Library directive may occasionally show a "Load lg32 error: filename.lg32" in the status bar. This message is generated when the specified library can't be located at the specified path or, when a path is omitted, in the current active directory, 'My Documents\lg32', or in directories that are searched using the SearchPath API function.
This message should therefore be called: "Lg32 file not found".

Reload project to reset current directory
When a lg32 can't be found the most obvious reason is an unexpected active directory. Sometimes when working at a project the active directory is changed and the project's directory isn't the current any longer. To check for this, simply reload the project, because this sets the current active directory back to the project's folder.

Set default path for Libraries
An undocumented feature is the possibility to set a default path for libraries in the registry. The registry key HKEY_CURRENT_USER\Software\GFA\Basic must contain the key "lg32Path" specifying the default path for the libraries. The key is not present at default, so you must add the key yourself.

07 October 2009

Hooking to handle Gfa_hWnd messages?

In the Why you cannot subclass Gfa_hWnd I explained why the Gfa_hWnd cannot be subclassed to extend the editor's functionality.

Still, I need to intercept and modify messages for the main IDE window Gfa_hWnd. Some kind of subclassing is required. The other feature Windows offers is the 'hooking', known as Windows Hooks. "Hooking is a sort of subclassing, only it isn't associated with a single window, but with a thread or even the whole system. It's a kind of filter of Windows' messages that allow you to override, or add functionalities when a particular message is received." Sounds promising, doesn't it? 

Hooking Example

In fact, I did create a test program. First, two hooks are installed, the WH_GETMESSAGE and the WH_CALLWNDPROC hooks. There are other hooks, but these seem to be the most logical ones. Just to show how hooking might be done in GFA-BASIC 32, I present some code.
After declaring two global Handle variables, the hooks are initialized as follows:

Global Handle hGetMsgHook, hCallWndHook
' Process or modify all messages (of any type) for the system whenever a
' GetMessage or a PeekMessage function is called (WH_GETMESSAGE).
hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE , ProcAddr( GetMsgProc), App.hInstance, GetCurrentThreadId())

' Process or modify all messages (of any type) whenever a SendMessage
' function is called (WH_CALLWNDPROC).
hCallWndHook = SetWindowsHookEx(WH_CALLWNDPROC , ProcAddr( CallWndProc), App.hInstance, GetCurrentThreadId())

From this code you can see that both hooks are thread wide, meaning all messages for the thread are passed on to the hook filter functions GetMsgProc() and CallWndProc(). This is one of the reasons I didn't use the hook technique to 'subclass' the Gfa_hWnd window. Later more.
The application defined hook filter functions are defined as:

' WH_GETMESSAGE Hook
Function GetMsgProc(ByVal idHook As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Dim msg As Pointer MSG
  Pointer(msg) = lParam
  If msg.hwnd == GfahWnd && msg.mess > 0 && msg.mess <> 275
    Trace msg.mess
  EndIf
  Return CallNextHookEx(hGetMsgHook, idHook, wParam, lParam)
End Function
'
' WH_CALLWNDPROC Hook
Function CallWndProc(ByVal idHook As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Dim msg As Pointer MSG
  Pointer(msg) = lParam
  If msg.hwnd == GfahWnd && msg.mess > 0 && msg.mess <> 275
    Trace msg.mess
  EndIf
  Return CallNextHookEx(hGetMsgHook, idHook, wParam, lParam)
End Function

The GfahWnd variable contains the Gfa_hWnd value and MSG is an API user-defined type to store the message parameters. All well known I presume.

Is hooking a solution?
Does Windows Hooking offer a solution to my problem? The answer is no. Subclassing goes further than hooking. Subclassing functions can return values in response to messages, hooking filter functions cannot. Another issue is the use of thread wide filter functions, that are called for every message that is sent through SendMessage or retrieved with GetMessage. Not only do these hook functions significantly drain on system performance, they might also interfere with the application at hand, that is, the program currently being developed and run in the IDE.

I need another solution.

05 October 2009

Why you cannot subclass Gfa_hWnd

In the course of interpreting the disassembly of both the GfaWin32.exe and the run-time I created quite some editor extension procedures. Most of them are rudimentary and should be developed further, but some of them are very useful (at least to me). I always hoped to put them together in one single GLL file and make that GLL available. However, I never came to do that. I had a problem I didn't understand, and thus didn't know how to solve. After unloading that GLL the IDE crashed.

To add new functionality a Windows developer will most likely start by subclassing the window. In fact, subclassing is the most elementary of all modification techniques. I started by subclassing both IDE windows, the main IDE window Gfa_hWnd and the editor window Gfa_hWndEd. The subclassing was performed in the Gfa_Init() procedure and undone in Gfa_Exit(), which seemed logical at the time. However, as said, this caused a (fatal) problem that at that time I couldn't resolve and I reserved it for a later moment to handle. I was convinced it was something I simply overlooked and at that stage it wasn't really an obstacle. The GLL was automatically loaded when the IDE was started and unloaded when it was closed. I never unloaded the GLL by hand using the Editor Extension Manager dialog box.

Sometimes I was annoyed by the problem and tried to find out the cause of it. It turned out that when the GLL was unloaded in the course of closing the IDE, the un-subclassing was performed after the main window was closed. So, the Gfa_hWnd window didn't exist anymore and re-installing the old window procedure didn't cause any problem. But, when I unloaded the GLL when the IDE was active, using the Extension dialog box, it always crashed the IDE. The undoing of the subclassing of Gfa_hWnd was the problem. In fact, you cannot subclass the Gfa_hWnd in a GLL.

My problem was that I didn't understand exactly what I was doing when I was subclassing a window. Let me illustrate by describing the path a WM_COMMAND (a posted) message follows to where it gets actually handled. Here is what happens when you select the GFA-BASIC 32's Editor Extension Manager dialog box, either from the menu-bar or through a shortcut First the OS puts a WM_COMMAND message in the application queue. The program obtains the message in an endless GetMessage loop and dispatches the message to the window procedure (note: the one I tried to subclass). The window procedure invokes the modal dialog box and a new message loop is started to handle the dialog box. Now, after subclassing, the OS invokes the new window procedure rather than the old one, and requires the new procedure to execute the old window procedure using the CallWindowProc() API. So, the old window procedure, which handled the WM_COMMAND, is called from the new window procedure located in the GLL. In fact, when the dialog box is put on the screen and a modal message loop is started, the program is still executing the new window procedure inside the GLL! After closing the dialog box the program will want to return to the location it was called from, the place where CallWindowProc() API was invoked. You see the problem now?

The old window procedure was invoked from inside the new window procedure (located in the GLL), which was just unloaded using the Editor Extension Manager dialog box. There is no place to return to! Ergo, the most elementary technique, subclassing a window to add new functionality, is not possible for Gfa_hWnd in an editor extension.