19 July 2022

Numeric/string conversions

Ever wondered how numbers are printed? Check out the following snippet:

Dim i As Int = 2345
Print i

Before the number is printed to the active window, the number is converted to a string using a hidden call of the Str(i) function. The same is true for printing a Date (numeric) value; the date-value is converted to a string before it is output in the Debug Output window:

Dim d As Date = Now
Debug.Print d
Debug Str(d)            // short for Debug.Print

The output is (at the time of writing this blog) is:

18-7-2022 17:03:14
18-7-2022 17:03:14

The snippet demonstrates that the date is converted using a hidden call of the Str() function. It is not possible to print any numeric value without converting it to a string first. GFA-BASIC 32 uses hidden calls to the Str() function to convert the numeric value(s). The need for a numeric-to-string conversion before printing to screen (or printer) is that the underlying output functions require strings. For instance, to put the value on the screen the Print command uses the API function TextOut(hDc, x, y, strAddr, strLen), which requires a string.

Another implicit to string conversion is demonstrated in this example:

Dim s As String, i As Int = 2345, d As Date = Now
s = i & " " & d
Debug.Print s   // shows:  2345 18-7-2022 17:12:44

The & string operator allows to concatenate numeric and string values without converting them to a string explicitly. Still, under the hood, everything is converted to a string first. A nasty habit of the Str() function is to insert a space in front of converted value (a VB compatible setting). To prevent the space insertion use Mode StrSpace "0" somewhere at the beginning of the program.

The Str() and CStr() functions
The Str() function converts the value argument using a classic-style method, the output of the function is not language dependent. The produced string does not contain locale info for punctuation to separate the thousands and decimals in the value. A floating point is converted to a string with a single dot as separator. If the value is too large or too small the exponent notation is used.

s = Str( 1 / 3)    // argument is a Double
Debug s            // 0.333333333333333
s = Str(_minDbl)   // a very small number
Debug s            // -1.79769313486232e+308

The output of Str() can not be manipulated. However, there is another function that converts the numeric value according the user's regional or language settings, the CStr() function.

Dim f As Float = 1000.2345
Debug.Print CStr(f)     // output: 1000,234

Here the output follows the regional setting for the Netherlands where the decimal part is separated with a comma. The CStr() function follows the rules defined by the OLE API function VariantChangeTypeEx() that handles coercions between fundamental data-types, including numeric-to-string and string-to-numeric coercions. One of the parameters of the API is the LCID, a value that uniquely identifies the language to use. GFA-BASIC 32 stores the LCID value at start-up, by querying the user's default LCID from the OS. The LCID value is stored in the Mode structure and cannot be changed directly. The only way to modify the internal LCID is by using the Mode Lang command. By specifying a 3 letter language abbreviation GB32 will change the internal LCID value accordingly.

Dim sMyLanguage As String = Mode(Lang)
Mode Lang "DEU"         // change to German, LCID is changed.

If you're not satisfied with the numeric format produced by CStr() you can switch to the Format() function. The Format() function not only allows language depending conversion, but you can fine tune the output for your particular needs. When your user inputs a value in a language dependent value, for instance by using a TextBox, your program must be able to convert the string to a numeric datatype in GFA-BASIC's internal format using one of the C*() conversion functions, like CInt($).

String to numeric
Converting from string to numeric, is done using the Val*() functions or the other C*()-conversion functions. The Val* functions accept strings containing numeric values in the classical format, ie. the only punctuation allowed is a dot in floating point values. To convert to a Double use either Val() or its synonym ValDbl():

Dim d As Double, sValue As String = "1000.234567"
d = Val(sValue)
d = ValDbl(sValue)

To convert to a 32-bit integer use ValInt() and for a 64-bit integer ValLarge().

The Val() functions are not suited for language dependent formatted values. In the next snippet, the number is formatted according the regional settings of the Netherlands and is then converted to a Double using CDbl().

Dim d As Double, sValue As String = "1.000,23"
d = CDbl(sValue)
Debug d         // output: 1000.23

CDbl() uses the same  API function VariantChangeTypeEx() to process the string and change it into a floating-point value. To parse a language dependent formatted numeric string use any of the following functions:

Bool = CBool(); Byte = CByte(); Currency = CCur(); Date = CDate(); Double = CDbl(); Short = CShort(); Integer = CInt() or Long = CLong(); Handle = CHandle(); Large = CLarge(); Single = CSng() or Single = CFloat(); String = CStr(); Variant = CVar()

These function not only parse strings, but can also be used to convert any other data-type using an explicit cast. For instance:

Dim i As Int, b As Bool = True
i = CInt(b)     // i becomes -1, same as i = b

These explicit casts produce the same code as simply assigning one datatype to another.

CStr() produces a language dependent formatted string using the current LCID value. The C* conversion functions use the LCID language value for conversion to a numeric datatype. Format() offers more possibilities to format a numeric value. Str() and Val*() function use classical formatted values.

25 April 2022

Variables and parameters

Let’s discuss some basic issues of variables and parameters and explain auto-complete information about variables and parameters.

This blog post is a sequel to: Where are variables stored?

Variable declaration
Before a variable can be used it must have been declared explicitly. A declaration introduces the variable-name into the compiler’s database. The declaration requires a name for the variable and a datatype so that the compiler knows what to do with that variable; an integer variable is handled completely different than a string variable. Usually, the type is specified in a declaration statement. If the type is omitted the variable gets the default type Variant.

Global i As Int, v      ' an integer and a Variant

Not specifying a type introduces a Variant that might quickly cause confusion and problems. Variant-operations use different functions than – for instance – integer and floating-point operations. When a Variant is used to store a numeric value, simply incrementing it would require the call of a special Variant-Increment function in the runtime. Incrementing a simple data type as Int and Float is an (almost) atomic operation and requires only one CPU or FPU instruction. Therefor, it requires some attention when declaring variables. An error is quickly made as shown in the next line:

Global pic1, pic2 as Picture  ' probably not wanted

This line declares two variables. Two Picture variables are required, but pic1 is a Variant!
Instead use this:

Global Picture pic1, pic2

Before the introduction of auto-complete it was difficult to note these errors. Now auto-complete shows you the type of the variable. Here the type of pic1 variable that was wrongly declared:

Screenshot 2022-03-04 Variant AC

Global, local and static
Other statements to declare variables are Local, Dim, and Static. The Local statement declares a variable that only exists in a procedure. When Dim is used inside a procedure it declares local variables, when it is used in the main part of the program it introduces global variables. (Note that the main part of the program can have local variables as well using the Local statement.) When a procedure returns the local variables go out of scope and the variables are removed from the stack or and the dynamic variables are released (their memory is deleted).
The variables declared with Static are global but only locally visible. They are not freed when they go out of scope, they keep their contents.

Initialization while declaring
Declaring a variable adds it to the compiler’s database, a declaration does not introduce any executable code! A common practice is to collect the declaration of global variables in a separate procedure often called Init or something like that. Note that such a procedure doesn’t need to be executed, i.e. called from the main-part of the program. The procedure would not contain any executable code.
However, this changes if the declaration is used to initialize the variable with a value:

Global s As String = "Hello"

Now the declaration statement contains executable code that needs to executed when the program is run. The statement introduces the variable s into the compiler’s database and produces code to copy “Hello” into the string variable. If the program uses a procedure to declare globals that also initialize the variables, the procedure should be executed when the program is run. The procedure must also be run if the contains array declarations.

A special case is the Static local-variable, which is usually initialized while being declared. The initialization code is executed only once: the first time the Static statement is executed. (This is accomplished by guarding the Static statement by a hidden global boolean variable. After executing the Static statement the hidden boolean is set to true and the statement is never executed again.) Here is an excerpt form gfawinx.lg32’s WinDpi function:

Function WinDpi(hWnd As Handle) As Long

  Static Long pfnGetDpiForWindow = GetProcAddress( _
    GetModuleHandle("user32.dll"), "GetDpiForWindow")

  If pfnGetDpiForWindow       ' works from Windows 8.1
    WinDpi = StdCall(pfnGetDpiForWindow)(hWnd)
    WinDpi = GetDeviceCaps(Screen.GetDC, LOGPIXELSX)

The pfnGetDpiForWindow is only initialized once with the function pointer to GetDpiForWindow() API or null if it isn’t supported. If the WinDpi() function is executed again, the pfnGetDpiForWindow variable is still pointing to the API or it is still null. If the API isn’t supported by the Windows version, the DPI of the screen-device context is returned.

Simple datatype parameters
When declaring procedure parameters you need to decide whether to pass a value or variable by value (ByVal) or by reference (ByRef). In general, a parameter is passed by value unless the passed variable needs to be modified. A by value parameter is pushed on the stack by the caller and popped from the stack by the called procedure. Passing a 32-bit integer by value requires 4 bytes of stackspace, passing a Variant by value takes 16 bytes (the size of a Variant).
When a variable is passed by reference the storage location of the variable – a 32-bit memory address -  is pushed on the stack. A Variant passed by reference takes only 4 bytes of stackspace. However, a by reference variable requires an additional step from the compiler: it needs to obtain the address of the variable before pushing it on the stack.

Dynamic datatype parameters
How about passing an array, hash, string, variant, or object (OCX) parameter? Well, an array is simple, it can only be passed by reference. A hash can not be passed without problems due to a bug in GFA-BASIC.

Passing a string by reference is faster than passing it by value. A by value string is first copied in the calling procedure and then the (hidden) copy is passed by reference. It isn’t possible to copy an entire string on the stack! Because the string is first copied, it takes a malloc to allocate the string memory and a memcpy to copy the string’s characters. So, it can be (much) faster to pass a string by reference, you only need to make sure you don’t change the contents of the by reference string parameter. 
Auto-complete cannot differentiate between these types of string parameters and always presents a string parameter with the Ref clause.

Screenshot 2022-03-11 085036

A COM object variable is best passed by value, it only takes 4 bytes to pass the contents of a COM variable. A COM or Ocx variable is a 32-bits integer pointing to the actual COM object. The only need for a by reference COM parameter is when the object must be set to Nothing.

Passing a Variant by value may cause trouble and even a program crash if not handled properly. The rule of thumb is:

Don’t write to a by value Variant parameter (don’t use the by value variant parameter as a convenient extra local variable).

Explanation of variant parameter issue
Often a subroutine parameter is used as an extra local variable that can be written to. For instance, the ByVal s parameter in the procedure foo above can be used to temporarily store a string, s is a copy of the string passed to the procedure. Writing to s won’t affect the string in the caller. A variant containing a string that is to be passed to a procedure by value does not copy the string before invoking the procedure.
Dim vnt = "Hello"
foo(vnt)        ' by value
Trace vnt       ' wrongly displays Hello

Proc foo(ByVal v As Variant)

This code sample produces problems. The vnt variable stores a pointer to an OLE string containing “Hello”. When passed by value the parameter v is a copy of vnt, a 16 bytes data-structure with type information (VT_BSTR) and a pointer to the OLE string “Hello” on the stack. Assigning a new string to v will release the OLE memory currently pointed to by v. The new OLE string’s memory address is stored in v, together with the new data type (again a VT_BSTR). When leaving a procedure parameters aren’t cleared, so the foo procedure does not free the new contents of v. The variant’s 16 bytes occupying the stack are simply popped off the stack, leaving the new OLE string unreferenced. After returning from calling foo there is nothing that holds a pointer to the new OLE string and the OLE memory will never be released, the program is leaking memory.

Now, why does Trace vnt display “Hello”? After executing foo, the vnt variable is still referencing the OLE memory allocated by the assignment of “Hello”. The OLE string was released in foo when the new string was assigned, but the original variable vnt is never updated. The variable vnt still references the memory bytes where Hello was stored, bytes that weren’t actually cleared when released. The variable vnt references released OLE memory. When vnt goes out of scope, at the end of the program, it is released by GFA-BASIC by calling the OLE system function VariantClear(). Since the variable vnt points to released memory, the program may crash.

The type of procedures and parameter-defaults
To declare a subroutine you can choose between a Procedure, Sub, Function, or FunctionVar. The rule of thumb here is to use a Procedure or Function, unless you explicitly need a Sub or FunctionVar. The Sub is needed for event procedures where the parameters are passed by reference, the default behavior for a Sub. However, using a Sub as a general procedure might cause problems due to a flaw in the default by reference behavior. See the link at the end of this post for more information. If you use a Sub for something else than event subs make sure to use an explicit ByRef or ByVal clause in the declaration of the parameters.

Default behavior of procedures and functions:

Type of subroutine  Default ByVal or ByRef  Default datatype  Default return datatype
Procedure ByVal Double -
Sub ByRef in event subs,
otherwise flawed
Variant -
Function ByVal Double Ref Variant
FunctionVar ByRef Variant Ref Variant

As you can see, a function’s default return type is a by reference Variant. This means that the caller of the function passes a Variant by reference, the variant is ‘owned’ by the caller. This is illustrated by the following example:

Proc foo()
  Dim v As Variant
  v = GetValue()        ' passes v by reference
Function GetValue()     ' default is variant
  GetValue = 10         ' this references v from foo

The GetValue = 10 assignment writes to the v variable from foo() directly.

Here you see the auto-complete information of the function-variable GetValue:

Screenshot 2022-03-06 101409

If the function’s return type is String, Object (or other Ocx type) or a user defined type, the caller passes a by reference variable to the called function.
A function cannot return an array or Hash datatype.
Note - Each function automatically gets a ‘local’ variable with the function’s name and type. This function-variable can only be used to assign a value, it is not available as a real local variable that can be used to read from; consequently auto-complete won’t show the function variable in a ‘read-context’.

Pay attention when declaring variables to not introduce unwanted Variants. Explicitly use ByVal or ByRef when declaring subroutine parameters, and also explicitly specify the datatype (either by name or by postfix). Auto-complete always shows by reference for string parameters, even if they are passed by value. The default return type of a function is a by reference Variant. Don’t use a Variant parameter as a general local variable, ie. don’t write to the variant.

See also: Function and Sub parameters & Passing default ByRef parameters to a Sub

13 March 2022

Update 2.62 March 2022

A picture says more than 1000 words, therefor a screenshot with notes of improvements and fixes of the IDE (running on Windows 11):

Update 262

Runtime Updates   
Since last year virus-scanners report the patched runtime (GfaWin23.Ocx) as malicious - a false positive! Consequently, it is no longer possible to release a runtime binary with bug fixes. A new way of fixing bugs had to be introduced. Starting with this update version 2.62 the runtime bugs are fixed on the fly, they are patched when the program is run (F5). For this to happen each new program automatically inserts the following lines:

$Library "UpdateRT"
UpdateRuntime      ' Patches GfaWin23.Ocx

The $Library “UpdateRT” command loads the compiled code with the exported procedure UpdateRuntime. The UpdateRuntime should be the first statement executed, so that the runtime is fixed before the actual program is run. By taking this approach we can continue fixing runtime bugs!
You can inspect the source code in UpdateRT.g32, which is stored in the Include directory.
For older programs these lines have to be added manually, so that they benefit form the fixes as well.

See also: Update 2.6 produces a false positive (virus scanner)

Fixed: the Branch-Optimization compiler bug
This update sets an important step forward by fixing the compiler’s Branch-Optimizing bug. When Branch-Optimizations was set to any other value than zero the program could crash in one particular situation: when a Case or Otherwise statement was directly followed by an Exit command or one of its variants (Exit, Exit For/Do, Exit Proc, etc). The EXE crashed also if ‘Full Optimization for EXE’ was set. Now this problem is fixed, your program can benefit from full branch optimization, it reduces the size of the program by 10% and increases performance.

A couple of new IDE features.

  • The editor now shows format lines (toggle with Ctrl+F7) and you can use PeekView to hoover over format lines to see what statements are connected by a format line.
  • New projects can be started from a template, ranging from a simple Hello World app to a sophisticated dpi-aware application. The templates can be found in the File | New menu-item.
  • AC (auto-complete) comes with many improvements.
  • In dark-mode the proc-line color is changed and the auto-complete list box is displayed in dark mode as well.

See Readme262.rtf for more fixes and improvements.

New Commands/Functions in gfawinx.lg32

  • PrevInstance helps in starting a program only once.
  • DlgCenter centers all GB32 dialog boxes in the main thread on top of their owner (Me).
    DlgCenterHook is used to center the dialog boxes in a additional threads.
  • WorkWidth & WorkHeight return the real size of the clientarea of a Form with a toolbar and/or statusbar.
  • OffsetXY sets the graphical offset using positive values for GB32 graphical commands.
  • VStrLen, VStrLenB, and VStrPtr return the length (in character or bytes) and the address of a string in a Variant.
  • AutoFreePtr returns a COM object for storage of handles and (memory) pointers that need proper releasing, even when the program halts with a runtime error or Stop/End. The COM object has a Ptr (read/write) property to read and update the resource after the object is created.  

See the updated CHM help-file for detailed information about these new features.
Note – gfawinx.lg32 is loaded automatically in new projects. Optionally, this can be disabled in the Extra tab of GFA-BASIC 32 Properties.

The new App_Close event
Probably the most important new feature of this update is the introduction of an application close event: the App_Close event sub. This event sub is automatically called after the program is closed. It provides a way to free global resources that might never be released otherwise. This happens when developing a program and the program stops abruptly with a runtime error. This happens all the time. When a runtime error occurs the IDE shows a message box telling about the error and puts an arrow on the line that caused the error. Maybe you don’t always realize, but after execution stopped no more code is executed and allocated resources won’t be released. For instance:

OpenW 1
' Create global resources
Until Me Is Nothing
' Delete resources: might never be reached!

The releasing of resources also fails when a program ends with the End or Stop statement, no more code is run after these commands. Any API handles, memory pointers, bitmaps, and other global resources are not released and the program is leaking memory. When the program is run again and then stops again in the middle of the execution the leakage accumulates and soon you might experience unexpected errors. Even more, there are situations where the program can’t be run again, because of locked handles (for instance mutex handles). By using App_Close this leaking is over:

OpenW 1
' Create global resources
Until Me Is Nothing

Sub App_Close()
  ' Delete resources: guaranteed to be released!

Now put the program-lines that free the resources in the App_Close sub and the global resources are guaranteed to be released in all situations. 

The AutoFreePtr object
The implementation of App_Close is only possible through the introduction of the AutoFreePtr object. Instead of using the App_Close event sub a resource can also be assigned to an AutoFreePtr, which will call a predefined or custom procedure to free the resource. Because App_Close can only be used for globally stored resources, any local resource can be automatically released by using an AutoFreePtr. For instance, when a procedure temporarily allocates some memory that needs to be freed at the end of procedure:

Proc Something
  Local pmem As Long, Mem As Object
  pmem = mAlloc(100)            ' alloc some memory
  ' Allocate an AutoFreePtr object and
  ' let it call mFree() automatically
  Set Mem = AutoFreePtr(pmem, AfpMfree)
  ' use pmem or Mem.Ptr
  pmem = mShrink(pmem, 50)      ' resize memoryblock
  Mem.Ptr = pmem                ' assign new pointer
EndProc                         ' No mFree call necessary!

The AutoFreePtr object provides a Ptr property (Long - Get/Put) to read the assigned pointer or handle and to re-assign a new pointer/handle. 
The AutoFreePtr can free several predefined pointer or handle types. By specifying an Afp* constant you can instruct the AutoFreePtr object to invoke that specific release function for you. See the helpfile for more info on which types can be freed automatically.
Instead of specifying a constant to execute a predefined freeing-function, you can set a custom procedure to call when the AutoFreePtr object goes out of scope.

Proc Something2
  Local pmem As Long, Mem As Object
  pmem = mAlloc(100)            ' alloc some memory
  Set Mem = AutoFreePtr(pmem, ProcAddr(FreeMem))
  ' use pmem or Mem.Ptr
EndProc                         ' mFree executed in FreeMem()
Proc FreeMem(ByVal ptr As Long)

Start using App_Close and AutoFreePtr to develop without leaking memory and handles.

Download the new update: GFA-BASIC 32 for Windows: Download