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.
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 ACGlobal, 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)
Else
WinDpi = GetDeviceCaps(Screen.GetDC, LOGPIXELSX)
Screen.ReleaseDC
EndIf
EndFunc
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.
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).
Dim vnt = "Hello" foo(vnt) ' by value Trace vnt ' wrongly displays Hello Proc foo(ByVal v As Variant) v = "GFA BASIC GFA BASIC GFA BASIC"
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:
otherwise flawed 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 EndProc Function GetValue() ' default is variant GetValue = 10 ' this references v from foo EndFunc
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日 101409If 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’.
Finally
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
04 July 2021
Interacting with Forms (Part 2)
This is the second post in a series about forms. In the first post we discussed the commands to create forms and their window styles. Now we’ll discuss how to interact with forms.
Owner and owned forms
The commands OpenW and Form support the creation of owned forms (windows). They provide the Owner clause to specify the ‘parent’ or owner of the new form. The following example creates an overlapped non-owned window (Win_0) and an overlapped owned window frm.
OpenW 0, 100, 100, 300, 300 : TitleW 0, "Owner" Form Owner Win_0, frm = "Owned", 150, 170, 300, 200 Do : Sleep : Until Me Is Nothing
There are advantages and disadvantages of using a parent-owner relationship.
- The owned form (frm) is always displayed on top of the non-owned window (Win_0). It is therefor mostly used for forms that behave as dialog boxes.
- When one of the windows is activated the other is activated automatically.
- When the parent window is closed the owned window is closed as well. Not the other way around. When you close form frm the Win_0 form is not closed and Me does not become Nothing. The message loop is not ended. When you close Win_0 the form frm is closed as well and Me will become Nothing.
- The owned window is not disabled (for input) if the parent is disabled. When a program displays a modal dialog box (a form with Ocx controls that disables all other windows while on screen) all other forms have to be disabled explicitly.
Suppose a program wants to display an About box in a modal dialog box. In GB32 a modal dialog box (form) is simulated by showing an owned form and then disabling input for the other forms using the Disable property. Then, after closing the modal dialog box form the other forms are enabled again using the Enable property. Like this:
OpenW 0, 100, 100, 300, 300 : TitleW 0, "Owner" Ocx Command cmd = "About", 10, 10, 100, 24 Form Owner Win_0, frm = "Owned", 150, 170, 300, 200 Do Sleep Until Me Is Nothing Sub cmd_Click ' Display frm1 (owned by Win_0) created using the Form-Editor LoadForm frm1, Win_0.Left + PixelsToTwipX(50), Win_0.Top + PixelsToTwipY(50) Win_0.Disable ' no input allowed frm.Disable ' no input allowed Sub frm1_Destroy ' frm1 is being destroyed frm.Enable ' enable input Win_0.Enable ' enable input Win_0.Activate ' activate
In the form-editor the Owned property is set to True. The form only has one Ocx control; a label with an enlarged font. The LoadForm command uses the current active form as the owner for the About box. The result is this, only the About box is active and enabled:
The message loop
To interact with forms the application needs a message loop. The most common message loop uses Sleep and ends with Until Me Is Nothing (Do : Sleep : Until Me is Nothing). When all windows are closed Me – the current active form - becomes Nothing and the program will end.
Sleep waits for a queued message; a message that is posted to the window’s message queue. The OS posts only a limited amount of messages, most messages are sent to the window-procedure directly. In general, the posted messages include the input messages for keyboard and mouse, the WM_PAINT and WM_TIMER message.
After Sleep detects a new message it dispatches (relays) the message to the window’s window-procedure. So, in the end all messages (posted or sent) end up in the window procedure of the window. The window procedure is responsible for executing the event subs.
Sleep is not the only command that reads the message queue, others are the (also new) DoEvents, and the older 16-bit GFA-BASIC compatible commands GetEvent, and PeekEvent. Sleep is the preferred command to wait for and handle a message. When the Sleep command executes it first processes all pending (waiting) messages and then puts the program to sleep using WaitMessage(). When a new message arrives Sleep processes all new message(s) before it returns back to the program. Schematically, from left to right Sleep executes:
Messages + Sleep(0) => return to program (loop)
As part of Handle All Events the Sleep command invokes the Windows API Sleep(0), giving up the remainder of the time slice that the scheduler assigned to the thread (process).
The other command that waits for a message is GetEvent, but this works a bit different. Upon entering the GetEvent handles one event if there is a message present in the queue. Without any pending messages GetEvent invokes WaitMessage() and halts the program. After obtaining a message it continues by processing exactly one event. If more messages are present GetEvent will process them the next time it is executed. Schematically, there are two situations:
Because GetEvent handles only one message a time it is able to place GFA-BASIC 16 compatible message-info in the MENU() -array. An application can respond to the values of the MENU() array inside the GetEvent-message loop. The GetEvent command does not invoke Sleep(0). With multiple messages pending the GetEvent loop will run until all messages are handled without giving up time, which is not a real problem. After all, the Sleep command does not call Sleep(0) before it handled all pending messages.
PeekEvent is like GetEvent: it fills the MENU() array, but it doesn’t wait for a message, it returns immediately to the program.
Because PeekEvent is usually placed in a (message) loop, it is likely that the CPU will be overloaded and will be running at 100%. You will need some mechanism to give up some processor time, for instance by using the Sleep(ms) API or the GB32 command Sleep ms inside the message loop.
DoEvents is a bit like PeekEvent: it doesn’t wait for a message. However, rather than processing only one message at a time, DoEvents handles all pending messages in a row before it returns to the program.
This does not mean that DoEvents - when used in a loop - will let the processor run at 100%. Before returning to the program DoEvents invokes Sleep(0) to give up CPU time to other processes. DoEvents is often used in a time-consuming loop to keep the GUI responsive.
All these commands Sleep, GetEvent, PeekEvent, and DoEvents handle the keyboard navigation between Ocx controls on the form. However before the keyboard navigation is applied GB32 invokes the Screen_Preview event sub if it is present. This event sub allows the program to intercept queued keyboard messages (WM_KEYFIRST – WM_KEYLAST) to respond to a key and/or cancel the message if it wants to.
Sub Screen_KeyPreview(hWnd%, uMsg%, wParam%, lParam%, Cancel?)
Set the Cancel? parameter to True when a keyboard message is handled and you don’t want to pass it on to the Ocx-control keyboard-navigator or to the window-procedure.
Event Subs
All message retrieval commands, including GetEvent and PeekEvent, execute event subs. (To be precise, the window procedure invokes the event subs.) Using event subs is the preferred way to respond to a message taking away the need to distinguish between posted and sent messages as in previous versions of GFA-BASIC. To handle a form-message simply add an event sub to the program. Note that when a form is being created it executes event subs while the form’s object variable isn’t initialized yet. In particular, the _Paint and _Resize events are invoked without having access to the form’s variable. This behavior is discussed in the previous blog post: Forms, the base of an app.
The _MessageProc sub event
A powerful event sub is the _MessageProc sub event. If defined, it is called for each and every message that is relayed to the window-procedure, including the posted messages.
' Called for all messages Sub Win_0_MessageProc(hWnd%,Mess%,wParam%,lParam%,retval%,ValidRet?)
You need to know what to do if you handle a message in the _MessageProc event sub. This knowledge is acquired through the Windows SDK (now available on line). For the sent messages the program needs to specify a return value in the by reference variable retval% and set the ValidRet? variable to True. This is necessary; if the program sets ValidRet? the message is considered handled and any event subs that would otherwise be invoked will not be executed. When ValidRet? is set the form’s window procedure will return to the caller (Windows OS) immediately. To handle a posted message – one that doesn’t require a return value - you needn’t set retval%, but to prevent further handling you should set ValidRet? though.
The _Message event sub is for posted messages
The _Message event sub is intended to process queued (posted) messages only. However, the _Message event sub is called for all messages (like the _MessageProc event sub) except for WM_PAINT and WM_ERASEBKGND (!?). Because of its syntax – it misses retval% and ValidRet? - it can only be used to process posted messages, the messages that don’t require a return value:
' Process posted messages only Sub Win_0_Message(hWnd%, Mess%, wParam%, lParam%)
Even when a program handles a message in the _Message event sub the event sub for that message is invoked as well. For instance, if WM_LBUTTONDOWN is handled in the _Message event sub and the _Click event sub is defined as well, the _Click event is also executed. If both _Message and _MessageProc subs are used both subs are invoked, first the _Message event sub followed by the _MessageProc. If a program responds to a (posted) message in the _Message event sub, it shouldn’t process it in _MessageProc again, or in some other event sub.
The practical use of the _Message event sub is limited, it is intended to port GB16 code that uses the MENU()-array or _hWnd, _Mess, _wParam, _lParam to GFA-BASIC 32. Because the _Message event sub is called from the window-procedure a program can handle all posted messages when the message loop uses Sleep or DoEvents.
25 April 2021
The gfawinx library
It’s been a while since GFA Software Technologies GmbH released GFA-BASIC 32. At that time (2001) it could not be foreseen how Windows - and PC-use in general - would evolve. Consequently, GB32 misses runtime functions to coop with the latest Windows developments and the introduction of better hardware. To fill that gap gfawinx.lg32 comes with a set of commands and functions to access and use the newer features. The name gfawinx stands for “GFA-BASIC 32 expansion” library, gfawinx contains several categories. When you load the gfawinx.g32 code file in the editor the Procs sidebar shows the current groups:
Screenshot 2021年04月23日 112115Download latest version
Previous gfawinx versions – until and including the version of 16 Feb 2021 - contain an error that may lead to problems when loading the library into a program. If you are planning to use gfawinx make sure you have a version later than 16 Feb 2021. Please check the date of the file in the Include directory.
Download a newer version if necessary here and unzip the file to the Include directory.
What’s the problem? One of the library functions uses a DLL function declared using the Declare command. However, when a lg32 library uses a declared function it must also export that DLL function using an $Export Decl command.The program that is importing the library must be able to add that declared function to the program’s table of DLL functions. Gfawinx used a declared function, but did not export it. The current version of gfawinx doesn’t use Declare anymore, but uses GetProcAddress() to obtain the function’s address and calls it using StdCall()(). Something to remember when you are developing your own libraries.
The String functions
The Wide and Ansi functions are mainly used with Windows API functions that take a UNICODE string. An example of both functions was discussed in Task Dialog as a replacement for Alert, which demonstrates the use of a Windows API taking UNICODE strings as parameters. This blog post also contained an example of StrToArr, which is used to convert a string, containing sub-strings separated with any character, to an array. StrToArr is an addition or replacement for the Array ar$() = $ command.
The Replace string function is a VB(A) compatible function, much easier to use then the Split command of GB32. The Replace function is optimized for speed and takes the string arguments by reference if the arguments are string variables. Only in case a string literal is passed GB32 will pass the string parameter by value (making a copy first and then pass that copy). This is achieved by making Replace a FunctionVar rather than a Function.
When a parameter of a FunctionVar or Sub does not explicitly specify ByVal or ByRef the parameter is anonymous. In this case the compiler decides how to pass the parameter to the FunnctionVar or Sub. If possible, the compiler generates code to pass the String or Variant by reference, otherwise the parameter is passed by value. To be able to pass by reference the caller must pass a variable; a literal is always copied to a temporary locally variable which is then passed by reference. Note that a ByVal parameter always gets a copy of the data that is passed, which requires the compiler to insert additional code to create a copy first. Passing by reference is almost always faster. Using anonymous parameters can speed up the execution of a procedure or function considerably.
An example that shows the difference:
$Library "gfawinx"
Dim src,ドル find,ドル new,ドル result$
result$ = Replace(src,ドル find,ドル new$) ' variables, fast
result$ = Replace("GFA-BASIC", "A", "x") ' literals, slower
ErrStr is a convenient function to obtain the relevant Err-properties as a string. Usually, ErrStr will be used in a Try/Catch handler to present information about the trapped error to the user. The problem is to decide which Err-properties contain the relevant error information. ErrStr examines the properties and only returns the properties that contain the actual error information. ErrStr takes one string parameter that is added to the error information. This parameter is perfectly suited for the current proc-name, which is easily inserted using the keyboard short cut App+P. Here App+P inserts “main” into text:
$Library "gfawinx"Screenshot 2021年04月23日 131437 Try Error 3 Catch MsgBox ErrStr("main") EndCatch
The program produces the following message box:
The $ = HexDump(addr%, size%) string function returns a memory dump starting from the specified address addr% and with a length of size%. The result of this debugging aid can easily be displayed in the Debug Output window or a MsgBox.
The Windows functions
Here we find the Windows API and form-windows related commands and functions. Let’s first examine the WinVer function which returns the Windows OS version. In the latest version of gfawinx it returns the value obtained using the RtlGetVersion() API in a hexadecimal format $XXYY, where XX represents the major version and YY the minor version:
$Library "gfawinx" Print Hex(WinVer()) // A00 (Win 10)
There is a lot of information to be found on how to obtain the Windows version. The method used by WinVer() does not depend on the manifest file as other methods do and should be safe to use with future releases of Windows. (Note - only the latest version of gfawinx - later than 16 Feb 2021- contains this method.)
GetWorkArea is a useful command when using multiple monitors where the Screen.Work* properties don’t work. GetWorkArea returns the work area of the monitor containing the largest area of a given window.
GetProcAddr returns the address of a function from a specified DLL. It works a little different than the GetProcAddress() API, because it first checks if the required DLL is already loaded.
The GetClientSize command returns the size of the client area, useful if _X and _Y are not available (in API windows).
ModifyStyle and ModifyExStyle make it easy to change the windows Style or ExStyle on the fly. They allow to both set and remove a windows-style simultaneously.
The Assoc() function returns system information associated with files, extensions or Prog IDs. For instance to obtain the full path of the default application that is associated with the .txt extension use:
$Library "gfawinx"
Print Assoc(".txt") // full path of default app
SaveFormPos and LoadFormPos save and load the form’s position respectively. For single monitor apps these are simple functions to store and retrieve the form’s current position in and from the registry. They are especially useful when used with multiple displays and DPI-aware applications. For detailed information see the help file.
MemStat is is a helper function to obtain memory statistics. If the argument is omitted (or is 0) it returns the memory used by the application. It is possible to trap a possible memory leak by using MemStat() when the program starts and compare the returned values at each start of the program.
The DPI-related functions
When you want to write DPI-aware applications these helper functions will be very useful. For more information on writing DPI-aware programs please see the English CHM helpfile: in the Contents section see Creating an application. Programming DPI aware programs is beyond the scope of this blog post. The gfawinx library provides the following functions.
WinDpi returns the DPI of the display for a window or form.
DpiAwareness set the DPI mode for the program.
ScaleToDpi(Sng) helps to scale coordinates.
ScaleXYWHToDpi scales ScaleLeft, ScaleTop, ScaleWidth, and ScaleHeight to a form's DPI.
The TimerQ function
The TimerQ function creates a high-resolution timer wrapped in a (minimal) COM object. This is the gfawinx implementation of the discussion in the blog High Resolution Timer . By using a COM object for a resource it can be guaranteed that the resource is freed when the program ends – either normally or through a runtime error.
Conclusion
The gfawinx library provides functions and commands that are not included in the GB32 runtime. The commands and functions are displayed in the GB32 syntax color for statements and GB32 functions. The procedures that implement the additions are optimized for speed, when possible the procedures are Naked. It is recommended that you – sometime - take a look at the code, it contains comments on implementation details you might find interesting. The library will grow over time when more ‘missing’ features will be added.
26 November 2020
The Naked attribute in practice
In the previous posts The Anatomy of a Procedure (1) and The Anatomy of a Procedure (2) I discussed the effect of the Naked attribute on the code generated by the compiler. Everything you want to know about the Naked attribute can be found in these posts, but – unfortunately – the posts are rather technical. If you lack any experience in assembly it might be hard to understand, so I will recap on the use of the Naked attribute in ‘layman’s’ terms here.
Naked explained
A Naked procedure is fully optimized, both in size and in performance. This comes with a penalty though, a naked procedure lacks support for dynamic variables types (String, Object, Variant, array and hash), structured exception handling, and runtime debugging (Tron, Trace). The reason for this is the lack of ‘procedure-housekeeping’ that GFA-BASIC 32 inserts in each regular procedure. In a regular procedure GB starts of by inserting a 80 bytes stackframe (68 in an EXE) to store all information necessary for housekeeping of the procedure. At the end of the procedure in insert code to restore the stack to automatically release all (dynamic) variables (even in case of a runtime error). The housekeeping code is missing in a naked procedure. Consequently, a naked procedure can execute faster, in certain cases up to 50% faster than a regular procedure. Only short procedures benefit from the Naked attribute; the executable code must be relative small compared to the code necessary to setup a stackframe of 80 bytes, as we will see. The example procedure from the previous post is a good example of a candidate for Naked, it executes 50% faster:
TestMul(2, 3) Proc TestMul2(x As Int, y As Int) Naked Local tmp As Int tmp = x Mul y EndProc
When the procedure grows and contains more executable code the relevance of Naked disappears. The next example shows two things. First, it declares a local dynamic string variable (which needs to be released explicitly by setting it to the ‘empty-string’). Secondly, the assignment of data to the string will allocate memory and produce code to copy the data to that memory. This is a relatively expensive operation and will reduce the possible performance gain of Naked.
Dim i%, t# t# = Timer For i% = 0 To 100000 : TestMul(2, 3) : Next Debug "Normal: "; Timer - t# t# = Timer For i% = 0 To 100000 : TestMul2(2, 3) : Next Debug "Naked: "; Timer - t# Proc TestMul(x As Int, y As Int) Local tmp As Int, s As String = "Something" tmp = x Mul y EndProc Proc TestMul2(x As Int, y As Int) Naked Local tmp As Int, s As String = "Something" tmp = x Mul y s = "" EndProc
The measured time for calling TestMul() and TestMul2() a 10000 times is
TestMul: ca 0.021 seconds
TestMul2: ca 0.019 seconds.
In this example, the time to execute the naked procedure is almost the same as executing the regular procedure. Adding a dynamic variable – and assigning it a value - to a naked procedure negates the benefit almost entirely. The code to execute is drastically increased, assigning a value to a string causes the execution of malloc() and memcopy(), and these take so much time the advantage of naked is almost gone. In addition, the string must be released which leads to an extra call of mfree(). All in all, just by adding one dynamic variable the procedure contains too much code to really benefit from Naked.
Other issues
Another disadvantage of using Naked is the issue of runtime error trapping. In case of an error the IDE stops running the program and puts the the error-line-marker on the line that calls the procedure and not inside the naked procedure.
A non-naked, regular procedure will not only trap the error, but also clears the contents of the (dynamic) local variables automatically. With naked-procedures the dynamic variables must be released explicitly. The local variables can be released using the following commands.
A local array cannot be used in a naked-procedure. A local array declaration allocates an array-descriptor plus the memory required to store the array elements. Using the Erase command on a local array only releases the memory for the data, not the array-descriptor. Each time the procedure is executed a new descriptor is allocated without being released. Consequently, the program will leak memory. If you need a local array it must be static, then the descriptor is allocated only once. This is not a problem in regular procedures, of course.
Conclusion
Only short procedures that don’t use local dynamic variables are candidates for the Naked attribute.
09 May 2019
Task Dialog as a replacement for Alert
Since Vista Windows supports the Task Dialog, an extended Message Box with a lot of new features. As the message box the task dialog displays an application-defined message, title, icons and any combination of predefined push buttons. In addition it supports a verification checkbox, command links, and radio buttons. In this post I’ll show you how to use the latest GB update (version 2.56) to easily create a task dialog. At the end of this post we will create the following dialog as an replacement for the famous Alert box.
There are two APIs that create a dialog box: TaskDialog and TaskDialogIndirect. The links will take you to the MS SDK site, to the pages that formally describe the APIs. Starting with GFA-BASIC update version 2.56 the APIs are declared in the include library file commctrl.inc.lg32. To get to the actual declaration you should inspect commctrl.inc.g32 – the source file for the library.
The TaskDialog API
The TaskDialog API is declared as follows:
Declare Function TaskDialog Lib "comctl32.dll" ( _ ByVal hwndParent As Long, ByVal hInstance As Long, _ ByVal pszWindowTitle As Long, ByVal pszMainInstruction As Long, _ ByVal pszContent As Long, _ ByVal dwCommonButtons As Long, _ ByVal pszIcon As Long, pnButton As Long) As Long
Note that only the last parameter takes a variable by reference, all others are declared as ByVal. The TaskDialog function returns the selected button through this variable. The return value of the function itself indicates success or failure.
The parameters that take a string expect a wide string, the string must be formatted as an Unicode string. I discussed Unicode strings in Ansi and Unicode. To convert an Ansi string to Unicode I’ll use the function Wide() from gfawinx.lg32, which is located in the Include directory as well. The name of this file does not include the inc part, because it is a library with executable code, which will add to the program’s size (be it minimal). The include files (those that include the inc extension in the filename) only contain declarations and definitions that don’t add to the program’s size.
If you didn’t change the path to the Include directory after installing the update the Extra tab in the GB Properties should contain a valid library-path. This also means that you can include commctrl.inc.lg32 and gfawinx.lg32 as shown in this code:
$Library "commctrl.inc" $Library "gfawinx" OpenW 1 Print DlgTask(Me.hWnd, "Prompt", "Content", "Demo" , , -3) Do Sleep Until Me Is Nothing Function DlgTask(hOwner As Handle, sMainText,ドル sContent,ドル _ Optional sTitle,ドル Optional iButtons% = TDCBF_OK_BUTTON, _ Optional Icon& = 0) As Long Local Long RetVal, lIcon Local String Title sMainText$ = Wide(sMainText$) sContent$ = Wide(sContent$) Title = Wide( Iif(IsMissing(sTitle$), App.Name, sTitle$)) lIcon = MakeLongHiLo(0, Icon&) If TaskDialog(hOwner, 0, V:Title, V:sMainText,ドル V:sContent,ドル _ iButtons%, lIcon, RetVal) == S_OK Return RetVal EndIf EndFunc
This code produces the following dialog box at the center of the parent window:
You can pass Null to the hOwner parameter of the TaskDialog, but that would display the dialog at the center of the main screen. Not very useful when using multiple monitors with high resolutions. The hInstance parameter can be 0 because we don’t use an icon from the EXE’s resources. Instead we pass a predefined value for the icon to display (-3). The wide strings are passed by providing their address. The RetVal variable is passed by reference to receive the button’s number (>1) that is selected. The DlgTask function returns 0 if TaskDialog doesn’t return with S_OK (=0).
Although commctrl.inc defines all constants to be used with the task dialog functions, it doesn’t provide constants for the icons (GFA-BASIC versions <= 2.56). The icons are defined as follows:
The icon parameter must be an integer resource value created with the macro MAKEINTRESOURCE(). This macro, defined in some SDK header, creates a long where the high word is zero and the low word contains the resource identifier (Word). Here we use MakeLongHiLo() to create the integer resource value. With a newer version of GB (> 2.56) you can use the constants as defined in commctrl.inc without the need to convert with MakeLongHiLo.
The TaskDialogIndirect API
The TaskDialogIndirect function allows further fine tuning of the task dialog. For this to happen you need to fill out a structure of type TASKDIALOGCONFIG.
Declare Function TaskDialogIndirect Lib "comctl32.dll" ( _ ByVal pTaskConfig As Long, pnButton As Long, _ pnRadioButton As Long, pfVerificationFlagChecked As Long) As Long
By using TaskDialogIndirect you can create custom buttons rather than using predefined buttons only. Therefor it is a perfect candidate to replace the good old Alert box with a nicer version. The next example shows how to set up the TASKDIALOGCONFIG structure and pass it to the TaskDialogIndirect API to create the dialog box as shown at the beginning of this post. The Alert2 function takes the same arguments as the Alert box function. The IconAndFlag% argument specifies the icon and layout of the alert box. The MainText$ argument can specify multiple lines by using | as a separator. The ButtonText$ specifies the custom buttons and DefButton% the button to preselect. These parameters are translated to the task dialog features.
$Library "commctrl.inc"
$Library "gfawinx"
OpenW 1
Print Alert2(2, "Which procedure should|be executed", 1, "Input|Calculate|Print")
Do
Sleep
Until Me Is Nothing
Function Alert2(IconAndFlag%, MainText,ドル DefButton%, ButtonText$) As Long
Dim Icon As Word, RetVal As Long, VerFlag As Long, i As Long
Dim sTitle As String, aBtn() As String, sVerificationText As String
Dim tdc As TASKDIALOGCONFIG, taskBtn() As TASKDIALOG_BUTTON
' Provide a title (Unicode)
sTitle = Wide(App.Name)
sVerificationText = Wide("Don't ask again")
' Determine the icon
Switch IconAndFlag% %& 7
Case 1 : Icon = -2 ' Stop: Stop/Error icon
Case 2, 4, 7 : Icon = -3 ' Question, Information: Information icon
Case 3 : Icon = -1 ' Exclamation: Warning
Case 5, 6 : Icon = -4 ' Windowsflag, Application: Shield icon
EndSwitch
' Text lines are separated with |, but we need #10
MainText$ = Replace(MainText,ドル "|", #10)
MainText$ = Wide(MainText$) ' to UNICODE
' Copy the button text to the TASKDIALOG_BUTTON array
StrToArr(ButtonText,ドル "|", aBtn())
ReDim taskBtn(0 .. UBound(aBtn()))
For i = 0 To UBound(aBtn())
aBtn(i) = Wide(aBtn(i)) ' convert to Unicode
taskBtn(i).nButtonID = i + 1 ' ID's are base 1
taskBtn(i).pszButtonText = V:aBtn(i)
Next
tdc.cbSize = SizeOf(TASKDIALOGCONFIG)
tdc.hwndParent = Me.hWnd
tdc.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW
' Right align text?
If IconAndFlag% %& 64 Then tdc.dwFlags = tdc.dwFlags | TDF_RTL_LAYOUT
tdc.pszWindowTitle = V:sTitle
tdc.hMainIcon = MakeLongHiLo(0, Icon)
tdc.pszMainInstruction = V:MainText$
tdc.cButtons = Dim?(taskBtn())
tdc.pButtons = V:taskBtn(0)
tdc.nDefaultButton = DefButton%
tdc.pszVerificationText = V:sVerificationText
If TaskDialogIndirect(V:tdc, RetVal, 0, VerFlag) == S_OK
' VerFlag is 1 if checkbox is checked
Return RetVal ' base 1
EndIf
EndFunc
As with the TaskDialog function all strings must be UNICODE. After setting the tdc.cbSize member to the required size the hwndParent and dwFlags members are used to position the dialog in the center of the parent window.
If the IconAndFlag% parameter specifies right aligned text it is honored by including TDF_RTL_LAYOUT in the dwFlags member.
The main text string must contain LF (#10) characters to separate multiple lines. However, the MainText$ argument will separate multiple lines using the | character. So, before converting MainText$ to a wide string we need to replace all occurrences of | with #10 characters. The Replace function is located in gfawinx.lg32.
Setting up the custom buttons requires a bit more work. First we need to create separate strings for each button text. For this to happen we use the gfawinx procedure StrToArr, which splits a string into multiple array elements. Then, in a loop, each array element is converted to a wide string. In the same loop we assign the button’s ID-value and the string pointer to an array of TASKDIALOG_BUTTONs. After initializing this array, it is assigned it to the tdc members cButtons, pButtons, and nDefaultButton.
Finally, we specify text for an additional checkbox control with the pszVerification member. When pszVerification holds a valid memory address the checkbox control is displayed, but it is enabled only if the pfVerificationFlagChecked parameter of TaskDialogIndirect specifies the address of a return variable. If this parameter is Null the check box is displayed in a disabled state. This is also true for additional option boxes. Note that you may pass Null (0) to a ByRef parameter of a declared DLL function.
16 November 2018
Anatomy of a procedure (1)
Only recently the IDE features ‘Proc Disassembly’, an option available under the Edit | Proc menuitem. This is a valuable resource if you want to get a better understanding of the code generated by the compiler. Once you understand the disassembly of a proc you can use the information to your advantage, especially when it comes to optimizing procedures.
Bare minimum: Naked
Let’s start with a Naked procedure. A Naked procedure is fully optimized, both in size as in performance. This comes with a penalty though, a Naked procedure lacks support for dynamic variables, structured exception handling, and runtime debugging (Tron, Trace). The Naked attribute forces the compiler to produce code much like if it would be done in a pure assembly program. The assembly code of a procedure has great similarity with textbook samples. It’s not hard to understand the procedure flow when it is compared to the theory in the assembly books. Therefor, I start this series on the anatomy of procedures with these bare minimum procs. Examining naked procedures allow us to understand how a proc is constructed and this knowledge can later be used to examine regular procedures.
The following sample shows a Naked proc taking two parameter of a simple type (Long). For now, we’ll omit the use of dynamic datatypes like String, Variant, Object, etc. The procedure contains the local variable tmp, also of a simple datatype, and assigns the product of x and y to tmp. This is the entire program:
TestMul(2, 3) Proc TestMul(x As Int, y As Int) Naked Local Int tmp tmp = x * y EndProc
Now put the caret inside the procedure TestMul and select Proc | Disassembly, it produces the following listing in the debug output window:
-------- Disassembly -----------------------------------
1 Proc TestMul(x As Int, y As Int) Naked (Lines=5)
042704D0: 53 push ebx
042704D1: 55 push ebp
042704D2: 8B EC mov ebp,esp
042704D4: 8D 5D 80 lea ebx,-128[ebp]
042704D7: 2B C0 sub eax,eax
042704D9: 50 push eax
042704DA: DB 45 0C fild dpt 12[ebp]
042704DD: DA 4D 10 fimul dpt 16[ebp]
042704E0: DB 5B 7C fistp dpt 124[ebx]
042704E3: 8B E5 mov esp,ebp
042704E5: 5D pop ebp
042704E6: 5B pop ebx
042704E7: C2 08 00 ret 8
The first line specifies the line number of the procedure (1), its entire prototype, and the number of lines (here 5, but might be more if the procedure includes any trailing empty lines).
The numbers at the start of each line show the memory address of the instructions, which might be different from your result. Consequently, in this case, the function ProcAddr(TestMul) would return the address of the first byte of the procedure: 0x042704D0.
After the memory address follow the opcodes for the assembly instruction. For instance, the opcode with value 0x53 corresponds to the push ebx assembly command. Some instructions require a one byte opcode only, others require multiple opcodes.
The first 6 lines make up the the procedure’s entry code (sometimes called prologue). The last 4 lines are the procedure’s exit code (or epilogue). The lines in between represent the actual functionality of the procedure.
Entry code
The procedure’s entry code prepares the procedure’s code to handle parameters and local variables:
push ebx ; save ebx
push ebp ; save ebp
mov ebp,esp ; establish stackframe
lea ebx,-128[ebp] ; let ebx reference local vars
sub eax,eax ; eax = 0
push eax ; clear first local var
Whenever a procedure takes a parameter or declares a local variable you’ll always find the same three instructions at the start of each procedure: push ebx / push ebp / mov ebp, esp. If the procedure also contains local variables the fourth line lea ebx, –128[ebp] is present as well. Following this line you’ll find the code that initializes the local variable; all local variables are initialized to zero.
Local variables, the purpose of ebx
In GFA-BASIC 32 the ebx register has a special purpose and thus ebx cannot be used as a general purpose register. It is used as a fixed reference point to address the local variables.
Note - According to the documentation it allows to layout variables that require more than 4 bytes (Double, Date, Large, Currency) on 8-byte borders increasing performance when accessed.
The ebx value points to an address 128 bytes down on the stack relative to the value in ebp, the stackframe. Although the first local variable is actually located at ebp – 4 , it will be referenced using the value in ebx. The location of the local variable is +124 bytes relative to the value in ebx, in assembly syntax the tmp variable is located at 124[ebx].
The value stored at that position is obtained using dword ptr 124[ebx]. This is illustrated by the next three lines of code where the parameters are multiplied by the fpu (floating-point processor) and where the result is assigned to the local variable tmp.
042704DA: fild dpt 12[ebp] ; load value of param x into fpu
042704DD: fimul dpt 16[ebp] ; multiply by value in param y
042704E0: fistp dpt 124[ebx] ; store result in tmp
The parameters x and y are accessed using the value in ebp as we will see.
Stack structure
When the procedure is called, the caller puts the parameters y and x on the stack, in reversed order. GB subroutines conform to the stdcall convention, which means that the parameters are pushed from right to left and that the subroutine corrects the stack before returning. Since y is the most right parameter it is pushed first, followed by the parameter at the left (here x). Then the CPU adds the return address on the stack and executes the subroutine.
From this point on the stack is prepared according the procedure’s entry code discussed above. The result can be viewed in the next picture:
The entry code saves the current values from the ebx and ebp registers on the stack. Then ebp is assigned the new esp value. Now ebp is used to address the parameters: parameter x is located at a positive offset of 12 bytes from the value in ebp; in assembly code 12[ebp]. Parameter y is 16 bytes up the stack relative to the value in ebp, in assembly code 16[ebp].
To address the parameters throughout the procedure ebp needs to remain constant during the execution of the procedure. The same is true for ebx that is used to address the local variables. We cannot use esp to reference both parameters and local variables because esp changes automatically during the execution of the procedure. (Although C/C++ compilers sometimes keep track of esp and address all stack variables using an offset to esp.)
Allocating and initializing
After the stackframe is established (mov ebp, esp) the next step requires the reservation of stackspace for local variables, see listing. The general idea, and described in most textbooks, is to subtract the required number of bytes from esp and then clear that piece of memory. In our sample esp would have to be decreased by 4 bytes (for the Long variable tmp) and then cleared by zero. Although GB produces the same effect, it proceeds a bit different.
Note that the first byte of the 32-bits local variable tmp is located at ebp-4. After creating the stackframe by mov ebp, esp the registers esp and ebp point to the same stack address.To reserve and initialize the 4 bytes below esp GB uses the instructions sub eax, eax / push eax.
Subtracting a register by itself results in zero. By pushing zero, now the value in eax, GB both reserves and initializes the local variable in one step. It prevents the additional step to first decrease esp explicitly. The technique to use push to reserve and initialize is typical for GB. The push eax can be repeated to clear and reserve all stack memory necessary for local variables. Thus, if the procedure would have contained two local variables of type long it would have had two push eax instructions.
Exit code
Before leaving the procedure the stack must be returned to the state it was when the procedure was entered. In addition, because of the stdcall convention, the procedure must remove the bytes necessary for the parameters (2 * 4 bytes for two long parameters). This is how its done:
042704E3: mov esp,ebp ; restore esp
042704E5: pop ebp ; restore ebp
042704E6: pop ebx ; restore ebx
042704E7: ret 8 ; return, discarding parameters
When the program returned to the caller the registers that matter and must remain constant are restored. This makes sure the caller can use the correct ebp value to access its parameters and that ebx can be used to access its local variables.
Optimize using disassembly
Inspecting a procedure’s disassembly is useful to get an idea what’s going on underneath the GFA-BASIC statements. The example presented in this blog proves why. The example performs a multiplication of two integer parameters and stores the result in another integer. As you can see, the compiler generates floating-point assembly instructions to perform the math. Since all variables are of type Long, the compiler could have generated more efficient code using the integer multiplication instruction imul. However, the compiler generates integer instruction only for addition and subtraction operators. Now its up to the programmer to optimize this procedure by replacing the multiplication operator * by the Mul operator. The optimized procedure then becomes:
Proc TestMul(x As Int, y As Int) Naked Local Int tmp tmp = x Mul y EndProc
Now compile the code and inspect the disassembly. As you can see the floating point instructions are vanished and replaced by the imul instruction.
Conclusion
Inspecting a procedure’s disassembly requires knowledge to identify the three parts of a procedure; the entry code, the actual code, and the exit code, We discussed how to identify parameters and local variables and saw how GB uses a specific technique to reserve and initialize local variables.
In coming blog posts we’ll discuss non-naked procedures and how you can tell a procedure is a good candidate to be naked.