27 August 2020

New features: my personal top 5

The past years GFA-BASIC 32 received some updates; bugs were fixed and new IDE features were added. If you checkout the readme25.rtf file that comes with the updates you might be overwhelmed by the number of (small) new features. To help you find your way I discuss my personal top 5 of the new IDE additions.

5 Print a Procedure
In a large program with many subroutines it is likely you want to print a single procedure rather than the entire program. To print the current subroutine – the one that contains the caret - choose the Procs button on the toolbar and select Print:

After selecting Print you will be presented the Windows Print dialog to select a printer and other options. Note that this is another fix, since the original GB only printed to the default printer.

4 Run As Exe
When developing a (new) program you should test the EXE version of the program on a regular basis. To make the process of compiling and testing a lot easier the toolbar offers the Run As Exe button. This button is enabled for a normal program and disabled when the editor contains a GLL or LG32. When you select the Run As Exe button the program is saved and compiled to exe. The first time you will be presented with the GFA-BASIC’s compile dialog box to specify the name and icon for the exe. If the program is compiled before and the exename is already specified the code is compiled with that name immediately and the dialog box is not shown. Also note that the dialog box contains a default icon for the exe.

When the compiling process did not generate any errors the EXE is executed directly. However, before executing the presence and version of of the GfaWin23.ocx runtime in the program’s directory is checked. In case there is a newer version of the runtime or if it is missing a message box will popup to inform you about the copying of the GfaWin23.ocx to the program’s directory. These days a DLL is no longer placed in the System32 directory, but accompanies the EXE in its own directory.

The new default icon for a GFA-BASIC standalone EXE contains 9 images to conform to the latest SDK requirements. A full set of icons includes images for 16x16, 32x32, 48x48, and 256x256. Windows selects the appropriate icon for the current DPI and Explorer View setting. The icons are taken from the multi-image app.ico file, which is located in the GFA-BASIC directory and is loaded when a program is compiled for the first time. The app.ico adds a resource of 41 KB to your program. Of course, a different .ico file can be loaded, but you should make sure to conform to UI-guidelines; an .ico format file of 766 Bytes is no longer the standard. Note - You can also use app2.ico (only 4 KB) from the GFA-BASIC directory which holds the same images but in a packed format. We did not encounter any problems with the packed version so far, so you could try it instead.

To support visual styles in your own programs the EXE must include a manifest file, either as a resource or as a stand-alone file. When Add Manifest Resource is checked the GfaWin32.exe.manifest is included as a resource after compiling automatically. The checkbox is only checked if the IDE is run with a manifest file. The checkbox setting isn’t stored in the program and is reset each time. To prevent the automatic inclusion of the manifest resource you should include the $ManifestOff directive in your code.

3 Insert miscellaneous text
Sometimes you want to store the program’s name and date into a comment line. By pressing App+I a popup menu appears that allow you to insert several kinds of information into the program’s code. These are the possible text items:

To easily insert the current procname into the code you can also use App+P. Inserting a procedure’s name is useful when displaying an error message box in a Try/Catch handler. The gfawinx function ErrMsg() is created with this feature in mind. ErrMsg returns the relevant properties of the Err-object in a single string ready to be displayed in a message box (see gfawinx.g32 in the Include directory for more info). The following picture shows when to press App+P to insert the proc’s name:

2 Grouping procedures
Actually this feature is my favorite, but I don’t use it as often as my number 1 favorite. To get a better overview of a large code file the procedures and functions can be grouped in collapsible groups. For this to happen a new editor command $Group has been implemented. To collect a series of procedures into a group add the $Group "title" statement just above a procedure in your code. As a result, the Procs tab in the sidebar will contain a collapsible group named “title” holding all procedures until the next $Group statement. A group can be removed by typing $GroupOff at the line with the $Group statement.

The picture above shows my grouping in the GfaWin32.gll program. Grouping allows me to collect related procedures into one section. It makes navigating the code very simple.
By right clicking in the Procs-tab listview you can fine tune the displaying of groups. For instance, the grouping can be (temporarily) disabled to display the procedures in the default manner. By default, groups outside the current the group are collapsed. This automatic collapsing of non-active groups can be disabled.

Hoovering over a procedure name (a Procs Listview-item) brings up a scrollable tooltip. The tooltip displays the code of that Proc. Use the mousewheel to show more or less lines. This also works in the editor. Hoover the mouse over a procedurename and Proc-PeekView pops up.

1 Edit history
My number one feature is Edit History, simply because I use it all the time without even realizing it. Edit History works in the background and saves the line last edited to the top of a stack. By using Esc or Ctrl+minus or the toolbar button < you are taken back to the lines you just edited. When stepping backwards, the steps are saved in a ‘forward stack’ so you can return easily to the last edited line using Ctrl+Shift+minus, or by clicking the appropriate > button in the toolbar.
The toolbar button for walking back has a collapsible menu that shows the line numbers and procnames you last edited. This way you can easily jump back to some line previously stored on the stack.

27 July 2020

Where are variables stored?

In the past months I got some questions that are perfectly suited for a blog post. One of those questions expressed a curiosity in the storage location of variables. In other words, where are the variables stored?
The location of variables depends on several things. First, there are global and local variables and they are handled differently. Second, there are non-dynamic variables – like simple numeric data types - and dynamic variables like strings, arrays and objects.

Global variables
To the compiler a variable name references a memory address. The declaration statements like Dim and Global enter the specified variable name(s) into a database which holds information about the declared global variables. At the same time, the compiler allocates some memory for the variable and when the compiler encounters the same variable again the variable is replaced by its memory address.

The compiler allocates a data section to store the global variables. When more global variables are entered in the database the data section will grow. When a program is run inside the IDE the compiler uses a simple malloc to allocate the data section. When a program is compiled to EXE the data section is saved with the EXE.

The amount of memory reserved for a variable depends on its data type. A Long (Integer32) variable is assigned a block of 4 bytes, a Word variable gets 2 bytes, etc. The following table describes the amount of memory that is reserved for non-dynamic variables.

Non-dynamic type           Memory requirement      
Bool 1 byte
Byte 1 byte
Word, Card 2 bytes
Integer 4 bytes
Single 4 bytes
Double 8 bytes
Currency 8 bytes
Date 8 bytes
Large 8 bytes
String * n n bytes

The location of a variable of a primary data type can be obtained with VarPtr, ArrPtr, V:, or the * – operator. These functions return the fixed memory address of the global variables. The contents of the global data section is cleared so that the value of each variable is zero.

The dynamic variables allocate their desired memory at runtime, for instance a string is dynamically allocated when it is assigned a value. However, for the compiler to handle a string it must be entered in the database and have some memory address assigned to it. In general, dynamic variables are assigned a Long (4 bytes) in the global data section to store the address of the – at runtime - dynamically allocated memory. This is also called a pointer. The data of a dynamic variable is stored elsewhere, not at the variable’s memory address. To obtain the storage address of the pointer use ArrPtr or the * – operator. The location of the data is only known at runtime and can be obtained using VarPtr or V:. These functions actually read the memory address returned from ArrPtr.
An OCX or Object variable receives a 4 byte pointer in the global data section and is initially zero (Nothing). The VarPtr function does not return the address of the object, but the location of the Object variable like ArrPtr.

An array is handled differently. A global array allocates an array descriptor with a size of 124 bytes in the global data section which contains information about the data type, number of dimensions (if specified in the declaration) and upper and lower bounds. The address of the descriptor can be obtained with ArrPtr, the actual memory locations of the array’s elements can be obtained with VarPtr.

The following table specifies the number of bytes required to store a pointer or descriptor for dynamic variables.

Variable type   Memory requirement
String 4 bytes
Array 124 bytes (descriptor)
Hash 8 bytes (descriptor: pointer + data type)
Object 4 bytes
Variant 16 bytes

For all dynamic variables the runtime uses malloc() to obtain the required memory. The malloc() function uses the Windows API HeapAlloc(), so the data for the dynamic variables is stored on the heap.

Local variables
Almost the same can be told for local variables, except that they are stored on the stack. When the compiler encounters a local variable declaration it calculates the offset to the stack pointer, which is then entered in the variable-database. Any reference to a local variable is then replaced with this offset value. The location of a variable of a primary data type can be obtained with VarPtr, ArrPtr, V:, or the * – operator. These functions return the calculated absolute memory address – relative to the stack pointer - of the local variables at runtime.

The only difference with global variable creation is the local array declaration. The compiler does not reserve a 124 byte descriptor relative to the stack pointer, but only a 4-byte pointer. When the program is executing the Dim statement allocates the descriptor and the required array memory. An array declaration without specifying a dimension only allocates a dynamic descriptor of 124 bytes at runtime. Note that the Erase command only releases the array elements, the dynamically allocated array-descriptor is not released. This conforms to the functionality of Erase for a global array where the descriptor is static and stored in the global data section.

Note – In GFA-BASIC 32 versions before 2.52 the automatic release of local arrays and local hashes did not work and the program suffered from memory leaks. The local hash could be freed using Hash Erase though, but there was no way to free an array-descriptor. Since then, the automatic release of arrays and hashes is fixed.

30 April 2020

Float to Integer–CInt & CintRZ

In the previous post Converting float to integer I discussed several possibilities to assign a floating-point value to an integer. The post discussed how the GFA-BASIC 32 compiler handles the different types of conversions; either with or without conversion functions. We saw that GB uses a default conversion that rounds to the nearest even number. When the fractional part of the number is exactly 0.5 the value is rounded down one time and up another time. For instance, 2.5 is rounded down to 2 and 3.5 is rounded up to 4. An application can change this behavior by using an explicit conversion function like Int(), Trunc(), Floor(), Round(), and QRound(). The blog post did not discuss CInt() and CIntRZ(), new functions added to GB 32 to easily convert VB(A) and C/C++ code.

CInt() converts and rounds to the nearest event number; the argument is converted using GB’s default setting of the FPU’s control register. Consequently, for floating-point arguments CInt() is equal to simply assigning a float to an integer:

Dim i As Int, f As Float = -3.5
i = f          ' assign directly, same as
i = CInt(f)    ' explicit conversion, i becomes -4

CIntRZ() rounds the argument down to zero and does what Trunc() does (for compatibility with C/C++).

Dim i As Int, f As Float = -3.5
i = CIntRZ(f)  ' round towards zero, i becomes -3

A Variant argument
The conversion functions like Int(), Trunc(), Floor(), Round(), and QRound() only accept numeric data types and variants for their arguments. The value passed is loaded into the FPU’s ST0 register and then rounded. (These functions also accept integer data types, but that won’t lead to anything useful.)
In case the argument is a variant it’s value is first converted to a floating-point data type (double), which is then loaded into ST0 and rounded. Since these standard GB functions now accept variants as well, the functions get to handle variants containing strings. The process is the same as with numeric variants, the variant-string must first be converted to a floating-point value as is required by these functions. This variant-string to double conversion uses the OLE function VariantChangeTypeEx() API from oleaut32.dll. The VariantChangeTypeEx() function handles coercions between the fundamental types including numeric-to-string and string-to-numeric coercions. One of the parameters of this function is the LCID value to use for the coercion. A LCID value is the locale identifier and specifies how dates, times, and currencies are formatted. The variant-string-to-double coercion uses the GFA-BASIC’s current LCID value. GB sets the LCID value to the user’s ‘Language and Regional’ settings when a program is started. For proper conversions the variant-string must contain a numeric value according to the locale settings. For instance, some European languages separate the integral and fractional part with a comma rather than a point. For instance, the following works with Dutch regional settings:

Dim i As Int, v As Variant = "2,5"
i = Trunc(v)  ' result is 2

If the variant v would contain a dot rather than a comma (“2.5”) the OLE conversion function ignores the dot and returns 25.

Note - you can change GFA-BASIC’s LCID value GB uses with the Mode Lang command.
Note - if you disabled the compiler setting ‘Don’t autoconvert numeric strings to values’ you could even pass a string data type to these standard GB functions. In this particular case the string is first copied to a hidden variant and then converted with VariantChangeTypeEx() to double. In most cases the compiler setting to not autoconvert is enabled (default setting) and the string data type is not accepted by the compiler for these functions.

The arguments of CInt() and CIntRZ()
CInt()
and CIntRZ() are capable of handling more data types than just numeric arguments as the other functions do. The documentation explicitly states that CInt() and CIntRZ() use an OLE function to convert the argument passed to the function. This is not entirely true, it depends on the data type of the argument. The OLE conversion is only applied if the argument is not numeric, ie String or Variant. For numeric arguments CInt() behaves exactly as the direct assignment of a float to an integer and CIntRZ() behaves exactly as Trunc().

As said, on the lowest level CInt() and CIntRZ() only accept floating-point values. Consequently, a string or variant argument must first be converted to a double (the default data-type for these functions). For numeric-variants GB extracts the value (to load in ST0 for conversion to integer) exactly as it handles the non-variant numeric values. In other words, GB does not use an OLE conversion function to extract a numeric value from a variant to coerce it to the floating-point data type.

Using a string argument
Interesting enough, CInt() and CIntRZ() accept the string data type for input. Before these functions process the float-to-int conversion the string must be converted to a floating-point data type. Because of VB(A) compatibility the string must be converted according the ‘Language and Regional’ settings; the conversion must use the LCID value. GB accomplishes this by first copying the string to a hidden temporary variant and than call VariantChangeTypeEx() to convert to double as input for CInt() and CIntRZ().

CInt() and CIntRZ() complement the standard GB function ValInt(). ValInt()converts a string to an integer according the Mode Val setting, CInt() and CIntRZ() use the regional settings for conversion. This is true for all C* conversion functions (CFloat, CDbl, etc) taking a string as input, the string is converted using the regional settings.

i% = ValInt("2.5")  ' = 2
i% = CInt("2,5")    ' = 2