19 March 2026

Fitting a Window to its monitor

How to detect when a window moves to a smaller screen and automatically adjust its size and position to fit.

Modern Windows setups routinely mix monitors of different sizes and resolutions. Drag a window from a large 27-inch primary display onto a smaller 24-inch secondary screen, and it may overflow the work area — clipped behind the taskbar, or hanging off an edge entirely. In a DPI-aware app Windows can help, but in a classic - non-dpi aware - GB32 application we're on our own. Fortunately, the Win32 API gives us everything we need to solve this cleanly in a few dozen lines of GFA-BASIC.

The two API calls that do all the work
The whole technique rests on just two USER32 functions:

MonitorFromWindow - Returns a handle identifying the monitor a given hWnd is currently (mostly) on. Pass MONITOR_DEFAULTTONEAREST so a window that's off-screen still maps to the nearest monitor rather than returning zero.

GetMonitorInfo - Fills a MONITORINFO structure for a monitor handle. The key field is rcWork — the usable rectangle after subtracting the taskbar and any docked toolbars.

Both are part of the multi-monitor API introduced in Windows 98 and fully available in every Win32 environment. GB32's $Library "winuser.inc" directive imports them along with the required types and constants.

Detecting the monitor change
Because GB32 is not DPI-aware, Windows will never send WM_DPICHANGED. Instead, we watch WM_MOVE and compare monitor handles. A monitor handle is stable for the lifetime of a display configuration, so a change in handle value is a reliable signal that the window has crossed onto a different screen.

We capture the initial monitor handle at startup, then re-check it on every move:

' Adjust window size for monitor

' Include API defintions from USER32.DLL
$Library "winuser.inc"

' Create a window & store it's monitor handle
FullW 1
Global Handle hMonWin1 = _
  MonitorFromWindow(Win_1.hWnd, MONITOR_DEFAULTTONEAREST)
Do
  Sleep
Until Me Is Nothing

' Handle WM_MOVE
Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)
  Local Handle hMon
  Switch Mess%
  Case WM_MOVE
    hMon = MonitorFromWindow(hWnd%, MONITOR_DEFAULTTONEAREST)
    If hMon != hMonWin1
      FitWindowToWorkArea(hWnd%)
      hMonWin1 = hMon
    EndIf
  EndSwitch
EndSub

' ============================================================
' Fit a window into a work area after a monitor change
' Shrinks if necessary, moves if partially off-screen.
' All values in screen pixels.
' ============================================================
Sub FitWindowToWorkArea(ByVal hWnd As Handle)
  Dim mi     As MONITORINFO
  Dim hMon   As Handle
  Dim rc     As RECT         ' current window rect (pixels)
  Dim wa     As RECT         ' work area
  Local Long wLeft, wTop, wWide, wTall, waWide, waTall

  ' Get current window rect in screen pixels
  ~GetWindowRect(hWnd, rc)

  ' Get work area of the monitor the window is (mostly) on
  hMon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST)
  mi.cbSize = Len(mi)
  If GetMonitorInfo(hMon, V:mi) = 0 Then Exit Sub
  wa = mi.rcWork

  ' Current size
  wLeft = rc.Left
  wTop  = rc.Top
  wWide = rc.Right  - rc.Left
  wTall = rc.Bottom - rc.Top

  ' Work area size
  waWide = wa.Right  - wa.Left
  waTall = wa.Bottom - wa.Top

  ' 1. Shrink if wider or taller than work area
  If wWide > waWide Then wWide = waWide
  If wTall > waTall Then wTall = waTall

  ' 2. Nudge so the window doesn't hang off any edge
  If wLeft < wa.Left Then wLeft = wa.Left
  If wTop  < wa.Top  Then wTop  = wa.Top
  If wLeft + wWide > wa.Right  Then wLeft = wa.Right  - wWide
  If wTop  + wTall > wa.Bottom Then wTop  = wa.Bottom - wTall

  ' 3. Apply only if something actually changed
  If wLeft <> rc.Left  Or wTop  <> rc.Top  Or _
    wWide <> (rc.Right - rc.Left) Or _
    wTall <> (rc.Bottom - rc.Top) Then

    ~SetWindowPos(hWnd, 0, wLeft, wTop, wWide, wTall, _
      SWP_NOZORDER Or SWP_NOACTIVATE)
  End If
End Sub

The three-phase fit algorithm
Once we know the window has changed monitors, we retrieve the new work area and apply a three-phase adjustment. Each phase is independent and only nudges values when needed:

Phase 1 - Shrink
Cap width and height to the work area dimensions. Handles windows that are simply too large for the destination monitor.

Phase 2 - Nudge
Push the top-left corner back inside the work area edges. Left/top checks run before right/bottom so a freshly shrunk window can't be pushed back off the opposite edge.

Phase 3 - Guard
Skip the SetWindowPos call entirely if nothing changed, avoiding unnecessary repaints and recursive WM_MOVE noise.

A few things to keep in mind
rcWork vs rcMonitor. MONITORINFO contains two rectangles. rcMonitor is the full physical screen including the taskbar area. rcWork is the usable rectangle — always use rcWork when positioning application windows.

GetMonitorInfo needs cbSize. The mi.cbSize = Len(mi) line is mandatory. The API validates the struct size before filling it, and will return zero (failure) if you omit it.

The V: prefix for structs. GB32 passes UDTs by reference automatically, but GetMonitorInfo is declared with a pointer parameter. Writing V:mi explicitly passes the address of mi, which is the correct calling convention for this API.

Minimum window sizes. If your window has a minimum size enforced via WM_GETMINMAXINFO, SetWindowPos will silently respect it. On a very small monitor the window may still partially overflow — this is expected behaviour and the right trade-off over making the window unusable.

SWP_NOACTIVATE. Including this flag prevents SetWindowPos from stealing focus while the user is mid-drag, which would cause a jarring activation flash on the destination window.

07 January 2026

Set and reference counting

You're probably familiar with the Set command for working with objects. In GB32, Set plays a crucial role when working with COM (Component Object Model) objects, and understanding what happens behind the scenes will help you write more reliable code.

What is a COM Object?
Think of a COM object as a box sitting somewhere in your computer's memory. Inside that box are two things:

  • A reference counter - a simple number that tracks how many variables are currently "pointing to" this box
  • The actual data and methods - the useful stuff your object does

When you create a COM object in GB32, what you actually get is a pointer - essentially an address that tells your program where to find that box in memory.

COM Variables Are Pointers
When you declare a COM variable in GB32:

Dim MyForm As Form

You're not creating the 'box' or Form itself. You're creating a variable that will point to the box. The variable contains just an address, not the entire object. At this point, MyForm doesn't point to anything yet - it contains Nothing.

To actually create the Form object, you use the Form command:

Form MyForm = "My Window", 100, 100, 640, 480

This single command does two things: it declares the variable MyForm and creates the actual Form object in memory, with the variable pointing to it. The Form now exists with the title "My Window" at position (100, 100) with dimensions 640×480.

The Reference Counter
The reference counter inside the COM object is like a visitor log. Every time a new variable points to the object, the counter should go up by one. Every time a variable stops pointing to it (goes out of scope or gets reassigned), the counter should go down by one. When the counter reaches zero, the system knows nobody is using the object anymore and it's safe to destroy it and free up that memory.

Interestingly, when you create a Form object, its reference counter starts at 2, not 1. This is because two variables point to it: the variable you declared (MyForm in our example) and the special Me keyword, which always points to the Form object from within its own event handlers and methods.

How Set Works
The Set command does two important things:

Dim MyForm As Form
Set MyForm = Win_1
  1. If MyForm was already pointing to something, it tells that old object "I'm done with you" (decrements its reference counter, unless it is already 0)
  2. It makes MyForm point to the new object (Win_1) and tells that object "I'm using you now" (increments its reference counter)

This keeps the reference counting accurate so the system knows which objects are still in use.

You can also use Set with the special keyword Nothing:

Set MyForm = Nothing

Nothing means "point to no object at all" - it's like erasing the address from your variable. When you set a COM variable to Nothing:

  • If MyForm was pointing to an object, it tells that object "I'm done with you" (decrements its reference counter)
  • MyForm now contains no valid address - it's empty and safe to check with If MyObject Is Nothing

Setting variables to Nothing when you're done with them is good practice. It explicitly releases your reference to the object, potentially allowing the system to free up memory sooner rather than waiting for the variable to go out of scope. It also makes your code clearer by showing when you're intentionally finished with an object.

Set MyObject = CreateObject("Some.Component")
' Use MyObject...
Set MyObject = Nothing  ' Done with it now

Passing COM Variables to subroutines
Here's where GB32 does something interesting. When you pass a COM variable to a procedure. Here is test program that allows you to obtain the current reference count of an object. The RefCount() function takes the address of COM variable and returns the current number of variables that reference the COM object:

Form frm1 = "RefCount example", 30, 30, 400, 400

Print "After creation RefCount = "; RefCount(V:frm1)
testbyval frm1
testbyref frm1

Do
  Sleep
Until Me Is Nothing

Proc testbyval(frm As Form)
  Print "In testbyval RefCount = "; RefCount(V:frm)
EndProc
Sub testbyref(frm As Form)
  Print "In testbyref RefCount = "; RefCount(V:frm)
EndSub

Function RefCount(ByVal ComVarPtr As Intptr) As Long

  Local Intptr ComPtr = {ComVarPtr}     // address of COM object
  Local Intptr ComPtr_Vtbl = {ComPtr}   // address of the vtbl
  Local Intptr ComPtr_AddRef = {ComPtr_Vtbl + 4}  // address of AddRef
  Local Intptr ComPtr_Release = {ComPtr_Vtbl + 8} // address of Release

  // Increment reference count and obtain the new number of references
  ~StdCall(ComPtr_AddRef)(ComPtr)       // pass the this pointer
  RefCount = {ComPtr + 4} - 1           // the new count - 1
  // Immediately decrease the reference count
  ~StdCall(ComPtr_Release)(ComPtr)      // pass the this pointer

EndFunc

The testbyval procedure shows that GB32 makes a copy of the pointer (so the function has its own variable pointing to the same box), but it does not increment the reference counter. This means the counter doesn't reflect that the function is also using the object.

In most cases, this works fine - your original variable keeps its reference alive, so the object stays valid while the function runs. However, if something unusual happens during the function (like the original variable getting reassigned or the function triggering code that releases the last reference), the object could be destroyed while the function is still trying to use it.

This is something to keep in mind when writing complex code, particularly code that might call back into other parts of your program or handle Windows messages while a function is executing.

Summary

  • COM objects live in memory and contain a reference counter
  • COM variables are pointers to these objects
  • Set properly manages the reference counter when assigning objects
  • Nothing is used to clear a COM variable and release its reference
  • Passing COM variables to functions creates a copy of the pointer without updating the counter
  • The reference counter helps the system know when it's safe to clean up unused objects

Understanding these mechanics will help you write more robust GB32 applications, especially as your programs grow in complexity.

10 October 2025

Choosing the Right Min and Max Functions

When working with Min and Max functions in GB32, you might be tempted to use the generic Min() and Max() functions for all your comparison needs. After all, they work with any numeric type, right? While this is technically true, there's an important reason why GB32 provides dedicated versions like MinI(), MaxCur(), MinLarge(), and others—and understanding this reason will help you write more efficient and accurate code.

The Hidden Cost of Generic Min/Max
Here's what many developers don't realize: when you call the generic Min() or Max() functions, GB32 performs an implicit conversion of all parameters to Double precision floating-point format before making any comparisons. This conversion happens behind the scenes, and it comes with two significant implications.

First, there's a performance cost. Converting integers or currency values to floating-point representation takes time. For a single comparison, this overhead is negligible, but in tight loops or performance-critical code, these conversions can add up quickly.

Second—and more critically—there's a precision issue. Floating-point comparisons don't always behave the way you might expect with integer or fixed-point values. Consider this example:

' Using generic Max with Large integers
Dim largeVal1 As Large = Large 9007199254740993
Dim largeVal2 As Large = Large 9007199254740992
Dim result As Large = Max(largeVal1, largeVal2)

Because Max() converts these Large integers to Double, and Double can only precisely represent integers up to 2^53, you might not get the result you expect. The two values might appear identical after conversion to Double, even though they're different as 64-bit integers.

Type-Specific Functions to the Rescue
This is precisely why GB32 provides dedicated Min and Max functions for each numeric type:

MinI() / MaxI() / iMin() / iMax() for 32-bit Integers - performs integer comparisons directly
MinLarge() / MaxLarge() for 64-bit Large integers - uses full 64-bit precision
MinCur() / MaxCur() for Currency values - maintains fixed-point accuracy
MinDate() / MaxDate() for Date types - compares dates without conversion overhead
Min$() / Max$() for String comparisons - uses lexicographic ordering

When you use these type-specific functions, GB32 performs comparisons in the native format of your data. No conversion to Double occurs, which means:

  • Better performance - no conversion overhead
  • Exact results - comparisons use the full precision of your data type
  • Predictable behavior - what you see is what you get

As a rule of thumb:
Use Min() and Max() only when you're genuinely working with Double values or when you need floating-point comparison semantics
Use the type-specific functions (MinI(), MaxCur(), MinLarge(), etc.) whenever you're working with integers, currency, or dates
If you're working with String data, use Min$() and Max$() for proper string comparison

Your code will be faster, more accurate, and your intent will be clearer to anyone reading it. The generic Min() and Max() functions are convenient, but the type-specific versions are almost always the better choice when working with non-Double data types.

Remember: in GB32, choosing the right function for your data type isn't just about style—it's about correctness and performance.

10 June 2025

Pointer (1)

With the Pointer command, Pointer() function, and Pointer type, GFABASIC-32 features a powerful tool. With the Pointer command you can assign a Pointer type variable to a memory address.The Pointer type variable is 32-bits integer variable that stores the address of some memory; it is a memory pointer. To see it in practice look at this code:

Dim i As Int = 9
Dim p As Pointer Int
Pointer p = V:i
Debug p                 ' displays 9

The variable p is a pointer to a type Int. From this declaration GB32 knows how to interpret the pointer p; it is pointing to an Int. Initially, the pointer variable p is 0, it is not assigned a memory address yet. You can obtain the memory address it is pointing to with the Pointer() function.

Debug Pointer(p)        ' displays 0

To be useful, it must be assigned a memory address and the contents of the memory address is then interpreted as an Int type. To assign a memory address use the Pointer p = addr command. The Debug command shows that p points to the memory address of the integer variable i, because it displays the value of i.

ByRef & Pointer are identical
Because the pointer variable is exactly the same as a ByRef variable, the auto-complete info shows a Ref indication. A by reference variable/parameter is also a (local) variable without an address. When a variable is passed to a ByRef parameter, the address of that variable is assigned to the by reference (pointer) variable. Because the compiler checks for the type of the passed in variable, you cannot assign a random memory address to a by reference parameter. With the Pointer type you can though.

Practical example: ownerdraw
The Pointer type comes to a practical use with Windows system messages that are handled in the MessageProc() event sub.One example is the ownerdrawn message WM_DRAWITEM. Ownerdrawn is used with controls and menu-items, to draw the items yourself. The WM_DRAWITEM specifies a pointer to a DRAWITEMSTRUCT structure in the lParam. In C/C++ this is written as DRAWITEMSTRUCT* or LPDRAWITEMSTRUCT, meaning Long Pointer to DRAWITEMSTRUCT, where the 'Long' is a holdover from the 16-bits days. This description is easily translated to GB32 to declare a pointer variable:

Dim pdi As Pointer To DRAWITEMSTRUCT

Since the DRAWITEMSTRUCT is defined in the Windows winuser.h header file, you can find the GB32 definition in winuser.inc.lg32. which must be included before the structure can be used:

$Library "winuser.inc"

In the MessageProc event sub the WM_DRAWITEM message is used like this:

Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)
  Switch Mess%
  Case WM_DRAWITEM
    Local pdi As Pointer To DRAWITEMSTRUCT      // = null
    Pointer pdi = lParam%

    // Do your drawing here

    retval% = 1                 // handled the message
    ValidRet? = True            // retval% is a valid value

    // other messages
  EndSwitch
EndSub

The local pointer variable pdi is 0 initially. By using the Pointer command the pdi variable is set to the DRAWITEMSTRUCT passed by the system in the lParam%. From that point you can use the pdi variable as any other user-defined variable to get access to a member. For instance, to obtain the device context use pdi.hDC.
If you handled the message, the system must be notified by returning 1, which is the C/C++ value for TRUE. To force GB32 to return the value set in retval% you must set the ValidRet? boolean variable to True.

The next time I will explore the use of Pointer with converting a C/C++ source using pointers.

04 February 2025

The 'Owned' property



You will have noticed the Owned property in the Properties sidebar for a Form. You might also have noticed that there isn't the same property when editing; the auto-complete box doesn't show an 'Owned' property.
A Form's owner
Whenever a Form is created it can be 'assigned' to another Form. When this happens the Form is said to be owned. Using the OpenW command, you can explicitly specify an owner:
// Create the first unowned form
OpenW 1
// Create the second owned form
OpenW Owner Win_1, # 2, 0, 0

When a Form is created using the Form editor, it is impossible to explicitly specify an owner form. All you can do is set the design-property 'Owned' to True.The effect will be that the Form is assigned to the current Me at the time it is created using the LoadForm command. (If you enable the Owned property for the first form, it will be assigned to Me = Nothing.and the form isn't owned. Since Me might not be exactly what you want it to be, you can explicitly set Me before executing LoadForm.

Owned windows
We mostly speak of owned windows when it concerns top-level windows, windows that are put on the desktop. The relation between a window and a child control is called a parent-child relation, it is not an owner-relationship. This concept can be seen from the properties of child Ocxes; they provide a Parent property, not an Owner property, only Forms have an Owner property.

When there is an owner relationship between two windows the first window (the owner) has certain control over the other window (the owned window). This relationship has several important implications:

  1. The owned window is always displayed on top of its owner window
  2. When the owner window is minimized, the owned window is automatically hidden
  3. When the owner window is restored, its owned windows are shown again
  4. When the owner window is closed, all its owned windows are automatically closed

This relationship is commonly used for dialog boxes that should stay above their parent window, but also for tool windows (or palettes) that should minimize/restore with their main application window. For example, when you open a "Save As" dialog from a text editor, that dialog is typically owned by the main editor window. This ensures the dialog stays above the editor and behaves appropriately when the editor is minimized or closed. There for the Dlg Save & Open command require a Form reference as their first parameter.

This is different from a parent-child window relationship, where child windows are confined within the client area of their parent. Owned windows can be positioned anywhere on the screen while maintaining their behavioral connection to their owner.

Conclusion
You should definitely consider using the Owned design-time property when using the LoadForm command, you get behavioral features for free and the application will certainly look more professional.

07 December 2024

CreateObject peculiarities (2)

Lately I received some COM-automation related questions which I answered by referring to this blog post: CreateObject Peculiarities (part 1). It advised to use the CoCreateInstance() API with the IID_IUnknown parameter to connect to an automation server. However, after re-reading I realized I could have demonstrated how to use CoCreateInstance. Below is an example that replaces the GB32 CreateObject() function with the custom made CreateObject2() function. It provides the same functionality as CreateObject and more. Where CreateObject only returns an object if it supports the IDispatch inteface, the CreateObject2() function has an optional parameter that takes any interface you want to request from the server. By default, it returns an object that  supports the IDispatch interface, and if that fails it returns the IUnknown interface.

The code is heavily commented, so I hope you will be able to understand it. As an example, CreateObject2 is used to obtain the IDispatch interface of the Scripting's FileSystemObject.

$Library "gfawinx"
$Library "UpdateRT"
UpdateRuntime      ' Patches GfaWin23.Ocx

Dim FSO As Object
' CreateObject2(ClsID [, IID]) replaces CreateObject(ClsID).
Set FSO = CreateObject2("Scripting.FileSystemObject", IID_IDispatch)
MsgBox0("Successfully created object.")
' FSO object is released when it goes out-of-scope.

Function CreateObject2(ClassID As String, Optional IID_Interface As Long) As Object
  '-------------------------------------------------------------------
  '  Like CreateObject creates an Instance of an OLE Server.
  '  The ClassID argument creates a class & specifies the OLE Server.
  '  The IID_Interface argument specifies the interface to create.
  '  There are two formats for the ClassID class argument:
  '   1. PROGID: "Excel.Application"
  '   2. CLSID: "{00000010-0000-0010-8000-00AA006D2EA4}"
  ' If a ProgID is used, the client's registry is used to get the CLSID
  ' If the optional IID_Interface parameter isn't used, a reference
  ' to the IDispatch interface is created. If that fails, a reference
  ' to the IUnknown interface is created.
  ' The IID_Interface is pointer to GUID type, created by GUID command.
  '-------------------------------------------------------------------

  Dim wProgId As Variant = ClassID          ' to Unicode
  Dim Ptr_ProgID As Long = {V:wProgId + 8}  ' string address
  Dim HResult As Long           ' COM error code
  Dim ClsID_ProgID As GUID      ' GUID is built-in type
  Dim fAskDispatch              ' set if II_IDispatch is asked

  ' Get CLSID (GUID) type from either GUID$ or ProgID$
  If Left(ClassID) = "{" && Right(ClassID) = "}" && Len(ClassID) = 38
    HResult = CLSIDFromString(Ptr_ProgID, ClsID_ProgID)
  Else
    HResult = CLSIDFromProgID(Ptr_ProgID, ClsID_ProgID)
  End If
  If HResult != S_OK Then _
    Err.Raise HResult, "CreateObject2", "Wrong CLSID"

  ' The default is to ask for IDispatch (like CreateObject)
  If IID_Interface = 0 Then IID_Interface = IID_IDispatch
  ' Remember whether IID_IDispatch is asked
  fAskDispatch = IsEqualGUID(IID_Interface, IID_IDispatch)

  ' Create a single instance of an object (ClsID_ProgID) on
  ' the local machine that supports the requested interface.
  ' Store the instance in the local function-return variable.
  HResult = CoCreateInstance(ClsID_ProgID, Null, CLSCTX_ALL, _
    IID_Interface, V:CreateObject2)

  // Only if asked for IID_IDispatch and it failed, try IID_IUnknown
  If HResult == E_NOINTERFACE && fAskDispatch
    IID_Interface = IID_IUnknown
    HResult = CoCreateInstance(ClsID_ProgID, Null, CLSCTX_ALL, _
      IID_Interface, V:CreateObject2)
  EndIf

  // If all requests for an interface have failed raise error
  If HResult != S_OK Then _
    Err.Raise HResult, "CreateObject2", "No interface"

  ' ------------------------------------------------------
  ' Global declarations section
  ' ------------------------------------------------------
  Global Const E_NOTIMPL = 0x80004001
  Global Const E_NOINTERFACE = 0x80004002

  ' -------------------------------------------------------
  ' GUID Identifier = value    (global declaration command)
  ' Generates a pointer to a 128 bit memory block containing
  ' a GUID value. So, IID_IUnknown is a Long holding the
  ' address of the GUID value.
  ' -------------------------------------------------------
  GUID IID_IUnknown  = 00000000-0000-0000-c000-000000000046
  GUID IID_IDispatch = 00020400-0000-0000-c000-000000000046

  Global Enum CLSCTX, CLSCTX_INPROC_SERVER, CLSCTX_INPROC_HANDLER = 2, _
    CLSCTX_LOCAL_SERVER = 4, CLSCTX_REMOTE_SERVER = 16, _
    CLSCTX_SERVER = CLSCTX_INPROC_SERVER + CLSCTX_LOCAL_SERVER + _
    CLSCTX_REMOTE_SERVER, _
    CLSCTX_ALL = CLSCTX_INPROC_SERVER + CLSCTX_INPROC_HANDLER + _
    CLSCTX_LOCAL_SERVER + CLSCTX_REMOTE_SERVER

  ' -------------------------------------------------------
  ' CoCreateInstance note.
  ' The GB GUID is a pointer to a GUID type in memory.
  ' To make it possible to use a GB-GUID, we should adjust
  ' the Declare to receive a Long holding the address.
  ' -------------------------------------------------------
  Declare Function CoCreateInstance Lib "OLE32" _
    (ByRef rclsid As GUID, ByVal pUnkOuter As Long, _
    ByVal dwContent As Long, ByVal pIID As Long, _
    ByVal ppv As Long) As Long
  Declare Function CLSIDFromString Lib "OLE32" _
    (ByVal lpszCLSID As Long, pclsid As GUID) As Long
  Declare Function CLSIDFromProgID Lib "OLE32" _
    (ByVal lpszProgID As Long, pclsid As GUID) As Long
  Declare IsEqualGUID Lib "ole32" (ByVal prguid1 As Long, ByVal prguid2 As Long) As Bool
EndFunc

01 October 2024

DEP and GB32

From MS:

"Data Execution Prevention (DEP) is a system-level memory protection feature that is built into the operating system starting with Windows XP and Windows Server 2003. DEP enables the system to mark one or more pages of memory as non-executable. Marking memory regions as non-executable means that code cannot be run from that region of memory, which makes it harder for the exploitation of buffer overruns."

I underlined the sentence that is important to GB32 developers. When a program is RUN (F5), the GB32 inline compiler creates an executable in memory and runs it from there. The DEP setting of your system can prevent the running of the code. This happened to a user who bought a new PC with a factory DEP setting of 3. Not only couldn't GB32 run the code, other nasty things happened as well, for instance programs couldn't be saved anymore.

To obtain the your system's DEP setting, you will need to follow the following steps:

  • Go to This PC -> right click and select Properties.Then select Advanced Settings and choose the Advanced tab, now click the Settings button of the Performance section. Here you can select the Data Execution Prevention tab. Normally, the option to protect Windows programs and services is selected. This conforms to DEP setting = 2.

You could also try this small GB32 program to obtain the system's DEP setting:

$Library "gfawinx"
$Library "UpdateRT"
UpdateRuntime      ' Patches GfaWin23.Ocx

Declare Function GetSystemDEPPolicy Lib "kernel32" ()
Debug "DEP-Setting: "; GetSystemDEPPolicy()

The fact that you can Run the code tells you that the DEP setting isn't 3. A setting of 3 wouldn't allow the execution of the code stored in memory by the GB32 compiler.

It isn't a problem solely for the GB32 developer, but the final EXEs created with GB32 will also suffer from a DEP setting of 3. The EXE is a stand-alone program and its code can be executed, but the UpdateRuntime function 'hacks' the GfaWin23.ocx runtime and reroute some code to new code in memory, and executing new code in memory is not allowed with DEP = 3.

Conclusion
To be able to execute a GB32 produced EXE the DEP system setting of the user must be 2 or lower.

03 July 2024

Function returning UDT

Last time we discussed why you better not use a Variant in a user-defined type. This time we look at functions that return a user-defined type. Let's look at a function - called MakeRect - that creates and returns a RECT (a built-in type). Here is non-optimized, but often used, version:

Dim rc As RECT
rc = MakeRect(0, 0, 100, 200)
Function MakeRect(ByVal x%, ByVal y%, ByVal w%, ByVal h%) As RECT
  Local rc As RECT
  rc.Left = x%
  rc.Top = y%
  rc.Right = x% + w%
  rc.Bottom = y% + h%
  Return rc
EndFunc

The function declares a temporary local variable rc and fills it's members with the passed values. Before the function ends the Return statement returns the local rc RECT variable to the caller. What actually happens is: the Return statement copies the contents of rc to a special local variable before it exits the function. Functions use a temporary local variable with the same name and type as the function declaration. In this case, the local variable is called MakeRect. Normally, the contents of the function-local variable is returned through the stack, meaning it is copied from the function-local variable to the stack.

In short, a Return value statement first copies the value to the function-local variable, it is then copied to the stack, and then the function returns. A two step process. Therefor, it is advised not to use the Return statement, but instead assign the return value to the function-local variable directly. When we change MakeRect function, it looks like this:

Function MakeRect(ByVal x%, ByVal y%, ByVal w%, ByVal h%) As RECT
  MakeRect.Left = x%
  MakeRect.Top = y%
  MakeRect.Right = x% + w%
  MakeRect.Bottom = y% + h%
EndFunc

As you can see, there is no local variable rc anymore, and no Return statement. The function is optimized for speed (and size, because there is less copying to do).

Last, but not least, GFA-BASIC 32 has another performance optimization. In case of UDT return values, GB32 passes the UDT variable as a byref parameter to the function. So, MakeRect actually receives 5 parameters. When GB32 creates the function-local variable MakeRect, it sets a reference to the hidden byref UDT parameter. So, when assigning values to the MakeRect function-local variable, we are writing directly to the global variable rc that is secretly passed to the function.

03 April 2024

Variant in an UDT?

Can you put a Variant in an user-defined type? It depends.

User defined type
A user-defined type is a data structure that can store multiple related variables of different types. To create a user-defined type, use the Type…End Type statement. Within the Type…End Type statement, list each element that is to contain a value, along with its data type.

Type SomeType
  Left As Long
  FirstName As String * 5
  Ages (1 To 5) As Byte
End Type

This code fragment shows how to define an user-defined type, it contains a 32-bit integer, a fixed string occupying 5 characters (bytes) and a 1-dimensional array of type Byte, also occupying 5 bytes. In total (theoretically) the user-defined type occupies 14 bytes. To get the actual size of the UDT you would use the SizeOf(SomeType) function, which reports a size of 16 bytes. The member Left is located at offset 0, FirstName at offset 4, and Ages starts at offset 9. Because the size of the structure is a multiple of 4, due to the default Packed 4 setting, the type is padded with empty bytes to fill it up until it is a multiple of 4. (A Packed 1 setting would return a size of 17.)

It is important to note that all data is stored inside the UDT. GB32 does not allow data to be stored outside the boundaries of the UDT. So, any Ocx type, Object type, or String type are forbidden, because they are pointer types that store their data somewhere else. (If it would be allowed, a Type with only one string member would have a size of 4, because only the 32-bits pointer to the string data would be reserved.) Because pointer types aren't allowed as Type members, GB32 does not call an 'UDT-destructor' when the UDT variable goes out of scope as it does with a String or Object variable as showed in the following sample:

Proc LocalStr()
  Local String s = "Hello"
  // ...
EndProc         // Destroys the string s

Now, suppose a dynamic String would be allowed in an UDT. When the UDT goes out of scope, GB32 would need to free the memory allocated by the string, which it cannot. In fact, GB32 does nothing at all when an UDT goes out of scope. A local UDT variable is located on the stack and GB32 simply restores the stack to its state it was before invoking the procedure.

Variant in UDT
Because of these restrictions, people are tempted to store a string in a Variant Type member, simply because GB32 does allow this. Then, when the Variant is then assigned a string (or an object) GB32 does not complain. However, when you assign a string to a Variant, the string is converted to a BSTR type and stored somewhere in COM-system memory (using SysAllocString*() APIs). Because GB32 does not release the Variant in the UDT, this memory is never freed. In addition, since the string memory isn't located on the program's heap, it won't even be released when the program is ended. It remains in COM-memory forever.

Conclusion
You can use a Variant in and UDT, but only to store simple numeric types. Dynamic data types (String and Object) are never released.

09 February 2024

What is the purpose of New?

The New keyword is used to create a COM object instance that cannot be created otherwise. In GB32 the most objects are created using the Ocx command, however not all COM types (Collection, Font, DisAsm, StdFont, and StdPicture) can be created this way.

The Ocx command is defined as:

Ocx comtype name[(idx)] [[= text$] [,ID][, x, y, w, h]

It performs two things:

  1. Declares a global variable name of the specified and predefined comtype
  2. Creates an object instance and assigns it to name.

The comtype is one of the GB32 provided COM types: a window based objecttype like Form, Command, but also Timer. Creating concrete objects of these types require initialization parameters, sometimes optionally, but the syntax must be able to accept additional parameters.

To create objects of non-window based types GB32 provides the New keyword to be used in a variable declaration statement. Here also, the two step process must be followed: declare a variable and assign the object instance.

Dim name As [New] comtype

Without the New keyword only a variable is declared and initialized to Nothing (0). New forces GB32 to create an instance (allocate memory for it) and assign that memory address to the variable name. In contrast with the Ocx command which creates global variables, these objecttypes can be declared and created locally. When the procedure is left the object is released from memory.

When declaring a COM type (Ocx or Dim) GB32 only reserves a 32-bits integer for the variable. The variable is a pointer to the allocated memory of the object and initially zero (Nothing). For instance:

Dim c As Collection

The variable c is a 32-bits pointer that can be assigned another instance of Collection by using the Set command.

Passing a COM object
An often asked question is whether an object variable should be passed ByRef or ByVal to a procedure. Since the variable is a pointer a ByVal parameter accepts a copy of the pointer and ByRef the address of the 32-bits pointer variable. In both cases GB32 knows how to handle the passed value, although a ByVal passed pointer outperforms a ByRef parameter. To access a ByRef parameter there is always an extra level of indirection.

Passing an object does not increment the reference count on the object.

20 October 2023

File Creation Date and Time

To read the file-time of a file GB32 offers the following functions:

Dim ft As Date
ft = FileDateTime(file$)        // last write time
ft = FileDateTimeAccess(file$)  // last access time
ft = FileDateTimeCreate(file$)  // creation time

They return a Date datatype with resp. the last write time. last access time, and the creation time.

A Date stores a date/time value as an 8-byte real value (Double), representing a date between January 1, 100 and December 31, 9999, inclusive.

The integer part of the Double represents the day:
- The value 2.0 represents January 1, 1900
- 3.0 represents January 2, 1900, and so on.
Adding 1 to the value increments the date by a day.

The fractional part of the value represents the time of day. Therefore, 2.5 represents noon on January 1, 1900; 3.25 represents 6:00 A.M. on January 2, 1900, and so on.
Negative numbers represent dates prior to December 30, 1899.

You can display the Double representation of the current time like this:

Dim dt As Date = Now
Debug CDbl(dt)

The FileDateTime* functions are wrappers around Windows API functions and from their name you expect the date of the last write, last access, or the creation time. For instance, the FileDateTimeCreate() should return the time at which a file is created, thus - for instance - the time a program executed the Open command to create a new file. (BTW the time of the file is not set before the Close command is invoked.) However, if the same file was created earlier, the date/time of the earlier creation is returned!
According to the MS documentation:

"If you rename or delete a file, then restore it shortly thereafter, Windows searches the cache for file information to restore. Cached information includes its short/long name pair and creation time."

Ok, that is a little disappointment, we cannot rely on FileDateTimeCreate to return the file's creation time. What's left is the most obvious function FileDateTime(), which is a kind of default function for a file's time. This first choice function returns the last write time, which seems to be the best thing to get the last time the file was created. The last write time returns the time the file is recreated, last updated or written to. So, you can use FileDateTime to obtain the 'last creation time', because FileDateTimeCreate returns the first creation time. To be sure I tested this by recreating an existing file created first at 3 March 2023:

Open "c:\tmp\test.txt" for Output As # 1
Print # 1; "Text"
Close # 1

After executing these codelines and subsequently choosing the file's properties in the Windows Explorer it indeed still shows a creation date of 3 March 2023. To force the file to a new creation date I changed the code with a Touch command:

Open "c:\tmp\test.txt" for Output As # 1
Print # 1; "Text"
Touch # 1
Close # 1

Now the Windows Explorer properties show the current date as the creation date. Using the Touch command will change all three filedates to the current time. To set each file date separately use one of the SetFileDateTime* functions after closing the file.

03 August 2023

File I/O using Get and Put

This time I want to put the focus on the Get and Put commands that were first introduced in GFA-BASIC 32. These are high-performance commands to save and load the contents of a variable in a binary format. The syntax for these commands:

Get #n, [index%], variable
Put #n, [index%], variable

The index% parameter specifies the record number, but it is only required for a Random mode file. A Random mode file needs a Len value when opened, which is the last parameter in the Open command:

Open pathname [For mode] [Access access] [share] [Commit] [Based 0/1] As [#]filenumber [Len=reclength]

When the index% parameter is used, the file pointer is positioned at index% * Len. For all other files (mode is Output, Input, Binary, Update, or Append) the index% parameter should be omitted and:

Get reads from the current file position.
Put writes the variable at the current position.

Depending on the Based setting in the Open command, which is 1 by default, the file position is actually calculated as (index% - Based) * Len. If Len is omitted the record length is set to 1. Consequently, when index% is used with a Len setting of 1, the index% value is multiplied by 1. Therefor, it is important to omit the parameter in non-Random files, so most often these commands are used like this:

Put # 1, , a$
Get # 1, , a$

The binary format
The contents of the variable is not saved as a string representation of the value like Print # and Write # do. The contents of the variable is saved in the same format as it is stored in memory. For instance, a Float variable is stored in 4 bytes and Put only copies those 4 bytes to the opened file. Get reads the 4 bytes and moves them directly into the Float variable. Saving a the floating-point as a 4 byte binary prevents the rounding otherwise necessary for writing a string representation using Write # or Print #. (BTW Print # isn't a suitable file I/O command unless you are saving a string.)

If the variable being written is a numeric Variant, Put writes 2 bytes identifying the variable type of the Variant and then writes the binary representation of the variable. So, a Variant holding a Float is saved in 6 bytes: first 2 bytes identifying the Variant as basSingle (4) and followed by 4 bytes containing the data.
If the Variant holds a string, Put writes 2 bytes to identify the type (basVString) , then 2 bytes specifying the length of the string, followed by the string data.

A variable-length string is saved likewise, only it omits the 2 bytes that specify the datatype. Put writes the length in 2 bytes, directly followed by the string data. The disadvantage is the limitation of 65535 characters, because that's the maximum value that can be stored in 2 bytes (Card).

File mode
Since Put and Get are binary I/O commands you might think they are restricted to Random mode or Binary mode files, but that isn't true. Binary I/O can happen with files opened in all modes, including Output, Input, Update, and Append.These modes do not differ much; opening a file with a certain mode is more of a reminder to the developer than that it defines an I/O operation. It is the file I/O command that defines the input-output format. Print # always writes a string, and Out # always write a binary value, whatever the mode of the opened file. Making a file Random allows Get and Put to read/write to a record directly with out setting the file pointer, otherwise it is the same as Update.

Conclusion
The Get and Put commands are fast binary I/O commands and are an interesting addition to the file I/O commands from previous versions of GFA-BASIC.

29 May 2023

Sleep doesn't wait?

Once in a while you might have created a quick and dirty program that repeatedly prints some value from inside the Sleep message loop. However, there is something funny about that. Shouldn't Sleep wait until there is a message in the queue? So, how can you print something repeatedly? How can Sleep return from its wait state?

The issue
The next small program shows the issue. It opens a window and enters the message loop while printing a dot each time. (I wanted to print the number of the message (_Mess) that is retrieved from the queue, but that isn't possible. The _Mess variable is set in the window procedure and is not part of the Sleep (GetEvent or PeekEvent) command. In addition, GB32 filters the message and not all message numbers are stored in _Mess. So, instead, we print a dot each time the loop is executed.)

OpenW 1
PrintScroll = True
PrintWrap = True
Do
  Print ".";
  Sleep
Until Me Is Nothing

According to the documentation Sleep should 'sleep' until there is a message available. However, the Print command is executed frequently at a pretty high speed printing dots in the window. This shouldn't happen, should it?

Now, lets copy the code above into the IDE, save it and execute the code as a stand-alone EXE. For this choose the 'Launch EXE' button on the toolbar:

SleepNoWait

Now we see something completely different: in the stand-alone EXE the Print command is not executed repeatedly; now Sleep does wait.

Difference between IDE and standalone EXE
When a program is run from inside the IDE the Sleep command doesn't seem to wait, because it returns repeatedly. However, the clue here is the word 'seem', because Sleep does actually wait (as does GetEvent), but it receives WM_TIMER messages at a pretty high speed. These WM_TIMER messages are missing in the stand-alone EXE. So, obviously, the IDE must be responsible for the WM_TIMER messages and indeed it is.

The IDE is to blame
The IDE uses several timers for different purposes. Well known timers are Gfa_Minute and Gfa_Second, that fire every minute or second resp. These two timers are disabled before a program is run (F5), so these are not the cause of the issue. However, there are two more IDE timers that are used to update UI elements like the toolbar buttons, for instance. Every 200ms the clipboard is checked to see if there is text available. If so, the toolbar's Paste button is enabled. Another timer is used to update the sidebar, this one fires every 100ms.

As it happens, these timers are not disabled before a program is run from within the IDE.These timers keep firing their WM_TIMER messages to the thread's queue, which are read by our Sleep in the program's message loop. Our program is executed in the same thread as the IDE and thus takes over the retrieving and dispatching of messages. While running a program, the IDE's message loop isn't executed any longer and all the messages for the IDE are obtained by the program's message loop. After quitting the program the IDE returns to its main message loop and takes over again.

Conclusion
Be aware that the number of times Sleep and GetEvent return from their wait state depends on the timer resolution of the IDE's timers that are still active while running the program.

04 January 2023

The danger of And and Or

The previous blogpost discussed the difference between And vs &&, and Or vs || in conditional statements. It showed why && and || should be used instead of And and Or. The post also contained an example where it is legitimate to use the And operator:

Local Int x = %111      ' binary notation of 7
If x And %11 > 0        ' test if lower 2 bits are set
  ' do something
EndIf

However, this condition is true for the wrong reasons. The condition is also true when only bit one is set. To prevent this and only check for the two lower bits we could change it into:

If x And %11 == 3       ' test if lower 2 bits are set

This seem to work ok, but the condition is still true for the wrong reason and that's gone haunt you. Let's try something different, let's test if bit 1 (value is 2) is set using the same method:

If x And %11 == 2       ' test if bit 1 is set
  Message "Condition is True"
Else
  Message "Condition is False"
EndIf

When we run this code, the program displays:

Screenshot 2023-01-04 071908

As you can see, the statement does not recognize that bit 1 is set (binary %10 == 2), despite the fact that the bit is 1.

So, what is going on here? Can't we trust And anymore? Maybe it is a bug? It is not a bug; it works like this is in any language and to understand this behavior we need to look at the operator precedence table. The helpfile contains the table under the topic name Operator Hierarchy:

( ) parenthesis
+ - ~ ! unary plus, unary minus, bitwise NOT, logical NOT
$ & explicit string addition
^ the power of
* / multiply, divide (floating-point)
\ Div Mul integer division, integer multiplication
% Mod Fmod integer and the floating point modulo
+ - Add Sub addition (the string addition, too) and subtraction
<< >> Shl Shr Rol Ror all shift and rotate operators (also: Shl%, Rol|, Sar8, etc.)
%& bitwise And
%| the bitwise Or
= == < > <= >= != all comparisons (also: NEAR ...)
And bitwise And
Or bitwise Or
Xor Imp Eqv bitwise exclusive Or, implication and equivalence
&& logical And
|| logical Or
^^ logical exclusive Or
Not bitwise complement

When an expression is evaluated the rules of operator precedence are applied. For instance, multiplication and division go before addition and subtraction. The table shows this by placing the * and / operators before (or above) + and -. Now look at the And operator; it comes after the comparison operators. This means that if an expression contains a comparison operator, it is evaluated (computed/calculated) before the And operator is applied. In our case, the part %11 == 2 is evaluated before And is applied. Since %11 = 3, 3 is not equal to 2 (results in False) and x And 0 is always 0, the result is 0 (False). These are the steps involved in the evaluation:

(Step 1) If x And %11 == 2 
(Step 2) If x And False
(Step 3) If x And 0
(Step 4) If 0

Can you now see why the original example form above results in True?

If x And %11 == 3       ' True for the wrong reason

The problem can be solved by using either parenthesis or %&:

If (x And %11) == 2     ' test if bit 1 is set
If x %& %11 == 2        ' GB way

In all other languages than GB the first solution is used. However GB defines an additional 'And' operator with a higher precedence that comes before the comparison operators: the %& operator. See the table for its location.

The Or operator suffers from the same problem, so GB defines an or-operator with higher precedence, the %| operator.

Is there a moral to this story? Try to avoid the use of And (&) and Or (|), unless - for instance - when translating C/C++ code to GB. (C/C++ does not support %& and %|.) Otherwise you might prefer the use of %& and %|.

16 October 2022

And / Or versus && / ||

Are you aware between the difference between And and && and Or and ||? And and Or are mathematical operators, while && and || are logical operators, so they are quite different. The only place where the && and || operators are used are in conditional statements like If, While, and Until. And  and Or are used in mathematical expressions (calculations) and are generally not meant to be used in the conditional commands  If, While, and Until.

The following example shows the difference:

Local Int x = 2, y = 3
' The inefficient way using And:
If x == 0 And y == 3
  ' do something
EndIf
' The efficient way using &&:
If x == 0 && y == 3
  ' do something
EndIf

The commands following the If statements aren't executed, but for different reasons.
The first If statement evaluates both expressions and then performs a mathematical operation on the result of both boolean expressions. The second If statement only evaluates the first expression and never bothers to check y == 3. Because And is a mathematical operator like +, -, *, / the expression x == 0 And y == 3 is completely calculated. First x == 0 is evaluated, which produces the boolean value False (0). Then y == 3 is evaluated, which returns the boolean value True(-1). After calculating both expressions the boolean results are combined by the And operator: 0 And -1 which then returns 0 (False), because only one expression is True. These are steps involved:

(1) x == 0 And y == 3
(2) FALSE And TRUE
(3) FALSE

The second If statement uses &&. Because && is not a mathematical operator the second expression is only evaluated if the first expression is True. Note that an AND operator requires both operands to be TRUE to result TRUE, as you can see from this AND table:

Value 1 Value 2 AND-Result
0 0 0
0 1 0
1 0 0
1 1 1

From the table you can see that if Value 1 equals 0 (FALSE) the result is always 0 (FALSE), despite Value 2. Only when Value 1 equals 1 (TRUE) Value 2 needs to be evaluated. The compiler produces code to do just that. It first evaluates the first expression x == 0 and because the result is FALSE the next expression y == 3 is never executed. This results in a considerable increase of performance, especially if the second expression calls a function. For instance, in the next example the function div3() is never called if x is 0 ( otherwise div3() would generate a division-by-zero exception):

Local Int x = 0
If x != 0 && div3(x) == 3
  ' do something
EndIf
Function div3(ByVal n As Int) As Int
  foo = 3 / n

The same is true for Or and ||. Look at this OR-table, the result is always TRUE if the first value is TRUE.

Value 1 Value 2 OR-Result
0 0 0
0 1 1
1 0 1
1 1 1

In the next example the second expression (y == 4) isn't evaluated because the first expression (x == 2) is already TRUE, making the result TRUE despite the result of the second expression.

Local Int x = 2, y = 3
If x == 2 || y == 4
  ' do something
EndIf

If we used the mathematical operator Or, first both expressions would be evaluated and then the boolean results would be or-ed, an unnecessary extra operation.

The And operator may have a purpose in a conditional statement, but it would be used as a mathematical operator in the expression, for instance to test if certain bits are set:

Local Int x = %111      ' binary notation of 7
If x And %11 > 0        ' test if lower 2 bits are set
  ' do something
EndIf

Here x (=7) is mathematically And-ed with 3 which results in 3, and 3 is larger than 0. The If statement will be executed.

Conclusion
Rather than using And and Or, use the logical operators && and || in conditional statements.

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.

Conclusion
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.