09 May 2019

Task Dialog as a replacement for Alert

Since Vista Windows supports the Task Dialog, an extended Message Box with a lot of new features. As the message box the task dialog displays an application-defined message, title, icons and any combination of predefined push buttons. In addition it supports a verification checkbox, command links, and radio buttons. In this post I’ll show you how to use the latest GB update (version 2.56) to easily create a task dialog. At the end of this post we will create the following dialog as an replacement for the famous Alert box.

There are two APIs that create a dialog box: TaskDialog and TaskDialogIndirect. The links will take you to the MS SDK site, to the pages that formally describe the APIs. Starting with GFA-BASIC update version 2.56 the APIs are declared in the include library file commctrl.inc.lg32. To get to the actual declaration you should inspect commctrl.inc.g32 – the source file for the library.

The TaskDialog API
The TaskDialog API is declared as follows:

Declare Function TaskDialog Lib "comctl32.dll" ( _
  ByVal hwndParent As Long, ByVal hInstance As Long, _
  ByVal pszWindowTitle As Long, ByVal pszMainInstruction As Long, _
  ByVal pszContent As Long, _
  ByVal dwCommonButtons As Long, _
  ByVal pszIcon As Long, pnButton As Long) As Long

Note that only the last parameter takes a variable by reference, all others are declared as ByVal. The TaskDialog function returns the selected button through this variable. The return value of the function itself indicates success or failure.

The parameters that take a string expect a wide string, the string must be formatted as an Unicode string. I discussed Unicode strings in Ansi and Unicode. To convert an Ansi string to Unicode I’ll use the function Wide() from gfawinx.lg32, which is located in the Include directory as well. The name of this file does not include the inc part, because it is a library with executable code, which will add to the program’s size (be it minimal). The include files (those that include the inc extension in the filename) only contain declarations and definitions that don’t add to the program’s size.

If you didn’t change the path to the Include directory after installing the update the Extra tab in the GB Properties should contain a valid library-path. This also means that you can include commctrl.inc.lg32 and gfawinx.lg32 as shown in this code:

$Library "commctrl.inc"
$Library "gfawinx"
OpenW 1
Print DlgTask(Me.hWnd, "Prompt", "Content", "Demo" , , -3)
Until Me Is Nothing

Function DlgTask(hOwner As Handle, sMainText$, sContent$, _
  Optional sTitle$, Optional iButtons% = TDCBF_OK_BUTTON, _
  Optional Icon& = 0) As Long
  Local Long RetVal, lIcon
  Local String Title

  sMainText$ = Wide(sMainText$)
  sContent$ = Wide(sContent$)
  Title = Wide( Iif(IsMissing(sTitle$), App.Name, sTitle$))
  lIcon = MakeLongHiLo(0, Icon&)

  If TaskDialog(hOwner, 0, V:Title, V:sMainText$, V:sContent$, _
    iButtons%, lIcon, RetVal) == S_OK
    Return RetVal

This code produces the following dialog box at the center of the parent window:

You can pass Null to the hOwner parameter of the TaskDialog, but that would display the dialog at the center of the main screen. Not very useful when using multiple monitors with high resolutions. The hInstance parameter can be 0 because we don’t use an icon from the EXE’s resources. Instead we pass a predefined value for the icon to display (-3). The wide strings are passed by providing their address. The RetVal variable is passed by reference to receive the button’s number (>1) that is selected. The DlgTask function returns 0 if TaskDialog doesn’t return with S_OK (=0).

Although commctrl.inc defines all constants to be used with the task dialog functions, it doesn’t provide constants for the icons (GFA-BASIC versions <= 2.56). The icons are defined as follows:

Icon Value (Word)
Warning -1
Error -2
Information                    -3
Shield -4

The icon parameter must be an integer resource value created with the macro MAKEINTRESOURCE(). This macro, defined in some SDK header, creates a long where the high word is zero and the low word contains the resource identifier (Word). Here we use MakeLongHiLo() to create the integer resource value. With a newer version of GB (> 2.56) you can use the constants as defined in commctrl.inc without the need to convert with MakeLongHiLo.

The TaskDialogIndirect API
The TaskDialogIndirect function allows further fine tuning of the task dialog. For this to happen you need to fill out a structure of type TASKDIALOGCONFIG.

Declare Function TaskDialogIndirect Lib "comctl32.dll" ( _
  ByVal pTaskConfig As Long, pnButton As Long, _
  pnRadioButton As Long, pfVerificationFlagChecked As Long) As Long

By using TaskDialogIndirect you can create custom buttons rather than using predefined buttons only. Therefor it is a perfect candidate to replace the good old Alert box with a nicer version. The next example shows how to set up the TASKDIALOGCONFIG structure and pass it to the TaskDialogIndirect API to create the dialog box as shown at the beginning of this post. The Alert2 function takes the same arguments as the Alert box function. The IconAndFlag% argument specifies the icon and layout of the alert box. The MainText$ argument can specify multiple lines by using | as a separator. The ButtonText$ specifies the custom buttons and DefButton% the button to preselect. These parameters are translated to the task dialog features.

$Library "commctrl.inc"
$Library "gfawinx"
OpenW 1
Print Alert2(2, "Which procedure should|be executed", 1, "Input|Calculate|Print")
Until Me Is Nothing

Function Alert2(IconAndFlag%, MainText$, DefButton%, ButtonText$) As Long
  Dim Icon As Word, RetVal As Long, VerFlag As Long, i As Long
  Dim sTitle As String, aBtn() As String, sVerificationText As String

  ' Provide a title (Unicode)
  sTitle = Wide(App.Name)
  sVerificationText = Wide("Don't ask again")

  ' Determine the icon
  Switch IconAndFlag% %& 7
  Case 1       : Icon = -2    ' Stop: Stop/Error icon
  Case 2, 4, 7 : Icon = -3    ' Question, Information: Information icon
  Case 3       : Icon = -1    ' Exclamation: Warning
  Case 5, 6    : Icon = -4    ' Windowsflag, Application: Shield icon

  ' Text lines are separated with |, but we need #10
  MainText$ = Replace(MainText$, "|", #10)
  MainText$ = Wide(MainText$) ' to UNICODE

  ' Copy the button text to the TASKDIALOG_BUTTON array
  StrToArr(ButtonText$, "|", aBtn())
  ReDim taskBtn(0 .. UBound(aBtn()))
  For i = 0 To UBound(aBtn())
    aBtn(i) = Wide(aBtn(i))        ' convert to Unicode
    taskBtn(i).nButtonID = i + 1   ' ID's are base 1
    taskBtn(i).pszButtonText = V:aBtn(i)

  tdc.cbSize = SizeOf(TASKDIALOGCONFIG)
  tdc.hwndParent = Me.hWnd
  ' Right align text?
  If IconAndFlag% %& 64 Then tdc.dwFlags = tdc.dwFlags | TDF_RTL_LAYOUT
  tdc.pszWindowTitle = V:sTitle
  tdc.hMainIcon = MakeLongHiLo(0, Icon)
  tdc.pszMainInstruction = V:MainText$
  tdc.cButtons = Dim?(taskBtn())
  tdc.pButtons = V:taskBtn(0)
  tdc.nDefaultButton = DefButton%
  tdc.pszVerificationText = V:sVerificationText

  If TaskDialogIndirect(V:tdc, RetVal, 0, VerFlag) == S_OK
    ' VerFlag is 1 if checkbox is checked
    Return RetVal   ' base 1

As with the TaskDialog function all strings must be UNICODE. After setting the tdc.cbSize member to the required size the hwndParent and dwFlags members are used to position the dialog in the center of the parent window.
If the IconAndFlag% parameter specifies right aligned text it is honored by including TDF_RTL_LAYOUT in the dwFlags member.
The main text string must contain LF (#10) characters to separate multiple lines. However, the MainText$ argument will separate multiple lines using the | character. So, before converting MainText$ to a wide string we need to replace all occurrences of | with #10 characters. The Replace function is located in gfawinx.lg32.
Setting up the custom buttons requires a bit more work. First we need to create separate strings for each button text. For this to happen we use the gfawinx procedure StrToArr, which splits a string into multiple array elements. Then, in a loop, each array element is converted to a wide string. In the same loop we assign the button’s ID-value and the string pointer to an array of TASKDIALOG_BUTTONs. After initializing this array, it is assigned it to the tdc members cButtons, pButtons, and nDefaultButton.
Finally, we specify text for an additional checkbox control with the pszVerification member. When pszVerification holds a valid memory address the checkbox control is displayed, but it is enabled only if the pfVerificationFlagChecked parameter of TaskDialogIndirect specifies the address of a return variable. If this parameter is Null the check box is displayed in a disabled state. This is also true for additional option boxes. Note that you may pass Null (0) to a ByRef parameter of a declared DLL function.

18 March 2019

StdFont and StdPicture

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

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

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

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

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

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

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

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

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

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

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

13 January 2019

January Update v 2.52

This update – version 2.52 - fixes many small bugs from version 2.5 (December 2018) and introduces several improvements, especially with auto-complete. First of all, auto-complete isn’t as intrusive as it was in 2.5. To select a word from the list you now need to enter the listbox explicitly using <arrow down>. This prevents accidently inserting words you don’t want. Where possible auto-complete is extended with statement completion: the popup listbox shows suggestions determined by the context. Please give it try and if you don’t like it you can disable it in the Properties dialog’s Extra tab, where you can customize more new features.

One of the goals of this update is to bring the GFA-BASIC 32 user interface to current standards, unfortunately the IDE wasn’t built for this. So, within the given limitations a first attempt was made to introduce new features. For instance, auto-complete relies on the status of internal databases of procs and variables, but these databases are updated only when the code is compiled (Shift+F5 or Ctrl+F5). This is especially true for user-defined types and the declaration of a variable of a UDT. To have auto-complete present the UDT-members the Type definitions must be compiled first. (Tip: define UDTs in a library, the $Library command loads pre-compiled UDTs.) In addition, to initialize the entire auto-complete feature the program is best compiled (test) just after loading. Normally, this isn’t a problem unless your program contains errors, that’s where the compiler stops and leaves auto-complete uninitialized.

Although the auto-complete feature is the most obvious addition, many more improvements have been made. I will discuss them briefly, but you can find more information in the readme25.rtf file.

Fixed: destruction of local arrays
The most important improvement of 2.5 and later is the fix of the destruction of local arrays and hashes. Until now it was difficult to use a local array or hash because the compiler didn’t add destruction code to the procedure. For a local array to be destructed you needed to introduce some another dynamic variable – mostly a string – as a workaround. A hash table wasn’t destructed at all, and required an explicit destruction in the code (Hash Erase). Although workarounds existed the problem remained that GB’s standard exception handlers didn’t destruct these data types. An exception (error) in the procedure caused a memory leak because the array and hash weren’t deleted (unless you used Try/Catch and followed the workaround rules). This problem now belongs to the passed, the destruction code is inserted for a normal flow of execution and for exceptions. Now this is fixed, GFA-BASIC 32 shouldn’t cause memory leaks anymore.

Groups in the Proc-tab
For large programs with a lot of procedures there is now a way to group the procedures in the Proc-tab of the sidebar. The following screenshot from the gfawin32.gll project illustrates the result of adding collapsible groups:

The $Group command is placed directly in the front of a procedure that is to be the first in the group. Each $Group command defines a new group of sequential procedures. By default, non-active groups are folded. The grouping feature allows very fast navigating through large programs. To remove a group type $GroupOff on the group line and after the group is removed you can simply delete the line.

New toolbar buttons
The screenshot shows a few more toolbar buttons.

  • The arrow-left and –right allow you to navigate through your edit-history. Since version 2.52 adds more actions to the navigation-history, this increases the chance that you can properly return to the line you previously edited.
  • A Procs button allows for fast access to commonly used procedure related actions like Set to Top, Print, Disassemble, and many more.
  • The validate (V) button is a shortcut for Shift+F5.
  • The window-wipe button performs a cleanup when a window or form remains on the screen after an abnormal exit.
  • The Launch Exe button (here dimmed because the current GLL project cannot be launched) helps in saving, compiling, and running your project in one simple step.
  • Finally, there is a button to quickly toggle the debug output window.

Quick Help
The quick help feature has been completely revised. Now when you hoover over a (key)word a popup with a short help description appears. Although most help topics were available, many (more than 2000) were mapped incorrectly to their help-ids. You should try it out and be surprised by new information never showed before.
Quick help is also available for procs, variables, and Types. For procs and types the quick help can be scrolled to show you the entire proc or type in the quick help window.

Keyboard shortcuts
There are now some really helpful keyboard shortcuts. For instance, App+P inserts the name of the current procedure into the text: an easy way to add a name to a message string.
Shift+Enter inserts the line-continuation character before invoking the Return key.
There are also many shortcuts for procedure related actions, for instance Alt+F12 to show the procedure’s disassembly and Ctrl+F11 to align the current procedure to the top of the editor which helps to focus on the current procedure.

But there is more. Known from Visual Studio is Incremental Search; press Shift+Ctrl+I and start typing letters, while typing the location of your search string is immediately shown. Press the arrow keys to reverse the search direction.

This post doesn’t cover all the new features. That’s why a Tip of the day (only once a day) presents a short note on a new feature. However, for more information please refer to the readme25.rtf file available from the Start Menu.

Bugs, questions and other remarks can be posted at gfabasic32@gmail.com.

01 December 2018

Anatomy of a procedure (2)

Using ‘Proc Disassembly’ we can inspect the assembly code produced by the compiler. In the first part of this series we looked at the generated code for a Naked proc. Now we will discuss regular procedures (or subs and functions) that do not produce minimal code. Regular procs support destruction for local dynamic variable types like String, Object, Variant, arrays and hash tables. In addition, they provide the logic to trace code using the Tron and Gfa_Tron statements. A regular procedure stores information for for Trace, TraceLnr, and other debugging related commands. Finally and maybe most importantly, regular procs support structured exception handling (Try/Catch, On Error, and unwinding).

Inspecting a procedure’s disassembly requires knowledge to identify the three main parts of a procedure; the entry code, the actual code, and the exit code, In the previous post we discussed how we can recognize these parts in Naked procedures, now we’ll see how to identify these parts in a regular procedure.  The test() procedure is changed a little to demonstrate the use of local dynamic variables:

test(2, 6)
Proc test(x As Int, y As Int)
  Local sStr As String, result As Int
  result = x \ y
  sStr = Str(result)
  Print sStr

This procedure declares a local dynamic variable of type String. Before the procedure exits the memory allocated for the string-data has to be released. We’ll see how this will become part of the exit code.

In a regular procedure all dynamic types are destroyed automatically before the procedure returns. (Starting with version 2.5 local arrays and hash tables are destructed correctly as well).

After selecting ‘Proc Disassembly’ the result is displayed in the Debug Output window:

--------  Disassembly -----------------------------------
1 Proc test(x As Int, y As Int) (Lines=7)
03C002D0: 6A 02                   push    2
03C002D2: B8 63 00 00 00          mov     eax,0x00000063
03C002D7: FF 15 3C 1A 4D 00       scall   INITPROC ; Ocx: $1802775D
03C002DD: E8 5A 00 00 00          call    0x03C0033C
03C002E2: FF 55 B4                call    dpt -76[ebp] ; @Tron
03C002E5: 8B 45 14                mov     eax,dpt 20[ebp]
03C002E8: 99                      cdq    
03C002E9: F7 7D 18                idiv    dpt 24[ebp]
03C002EC: 89 43 78                mov     dpt 120[ebx],eax
03C002EF: FF 55 B4                call    dpt -76[ebp] ; @Tron
03C002F2: 50                      push    eax
03C002F3: FF 15 60 1B 4D 00       scall   STRSTRI ; Ocx: $1806AC50
03C002F9: 50                      push    eax
03C002FA: 8D 43 7C                lea     eax,124[ebx]
03C002FD: 50                      push    eax
03C002FE: FF 15 C0 1D 4D 00       scall   STOSTRSV ; Ocx: $18067F73
03C00304: FF 55 B4                call    dpt -76[ebp] ; @Tron
03C00307: 6A FF                   push    -1
03C00309: 8B 43 7C                mov     eax,dpt 124[ebx]
03C0030C: FF 15 08 24 4D 00       scall   PRSEXPCR ; Ocx: $18043D3F
03C00312: 5A                      pop     edx
03C00313: FF 55 B4                call    dpt -76[ebp] ; @Tron
03C00316: 8B 4D F0                mov     ecx,dpt -16[ebp]
03C00319: 64 89 0D 00 00 00 00    mov     dpt fs:[0x00000000],ecx
03C00320: 8D 4B 7C                lea     ecx,124[ebx]
03C00323: FF 15 CC 25 4D 00       scall   CLEARSTR ; Ocx: $1807BA06
03C00329: 8B E5                   mov     esp,ebp
03C0032B: 5D                      pop     ebp
03C0032C: 5B                      pop     ebx
03C0032D: 5F                      pop     edi
03C0032E: 5E                      pop     esi
03C0032F: C2 08 00                ret     8
03C00332: 51                      push    ecx
03C00333: 8D 4B 7C                lea     ecx,124[ebx]
03C00336: FF 15 CC 25 4D 00       scall   CLEARSTR ; Ocx: $1807BA06
03C0033C: C3                      ret 
03C0033D: 90                      nop    
03C0033E: EB F2                   jmp     short 0x03C00332  

It’s immediately clear that the disassembly differs greatly from the Naked attributed procedure discussed in the previous post. The first thing we need to identify is the entry code where the stackframe is established.

The entry code
Part of the procedure’s entry code, where the stack is prepared, is located in the INITPROC library function, which takes two arguments. As we will see, in GB arguments to library functions are often passed via eax and the stack. Here, the first argument is passed through the stack and specifies an encoded value used to reserve and initialize stack space for local variables. The second argument is stored in eax and specifies the offset to an unwind (termination) handler. INITPROC is a general function that is responsible for setting up a stack for a regular GB procedure, preparing it for structured exception handling and for use with Tron/Trace. Therefor, the stack needs an additional of 80 bytes for an ‘Extended Information Block’ .

After INITPROC has returned the stack for test() has been setup as shown if the figure below.

These four lines constitute the entry code of the proc:

03C002D0:  push    2
03C002D2:  mov     eax,0x00000063
03C002D7:  scall   INITPROC ; Ocx: $1802775D
03C002DD:  call    0x03C0033C

If a procedure contains local variables the first argument tells INITPROC the number of stack-bytes to reserve and initialize. This happens in the same way as we saw in the Naked procedure. The stack bytes are reserved and initialized through a series of push eax instructions, determined by the encoded value of the argument. (The value is a compiler encoded number and does not specify the actually number of pushes that are inserted. In this case the value is coincidentally 2.)
The second argument of INITPROC, passed via eax, is an offset value used by INITPROC to calculate the address of the unwind code stored at virtual address $03C00332.

Without local variables the compiler inserts a call to INITPROC0 instead of INITPROC. INITPROC0 takes one argument only, the offset to the unwind-handler, and omits the code to prepare the procedure’s stack for use of local variables.

The unwind-termination code is only executed in case of an unhandled exception in the current proc, that is an exception that isn’t caught by a Try/Catch handler. (To be discussed in a coming post.)

The fourth and last line of the entry code calls $03C0033C and returns immediately. Why? I have no idea …

When the program is compiled to EXE, the calls to INITPROC or INITPROC0 are replaced by calls to INITPROCEXE and INITPROCEXE0 respectively. These library functions produce smaller extended stack information blocks (68 bytes), because EXEs don’t need the Tron/Trace support. In addition, the mysterious call just below INITPROC is removed.

The actual code
The code that represents the actual code starts at the fifth line at $03C002E2. The actual code starts with a call to the tron-handler:

03C002E2:  call    dpt -76[ebp] ; @Tron

The address of the tron-handler is stored on the stack in the extended information block. If there is no tron procedure present the call immediately returns and nothing happens. Because calls to a tron procedure occur before a code-statement is executed we use that information to identify and examine the actual code. The first executable statement is result = x \ y, so its assembly code is found just below the first call to the tron-handler. Note that the Dim statement doesn’t produce executable code, declaration statements only introduce variables to the compiler.

We can now inspect the code for the division of x by y and the storage of the result in a local variable. Identifying the parameters is a bit more complicated now.

03C002E5:  mov     eax,dpt 20[ebp]   ; eax = x
03C002E8:  cdq                       ; clear flag
03C002E9:  idiv    dpt 24[ebp]       ; eax idiv y
03C002EC:  mov     dpt 120[ebx],eax  ; store eax in result

The x-parameter is accessed using 20[ebp] and y-parameter through 24[ebp]. This means that the stackframe has moved 8 bytes compared to the Naked attributed procedure. Exactly the number of bytes required to save esi and edi so that they can be used for Register Int types.
The local variable result is access though the value in ebx at 120[ebx]. As you can see the compiler generated code for an integer division (idiv).

The next statement assigns result to sStr: sStr = Str(result) Again, the statement is preceded by a call to the tron-handler. We can easily identify the code:

03C002EF:  call    dpt -76[ebp] ; @Tron
03C002F2:  push    eax          ; result of division
03C002F3:  scall   STRSTRI      ; integer to temp string
03C002F9:  push    eax          ; address of temp string
03C002FA:  lea     eax,124[ebx] ; address of string variable
03C002FD:  push    eax
03C002FE:  scall   STOSTRSV     ; assign to variable

The instruction push eax passes the result of the division, which is still in eax, to STRSTRI. The integer argument is converted to string and STRSTRI returns (in eax) a pointer to a temporary string. Both the temporary string and the local string variable sStr at 124[ebx] are passed to STOSTRSV to assign (attach) the temporary string to the variable sStr, which makes it a permanent string.

Finally, the code prints the contents of sStr to the window: Print sStr

03C00304:  call    dpt -76[ebp] ; @Tron
03C00307:  push    -1                    ; True, print CRLF
03C00309:  mov     eax,dpt 124[ebx]      ; address of stringdata
03C0030C:  scall   PRSEXPCR              ; print to window 
03C00312:  pop     edx          ; fix the stack      

The PRSEXPCR shows how GB optimizes library function calls. The calling convention of this function and many more is GB-specific, one argument is pushed on the stack and one is passed via eax. Passing arguments via a register is very common and is always faster than using the stack. VC++ uses ecx and edx to pass the first arguments to a __fastcall function and Borland uses eax, ecx, and edx with its fast function calls. Many GB library functions use eax and the stack, however there are also examples of GB library functions that use the VC++ __fastcall convention and use ecx and edx for argument passing. We’ll see this with local variable destruction functions in the exit code.

The function PRSEXPCR uses the cdecl convention and doesn’t cleanup the stack so it has to be corrected by popping the one argument.

The exit code
The EndProc statement is preceded by a call to the tron-handler as well:

03C00313:  call    dpt -76[ebp] ; @Tron
03C00316:  mov     ecx,dpt -16[ebp]         ; get saved ptr to prev SEH
03C00319:  mov     dpt fs:[0x00000000],ecx  ; remove us from SEH-list
03C00320:  lea     ecx,124[ebx]             ; address string variable
03C00323:  scall   CLEARSTR ; Ocx: $1807BA06
03C00329:  mov     esp,ebp                  ; restore the stack
03C0032B:  pop     ebp
03C0032C:  pop     ebx
03C0032D:  pop     edi
03C0032E:  pop     esi
03C0032F:  ret     8                  ; pop 2 parameters of 4 bytes

The first two lines remove the structured exception record from the thread’s SEH-linked list. The record was inserted when INITPROC created the ‘Extended Information Block’. Then, before leaving the procedure, the dynamic string has to be destroyed. The address of the string variable is passed in the ecx register to CLEARSTR which frees the allocated string memory.

The compiler inserts destruction code for all local variables with a dynamic datatype: String, Variant, Object (all COM objects), array and hash table. Before GFA-BASIC version 2.5 the destruction code for an array was only inserted if the proc contained at least one other local variable of type String, Variant or Object. Often, this required the addition of a dummy local string variable so the compiler was forced to generate the array’s destruction code. For hash tables the situation was even worse; a local hash wasn’t destructed at all. This has been fixed in update version 2.5.

Finally, the disassembly shows some more code below the the procedure’s return instruction.

03C00332:  push    ecx
03C00333:  lea     ecx,124[ebx]
03C00336:  scall   CLEARSTR ; Ocx: $1807BA06
03C0033C:  ret 
03C0033D:  nop    
03C0033E:  jmp     short 0x03C00332 
some more code that is actually data

Below the procedure’s return instruction the local variables destruction code is replicated. With a normal execution of the procedure, without exceptions, this code is never executed. It is only called by the OS when the structured exception handler tries to recover from an error and starts unwinding. Most importantly, the destruction code for dynamic types within the normal flow of the procedure must be replicated here.The rest of the disassembly contains information for Tron/Trace. Although these bytes represent data, the disassembler tries to produce assembly code. Actually, everything below the second ret instruction is data.

Regular procedures are separated into four parts: the entry code, actual code, exit code, and unwind code. The construction of the entry code is relayed to INITPROC(0). The statements in the actual code are preceded by calls to the tron-handler and they can be used to identify the statement lines. Library functions use a wide variety of calling conventions, it sometimes requires some puzzling to identify the arguments.
When you start analyzing procedure disassemblies you will encounter a variety of the same sort of code. The information presented in this and the previous post should help you in interpreting it.