Showing posts with label Bug. Show all posts
Showing posts with label Bug. Show all posts

13 September 2021

Update 2.6 produces false positive viruscheck

The September 2021 update version 2.6 causes problems with some virus scanners like F-Secure, Secure Point, and some others. The file that causes the problem is GfaWin23.Ocx version 2.39, the GFA-BASIC runtime. After investigating the problem I’m sure the scanners report a false positive. My PC and the files on it do not contain a virus. The GfaWin23.Ocx file can be used without any problems, the GFA-BASIC 32 installer will not install a virus.

GfaWin23.Ocx version 2.39 is a patched 2.38, which is not trapped by any virus scanner according to VirusTotal. To my surprise, patching (changing) only one byte in the binary already causes 5 out of 66 online virus scanners to complain. This is the first time patching causes problems. I patched the GfaWin32.exe as well, and this file does not raise any problems.
I have contacted the vendors of the virus scanners to notify them of the false positive and hope they will add it to their exception list. Until then I will no longer release a patched GfaWin23.Ocx.

Use version 2.38 instead, reinstall
As a GFA-BASIC 32 developer you can ignore the virus scanner’s report, but when you release your software with the GfaWin23.Ocx runtime your customers face the same false positive virus reports. Therefor, I advise you to restore the previous version (2.38) of GfaWin23.Ocx, which does not give any problems.

The download page now contains installer version 2.6a, which contains version 2.38 of the runtime. Please download 2.6a and reinstall GFA-BASIC 32 version 2.6.

Apply the runtime bug fixes yourself 
Version 2.39 did fix a number of bugs, you now must do without. However, you can apply those fixes yourself while running your program. There are 4 bugs that need to be fixed:

  • Alert box displays wrong background color.
  • The mouse pointer over Ocx controls is not the control’s default mouse, but the Form’s mouse pointer. Especially, the TextBox Ocx shows a constantly flipping mouse pointer (arrow <–> I-beam).
  • The FileName property after using CommDlg.ShowFolders returns a string with a terminating null character.
  • The RichEdit Ocx property set CharFormat causes an exception when the string contains a 9.

You can apply all fixes or only the ones your program requires. The following code must be inserted at the beginning of your code (it requires the gfawinx.lg32 library):

'
' Patches for GfaWin23.Ocx 2.38 - 13-Sep-2021 (SH)
' Bug fixes to apply on the fly in a program.
' Note - Patches are only applied once: on first RUN only.
'
$Library "gfawinx"

If Round(DllVersion * 100) == 238       ' Only apply to 2.38

  ' Fix: Alert box background color
  If Peek($1800193C) != 6 Then PokeProcess1 $1800193C, $06

  ' Fix: Get FileName after CommDlg.ShowFolder
  If Peek($1801BCEF) != $4A Then PokeProcess $1801BCEF, Chr($4A, $89, $06, $89, $56, $10)

  ' Fix: MousePointer in Ocx Controls to default mouse
  If Peek($18013B5D) != $5C Then PokeProcess1 $18013B5D, $5C

  ' Fix: Rtf.CharFormat set fails if string contains a 9
  If Peek($1805BA4D) != $7F Then PokeProcess1 $1805BA4D, $7F

EndIf

When GfaWin23.Ocx keeps being rejected by virus scanners, I will add these and possible new bug patches to a single routine in gfawinx.lg32.

26 August 2017

A little array magic

Without going into a formal description of an array, we simply state an array stores multiple values of the same type in contiguous memory. In code an array is recognized by a variable name followed by parenthesis, either with or without indices. Like any other variable an array should be declared before it can be used. (Declaring a variable introduces a variable to the compiler.) Generally, a declaration specifies a name and a type. In case of an array the declaration may include values for lower and upper boundaries up to 7 dimensions.
Array declaration and dimensioning
An array declaration always results in the creation of an array-descriptor. For a global array the descriptor is added to the program’s global data section and for a local array the compiler inserts code to allocate an array descriptor dynamically.
' Declaration and allocation separated:
Global Dim a() As Long  ' adds descriptor to data
ReDim a(6)              ' code to allocate memory

' Declaration and allocation at once:
Global Dim b(3, 1) As String
The second declaration forces the compiler to add a descriptor to the global data and to generate code to allocate memory. It is the exactly the same as Global Dim b() As String : ReDim b(3,1).
A local array variable declaration is handled differently from a global declaration. First of all, the array is not assigned a static descriptor by the compiler. The declaration of the local array let the compiler insert code to obtain (or allocate) an array descriptor dynamically when the procedure is executed. The pointer to the descriptor is stored in a hidden local memory location on the stack of the procedure. Then the address of the descriptor is passed to the same ReDim to allocate memory for the array elements.
Proc LocalArr()  ' Naked forbidden
  Dim dum$       ' prevent compiler bug
  Dim h()        ' allocates a descriptor
  ReDim h(4, 5)  ' allocates memory for descriptor
  Dim v(3)       ' 1-step: descriptor + memory
EndProc          ' destruction for h() and v() and dum$

Local arrays have the same anatomy, but they have no descriptors in the global data-section. Both the descriptor and the memory are allocated – from the heap - when the subroutine is executed. Room is reserved on the procedure stack for a (hidden) pointer to store the address of the descriptor. Later this pointer is necessary to clean the local stack and call the array-destruction code when the subroutine is left.

Local Array Destruction
Local array destruction is part of the termination handler of the procedure, that is if it has one. A Naked procedure doesn’t include termination handlers; the procedure needs to clear pointer variables manually (= the developer). However, a local array cannot be destructed manually, there is no statement to do so. The obvious Erase would only release the data memory, not the array-descriptor, leaving it on the stack. Eventually, the stack might overflow when the procedure is executed repeatedly. 

  • An array cannot be destroyed explicitly, Clr doesn’t work with arrays (and hashes).
  • Local arrays are not allowed in Naked procedures.Naked prevents the compiler from insertion of destruction code for all pointer variables (String, Variant, Object).

Be aware of two possible problems
When a subroutine contains only one or more local array variables, without any other local variables of pointer types (String, Variant, Object), the compiler ‘forgets’ to insert the array-destruction code at all. This is a bug. In this specific situation it is necessary to force the compiler to add array-destruction code. This requires the introduction of another dynamic data-type that requires destruction code. A local String is the easiest solution as is demonstrated in the example above.( A bug still waiting for resolving ….)

The other problem involves ReDim, which - unlike Dim - does not default to the Option Base setting. Instead, ReDim always uses 0 as the lower bound of the array. When Option Base 1 is the default setting for your application, you need to use ReDim ar(1 .. x) explicitly, rather than ReDim ar(x).

- Important note on a Hash
A local Hash isn’t destroyed automatically as well (Naked or otherwise). Clr cannot be used and there is no way to force the compiler to insert Hash destruction code. All local Hash variables must be released manually using Hash Erase. You might want to use Static Hash for local variables. A Hash is a (relative) time consuming type, all entries of a Hash are released one by one. Static preserves the contents and prevents time consuming destruction.

Global Array destruction
GB implements hidden destruction for releasing arrays.  A local array is destroyed on exit and a global array when the program is terminated. For a global array the descriptor is static and part of the global data-section and is an inherent part of the program. After a program exits (either as an EXE or in the IDE) the global data-section simply disappears and the descriptors with it. In case of an EXE-process all memory is released to the OS, and in the IDE the global data is destroyed after ending the program (RUN). There is no cause for memory leak on global arrays.

Anatomy of an array
In GB32 an array is described using a variable name, a descriptor, and a piece of contiguous memory to store the array data. When the compiler hits on a global array declaration it will create a mapping between the variable name and an array-descriptor stored in the global data section. This is true for in-memory compiling and when an EXE is created. A local declaration introduces a mapping between a hidden local pointer variable (32-bits pointer) and the name. The hidden variable stores the pointer to the dynamically allocated array descriptor.
An array-descriptor is a structure defining the attributes of an array. This ArrayDesc - structure is defined like this (note how the last member reserves LBound/UBound information for a maximum of 7 dimensions):

Type ArrayDesc
  -Int    Magic         ' "arry" or "ArrY"
  -Int    ptype         ' vtType (internal const)
  -Int    size          ' size of datatype
  -Int    dimCnt        ' number of dimensions
  -Int    dimCnt2       ' # of dimensions     == IndexCount
  -Int    paddr         ' address of data     == ArrayAddr()
  -Int    corr          ' correction value
  -Int    paddrCorr     ' void* addrCorr;
  -Int    anzElem       ' number of elements  == Dim?()
  -Int    sizeArr       ' size in bytes       == ArraySize()
  -Int    Idx(7 * 3)    ' == LBound()/UBound()
EndType

For global and static arrays an instance of this structure is stored in the global data section. For local arrays the structure is allocated dynamically. Important to realize is that every declaration (Dim/Global/Local/Static) of an array immediately results in an array descriptor dimmed or un-dimmed. The values of the structure members determine the status of the array. The Magic member is for internal use, although it perfectly well indicates if an array is empty – Erase-d or an empty declaration. Other members can be retrieved using  the following functions.

FunctionMember ArrayDescDescription
Dim?(a()) anzElem (element count) Returns the number of elements in the array. Erase clears this value (sets to 0). One way to determine if an array has been ‘dimmed’.
IndexCount(a()) dimCnt2Returns the number of dimensions. Returns 0 when not ‘dimmed’. Another way to determine if an array is empty.
ArrayAddr(a()) paddrReturns the memory address of the first element of the array. Returns 0 if erased or not ‘dimmed’. Can be used to determine if an array is empty.
ArraySize(a()) sizeArrReturns the size of all elements in bytes. Returns 0 if array is empty.
LBound(a()[,i=1]) Idx[]Returns the lower bound for a dimension (default is 1). Raises an error when an array is empty.
UBound(a()[,i=1]) Idx[]Returns the upper bound for a dimension (default is 1). Raises an error when an array is empty.
  • Only LBound and UBound cannot be used to inquire for an ‘un-dimmed’ array.
  • For the special case OLE Automation array-type ParamArray, LBound and UBound return 0 and -1 respectively; these functions do not raise an error (VB compatibility). The ParamArray datatype is in fact nothing more than a Variant containing an OLE/COM SafeArray.
Functions and statements that do not apply to arrays
An array variable is treated differently from any other variable type. The array’s variable name cannot be used in any other GB32 functions and statements as other variables can. For instance, the Clr a() statement is forbidden, ArrPtr() function does not return the location of the array’s variable name, but the location of the array-descriptor instead. You cannot use Pointer to redirect an array variable name to another descriptor. TypeName(ar()) cannot be used to obtain the data type of the array. Etc.
  • Generally, all GB-functions and statements that use a variable name as an argument are forbidden for arrays.
When the compiler refers to an array it refers to the descriptor directly. The compiler doesn’t preserve a mapping between the array’s variable name and a particular location of a pointer as it does with Strings for instance. The generated code simply doesn’t ‘know’ the array name anymore, only the location of descriptor.
There is only one runtime function that accepts the address of a (local hidden) pointer containing the address of an descriptor: CLEARARR() the local array destructor. This function cannot be invoked manually – not even when using assembler, because the address of the hidden variable is unknown. Asm lea eax, ar will not work, it still returns the address of the descriptor.

02 August 2017

StatusBar fix (August 2nd Update)

Fixing one bug often leads to the introduction of another. The June update introduced a bug in the runtime in the Panels.Remove method. This is fixed and the most recent update of the runtime – gfawin23.ocx – is now version 2.32.0.1202. Although I like to step away from incrementing build-numbers, I decided to give it a new build because nothing new was added, only a bug is fixed.

StatusBar redraw problem
During testing I noticed the following. In some circumstances the StatusBar isn’t updated or redrawn to reflect the new situation with a panel removed. This is not GB32 bug, but it is related to the common controls. The StatusBar is only completely reset when it receives a WM_SIZE message. Usually, this message is forwarded by the Form, the parent of the control, when it processes the WM_SIZE message. When the Panel is removed the code does not send or post this message and the Form will never receive the message. The following sample code solves this problem, where sb is the name of the StatusBar Ocx.

' Remove a StatusBar Panel:
sb.Panels.Remove
"Panel1" ' WM_SIZE recalculates and updates: PostMessage sb.Parent.hWnd, WM_SIZE, 0, 0

The lacking update and redraw is a known problem for more common controls. Usually this can be solved by posting a WM_SIZE message to the parent (without any values in the wParam and lParam). The preferred solution would be invalidating the control followed by a call to the UpdateWindow() API. However, this doesn’t recalculate and reset the panels. The status bar is simply redrawn using the ‘previous’ or current settings. This makes sense when you think about it. If the status bar would recalculate its contents each time it has to redraw, it would provide a serious performance penalty in redrawing a Form when it is manipulated. The common control needs to be told explicitly to adjust to the new situation.

10 May 2017

Error free using a library

I use libraries a lot, but there are few things that make using them a bit obscure and may lead to the non-descript message "Load lg32 error: filename.lg32" in the status bar and debug window. In addition, a compiled library may produce strange runtime errors.

BUG - Runtime errors
When you run a project which includes a library it may generate strange, seemingly unrelated error messages. In particular the error "Hash Internal Error 1/2 (Version?)" pops up regularly. The reason for runtime errors inside the code of a library is a bug(!) in applying the setting for Branch Optimizations.

For a lg32 file, GFA-BASIC wants to apply the Full Optimization for Exe setting on the compiling process. However, it is never applied at all, because the code applies this setting in the wrong place, after the code is compiled ;). Consequently, the compiler switches to the trackbar/slider setting from Branch Optimizations.
This is a bug from a long time ago and it is simply never tested properly.

In general objectcode generated for a lg32 file is position independent, it differs from code generated for  EXE (and GLL files). Therefor, the lg32-generated code for the jump-tables for Switch/Case statements and On n GoSub/Call statements are wrong (this is also true for a GLL, for which I always use the default settings). 

The only setting that work flawlessly is the None setting of the slider in 'Branch Optimizations' and uncheck the 'Full Optimization' check box. 


A lg32-file has to be compiled using the default settings for Branch Optimizations.
The slider must be set to the first position (None) and the checkbox Full Optimization for Exe must be unchecked.

Note - The slider setting is applied to compiling code in memory, independent of the required output file type (EXE, LG32, or GLL). The most right position (Full) is exactly the same as checking the Full Optimization for Exe - box. This way you can test fully optimized code inside the IDE.

Note - The branch optimizations of the compiler do not lead to remarkable performance results. These days with fast CPUs and large caches performance increase is hard to provide, the only real performance increase is accomplished by using Naked procedures. Remember however, Naked procedures do not include termination code and do not allow exception handlers.

The $Library statement
The $Library statement loads a lg32 file into memory. But sometimes it cannot locate the lg32 file. The IDE code to find a lg32 file is a bit complicated. In some conditions you may omit the extension and in others you cannot. It depends on the inclusion of a path in $Library statement. For instance, you may include a relative path (relative to the current directory, mostly the g32-file directory, but not necessarily), but than the extension must be provided. It's all a bit incoherent. But there is a solution that always works correctly. That is - the library is always located properly.

Solution for load errors
This solution adds more functionality to the $Library statement and so it complements the current functionality. You must add a (new) register entry to the GFA/BASIC key in the HKEY_CURRENT_USER/Software setting. The key must be named "lg32path" and the value can contain multiple full paths separated by commas. (The value uses the same syntax a the PATH environment variable).

New key: "lg32path", REG_SZ
Value: "C:\GFA\Include, D:\GFA\MyLibs"

Have fun with lg32 file.

27 June 2014

Passing default ByRef parameters to a Sub, a flaw?

In my opinion the GFA-BASIC’s Sub procedure type is an anomaly. When possible you should use Procedure instead. Oh, and when you are too lazy to type Procedure than just use its abbreviation Proc. A Procedure takes all arguments by value by default and is completely backwards compatible to previous GFA-BASIC versions.

On the other hand, if you are stubborn enough and want to use Sub anyway, make sure you use ByRef and ByVal explicitly with all parameters. Do not expect the default ByRef passing will work, because it won’t. We discussed this earlier in The Sub-ByRef flaw. This posting ended with the quote “Why this is happening is unclear, but it is easily repaired by using ByRef explicitly in Sub headings.” I was never convinced this was a real bug and I always assumed a reasonable explanation would come up some day. And it did ….

VB is the flaw; ByRef turns into ByVal
Let us consider the default Sub by reference implementation in VB, VBA, and VBScript. An argument is passed by reference only when the caller uses a very specific syntax. Only when the program executes the subroutine without parentheses the sub gets a reference to the actual argument. When the programmer puts the arguments between parentheses the arguments are passed by value. The next sample demonstrates how and when VB(A/Script) executes the default:

' Normal VB call of a Sub
' passing a reference to Hello$
SPrintStr Hello$ Sub SPrintStr(sArg As String)

When VB encounters a call with arguments inside parentheses, the arguments are evaluated first. In other words, before something is put on the stack the expression inside the parentheses is evaluated (executed, calculated, etc) and a local copy of the variable is assigned the outcome of the evaluation. The local copy is passed by reference to the subroutine, which than becomes a by value passing of the original parameter. Using the parentheses-syntax in the next example will pass the address of a local variable to the Sub SPrintStr.

' VB call of a Sub using ()
’ making and passing a copy of Hello$
SPrintStr(Hello$)
Sub SPrintStr(sArg As String)

Conclusion VB
When VB calls the subroutine with parentheses the sub gets a reference to a copy of the argument and the default ByRef situation suddenly turns into a ByVal passing. The VB Sub and its implicit, default, by reference passing introduces performance loss and unnecessary memory consumption! Remember this when you port VB code to GB32.

You’ll find the whole story on http://blogs.msdn.com/b/ericlippert/archive/2003/09/15/52996.aspx

GFA-BASIC supports with parentheses
You can almost feel the agony FO must have felt when he found out about this VB quirk. How to maintain backward compatibility to previous GFA-BASIC versions and VB? In GB we are used to place parameters between parentheses. Should we suffer from this VB anomaly as well? Either way, FO decided to support the Sub implementation with parentheses only. Passing arguments without parentheses is equal to passing them with parentheses.

Although the Sub-ByRef problem is much clearer now, the GB32 implementation is still a bit ambiguous. Because GB32 mimics the call with parentheses only the subroutine always receives a by-value argument to a default ByRef parameter. You may omit the parentheses in GB32, but it still executes the version with parentheses. Only when a global variable is passed to a default by reference argument the actual variable address is put on the stack.

Conclusion GB32
Despite the ambiguity between global and local parameters, you should, as a general rule, no longer consider a default by reference parameter as [in/out] when you omit ByRef in a Sub declaration. Inside the Sub you don’t know if you are dealing with the actual variable [in/out] or with a copy [in]. Always use ByVal and ByRef explicitly in a Sub declaration.

04 January 2013

Memory Leak on Image Ocx

At the end of 2012 Peter Heinzig posted a sample showing GFA-BASIC 32 memory leaks with the Form and Image Ocx. In Memory leak with Form.Picture I focused on the Form object and a remedy against the memory leak. In the meantime I investigated the Image Ocx and can confirm a memory leak when the Ocx control is destroyed. Just like the Form Object the Image Ocx doesn't release it's Picture object. The remedy is actually the same. In the parent Form Ocx _Destroy sub event add a command to release the Picture property of the image control.

The solutions presented here are applicable on all versions of GFA-BASIC 32. However I hope to build an update of the runtime as soon as possible. I hope these are the only memory leaks, but it takes time to verify these are the only Picture objects that aren't released after a program has been closed.

29 December 2012

Memory Leak with Form.Picture Object

When you assign an image to the Picture property of a Form Ocx, and don't release the Picture explicitly, a memory leak occurs when the Form is destroyed.

An image can be assigned using the Form-Editor Properties window in the sidebar. For a Form object the Properties window shows a Picture property that can be assigned an image (bitmap, icon, cursor, GIF, JPEG, and metafiles) by double clicking in the value column. Behind the scenes the LoadForm command - that is used to load a Form created with the Form-Editor - will create a COM Picture object on behalf of the Form. The dynamically allocated Picture COM object is then assigned to the Form.Picture property. 
However, a program may also set a Picture object to the Form.Picture property explicitly in code.

The memory leak occurs when the Form is closed. In the process of destroying all resources the Picture object is simply forgotten. The memory for the image is never released and the reference count on the COM object never reaches zero. Not only the memory necessary to store the image, but the memory allocated for the COM object will remain occupied. The memory used to store a bitmap can be of a considerable size and after some time you may even run out of memory. This most likely will present itself after RUNning a program multiple times within the IDE.

The best place to set the Picture Object to Nothing is within the Form_Destroy() event sub. A program cannot release the object when the Form has gone (==Nothing).

The leaked memory can never be reclaimed, because after the Form is destroyed the pointer to the Picture's COM Object is zeroed out. Selecting 'Cleanup Resources' from the Project menu will not work.

24 May 2011

Passing a Hash to a procedure (III)–Workaround

This part three in a series about the GFA-BASIC 32 Hash data type. In this part I discuss a workaround to use Hash procedure parameters and some peculiarities of the hash data type. However, you might like to read the previous posts first.
The workaround
A compiler bug blocks the the Hash Xxx commands for a Hash data type passed by-reference. On the other hand, the hash [ ] - operator syntax works well. The Hash Xxx commands work correctly for a local Hash data type, the one that is not passed as an argument to the procedure. The logical conclusion is to declare and use a temporary local Hash of the same data type and then somehow 'assign the hash parameter to the local hash'.
GFA-BASIC 32 has a two constructions to do just that:
  • Declaring a variable p As Pointer To data-type and than use Pointer(p) = address command to assign the pointer (<=> variable) an address.
  • Swapping the descriptors of the Hash variables: Swap hs1, hs2.
We can skip the first option. It should work but it doesn't, the compiler refuses the statement:
Dim hs As Pointer Hash Int
The swap method should work as well, but again it doesn't. The compiler doesn't know what to do with Swapping two Hash variables. However there is light at the end of the tunnel, Swap works for all other GFA-BASIC 32 data types including the UDT (Type). And that's the one we are going to use to Swap the hash variables, hence the procedure SwapHash().
Local hsInt As Hash Int
hashtest(HB[])

Proc hashtest(ByRef HS As Hash Int)
  Dim ths As Hash Int   ' temporary Hash
  SwapHash( *HS, *ths ) ' swap descriptors

  ' Now ONLY use ths[]
  Hash Add ths["dd"], 7
  ths ["aa"] = 8
  ' ...

  SwapHash( *HS, *ths ) ' swap back
EndProc

Proc SwapHash(ptrHash1%, ptrHash2%) Naked
  Local hsdesc1 As Pointer HashDesc
  Pointer(hsdesc1) = ptrHash1
  Local hsdesc2 As Pointer HashDesc
  Pointer(hsdesc2) = ptrHash2
  ' Make sure the types are the same:
  Assert hsdesc1.pType == hsdesc2.pType _
    ' SwapHash() - Hash type mismatch

  Swap hsdesc1, hsdesc2

  Type HashDesc         ' The Hash Descriptor
    pHashData As Long   ' pointer to memory
    pType As Long       ' Hash data type
  EndType
EndProc

19 May 2011

Passing a Hash to a subroutine (II) - The Descriptor

In the previous post we discussed the GFA-BASIC 32 compiler bug when a Hash variable is passed to a subroutine. The compiler produces incorrect code for the Hash Xxx commands, but it works well in case hash-operators are used.
The situation is like this:
Sub q(hs As Hash String)
  ' Errorneous:
  Hash Add hs["mykey"], "Entry1"
  Hash Add hs[2], "Entry2"
  ' OK:
  hs[3] = "Entry3"
  Print hs[%]
EndSub
Note that the Hash variable hs is passed by-reference (implicitly due to the use of the keyword Sub).

Hash descriptor
A Hash variable is actually a 8-byte structure (user-defined type) containing two LONG integers. The Hash variable references the address of this hash-type, called the descriptor. The Hash-descriptor is defined as follows:
Type HashDesc
  pHashData As Long
  pType As Long
EndType
After declaring a hash variable (Dim hs As Hash type) the first long integer of the descriptor is Null indicating that the hash didn't allocate any memory. The second long contains a value indicating the GFA-BASIC 32 primary data type used to store values in the hash table. The data type could be Int, Byte, String, Variant, etc. Every GFA-BASIC 32 data type can be used, with the exception of UDT types. As an example the next code declares a Hash to store (long) Integer values and displays the descriptor:
Local HB As Hash Int
Local hs_desc As Pointer To HashDesc
Pointer(hs_desc) = *HB
Debug "HASH DESCRIPTOR"
Trace Hex(*hs_desc)
Trace Hex(hs_desc.pHashData)    ' = 0
Trace Hex(hs_desc.pType)        ' = $4018
The variable HB references the starting address of the 8-byte descriptor. As with any GFA-BASIC 32 variable that uses a descriptor, the address can be obtained using ArrPtr(HB) or *HB. Using the preceding code you can inspect the descriptor.

Hash MUST be declared as ByRef 
When a hash variable is passed to a subroutine, its descriptor address is always passed by reference (whether or not ByRef or ByVal is used in the procedure header). BUT, the Hash parameter MUST be ByRef, otherwise the code generated for the procedure will use a wrong address. A ByVal Hash parameter sets the compiler in error mode without returning to the editor. As a consequence all code following might (will actually) be compiled wrongly.
When the Hash parameter is passed correctly by-reference the compiler still generates incorrect code for the Hash Xxx commands, like Hash Add, Hash Remove,...
The advise remains the same, don't pass Hash tables around, but use them as global variables only.

18 May 2011

Hash passing to a subroutine (I) - The bug

As mentioned in the (English) help file the handling of a Hash type argument is erroneous. The compiler produces incorrect code when the local Hash parameter is used in Hash commands like Hash Add, Hash Remove, etc. As far as I know all Hash Xxx commands suffer from the compiler bug. In contradiction with this behavior is the handling of the Hash–operator code. When a Hash data type is passed to a subroutine the compiler produces correct code for all [ ] hash-operator code. The following code illustrates the situation:
Sub q(hs As Hash String)
  ' Errorneous:
  Hash Add hs["mykey"], "Entry1"
  Hash Add hs[2], "Entry2"
  ' OK:
  hs[3] = "Entry3"
  Print hs[%]
EndSub

What is happening?
Due to the implicit Sub by-reference passing the compiler puts the address of the Hash argument on the stack when invoking q( Hs[] ). This is the first step performed by the compiler when a call to q() must be produced and it turns out to be ok. The second step performed by the compiler is when the subroutine q() is compiled. Due to the by-ref passing the compiler must set the local hs variable to the address put on the stack by the code that invoked the call. That way the local variable hs points to the same data as the hash passed to q().
However, somehow the compiler messes up with the Hash Xxx commands. When inspecting the assembly code for q() it can be seen that for the hash-operator instructions the correct Hash address is passed to the runtime library functions. Also, it is clear that the compiler passes the incorrect address in case the Hash Xxx commands are used.

What is to be done?
At this moment this bug cannot be fixed, so we either need a workaround or we avoid the use of Hash variables as a subroutine argument at all. The second option isn't the worst of both, because due to their nature Hash tables are mostly used globally and don't need to be passed to subroutines at all. Of course, a hash table could be used to store temporary results (regular expression commands), but in that case the Hash is usually declared locally and not passed to any subroutine.
Advise: Don't pass Hash variables to subroutines.
In the next post I will discuss a workaround together with the layout of the GFA-BASIC 32 Hash data type.

21 April 2011

CommDlg.ShowFolders

The ShowFolders method of the CommDlg Ocx isn't documented properly. The method is included to provide an easy way to display the Shell's SHBrowseForFolders dialog box allowing the user to select a folder rather than a file. It is - of course - not part of the the Common Dialog Box Library and should  have been implemented as a separately function. Or shouldn't it?

The FileName and Title properties
The CommDlg.ShowFolders(Optional flag As Variant) method shares two properties with other CommDlg methods that show dialog boxes: FileName and Title. In case these are used with ShowFolders their meaning is as follows:

- CommDlg.Title  (R/W)
Sets the string that is displayed above the tree view control in the dialog box. This string can be used to specify instructions to the user. Returns the same value as was put into it.

- CommDlg.FileName  (R/W)
Sets the browse dialog box's initial selection folder (has nothing to do with filenames!). Note, by default, the SHBrowseForFolder API lets the user start at the desktop to browse the shell's namespace and pick a folder. GFA-BASIC 32 overrides this behavior by starting the browse dialog box at the folder specified in the .FileName property. To let the dialog box start at the desktop set the FileName property to an empty string. To bring up the browse dialog box with the current directory selected, set FileName = CurDir.

After closing the ShowFolders dialog box the FileName property contains the name of the selected folder. That is if you select the Ok button; after Cancel the FileName is undefined.

CommDlg code was/is buggy
The GFA-BASIC 32 library code that implements CommDlg Ocx and specifically the Show Folder function, has always been buggy and I've been fixing it many times in the past. I was confident I fixed all bugs in Build 1169. At the time of writing this blog I tried it again and I was shocked to see that it still has a bug. When you press the browse dialog box's Cancel button the FileName property is undefined and present an Access Violation error. I will fix it as soon as possible and release Build 1170, which also contains fixes for the Dlg Open/Save commands.

The optional parameter
The ShowFolders(flag) method accepts one optional argument that specifies the options for the dialog box. When you omit the argument, the default behavior is BIF_RETURNONLYFSDIRS. The flag can include a combination of the following values:

BIF_BROWSEFORCOMPUTER (0x1000) Only return computers. If the user selects anything other than a computer, the OK button is grayed.
BIF_BROWSEFORPRINTER (0x2000) Only return printers. If the user selects anything other than a printer, the OK button is grayed.
BIF_BROWSEINCLUDEFILES (0x4000) The browse dialog will display files as well as folders.
BIF_DONTGOBELOWDOMAIN (0x2) Do not include network folders below the domain level in the tree view control.
BIF_EDITBOX (0x10) Version 4.71. The browse dialog includes an edit control in which the user can type the name of an item.
BIF_RETURNFSANCESTORS (0x08) Only return file system ancestors. If the user selects anything other than a file system ancestor, the OK button is grayed.
BIF_RETURNONLYFSDIRS (0x01) Only return file system directories. If the user selects folders that are not part of the file system, the OK button is grayed.
BIF_STATUSTEXT (0x04) Include a status area in the dialog box. The callback function can set the status text by sending messages to the dialog box.
BIF_VALIDATE (0x20) Version 4.71. If the user types an invalid name into the edit box, the browse dialog will call the application's BrowseCallBackproc with the BFFM_VALIDATEFAILED message. This flag is ignored if BIF_EDITBOX is not specified.

The BrowseCallbackProc
Although the ShowFolders method will pass most of the flags without questions asked, it doesn't honor them all. Some of the flags require an application-defined callback function to handle application defined behavior. The underlying SHBrowseForFolder API function calls BrowseCallbackProc to notify it about events. The address of the callback function is passed in the BROWSEINFO structure that contains information used to display the dialog box. The GFA-BASIC 32 ShowFolders method uses the callback proc to select the folder set with CommDlg.FileName in response of the BFFM_INITIALIZED message. GFA-BASIC 32 does not provide a way to interact with its BrowseCallbackProc, making ShowFolders a simple folder selection mechanism.

28 November 2010

Download new update

General note on updates. Go to the Download Page (button on the top of the blog).

Update of Build 1169 (2)

This is the second update of Build 1169. The IDE (GfaWin32.exe) isn't changed and still has build number 1169; however the runtime in the previous build-release 1169 (1) contained a new bug.

The CommDlg property .FileName didn't return the selected folder when CommDlg.ShowFolders was used. This is now fixed and the .FileName property should now return the correct filename(s) or folder. Note that the .FileName property was associated with three bugs. I hope they are all fixed now.
The runtime has build number 1169 now as well.

GOTO DOWNLOAD

24 April 2010

Replace a Toolbar's button image

Back in 2000 a bug report was posted describing how GFA-BASIC 32 failed to properly remove a Toolbar Ocx button. I say properly, because the button was removed, but the button count remained the same. Now in 2010 another bug was reported. The button's image couldn't be easily replaced, because the button's property .Image isn't implemented. So, the Ocx Toolbar has some flaws ....

To address the new bug, we need to find a work-around. This is easy, we must invoke Toolbar.Remove index to remove the button and than Add it again with the new image index. But for this to work properly we also need to know why Toolbar.Remove index failed in the first place. Well, the cause for the failure was quickly found; the .Remove method failed because the button wasn't assigned a key. The Toolbar's Add method takes the following (optional) arguments. Each argument is a Variant, GFA-BASIC converts the argument to a proper data-type internally.

ToolBar.Add index, key, caption, style, image

index - defines the position of the button.
key – specifies a character string used to store and retrieve the button by name.
caption – defines the string to display next to the button.
style – specifies the type of button.
image – is a key specifying the image to use from the ImageList control.

ToolBar.Remove index requires the index (== position) of the button to remove. Subsequently, most of us don't specify a key for the button, we don't need it later, do we?
Well, as a matter of fact, GFA-BASIC 32 uses the key to remove the button from the underlying Buttons collection, if it isn't specified it cannot be removed. Despite the fact that Remove requires the index (or position) only, it requires the accompanying key to actually remove. So, add a key argument to ToolBar.Add and you are good.

Now we know how to remove a button we can insert a new one. To accomplish this we must use the index argument of Add to insert a new button with a new image. The sample code looks like this:

' Add button #1
tb.Add 1, "but1" , , 0, 1
' Replace button #1
Tb.Remove 1
Tb.Add 1, "but1" , , 0, 2

07 February 2010

GFA-BASIC Update Build 1166

The internal linker error caused a "File write error" when creating a stand-alone EXE or a Gfa Editor Extension GLL. The bug occured when one or more $Libraries are linked to the compiled code of the program currently editted. When none of the Lg32 libraries contained subroutines (Sub, Proc, or Func) the linker misses a pointer and reads from address NULL. The entire process of creating the output file is guarded in a Try-Catch exception handler that only reports a "File write error in filename". It doesn't specify what went wrong.
Anyway, this bug is solved in Build 1166, which you can download at http://gfabasic32.googlepages.com/update

08 December 2009

Why you should use the latest update

Bugs and notes come to me by electronic messages. I check them to see if I can reproduce them and if so I make a note to investigate them. Sometimes, the notes lay around my desk for a long time, because 1) I don't have time, or 2) this is more often the reason, the bug is hard to locate. Because of the complexity, after solving I find it sometimes hard to describe the problem and the resolution. You know: "Doctor, when I push here, it hurts there...".
 
The Vista bug has to do with the memory allocation of the string memory for the Ocx.Name property. When you rename the Ocx (in the Form editor) the memory is released before new (string) memory is allocated to store the new name. GFA bombs! Why? Because GFA mixes memory heaps. The problem is located both in the runtime as in the IDE, but I managed to solve it by a Try/Catch structure in the IDE. For that to happen I had to teach myself code insertion in a binary and writing Try/catch handlers in machine language. Well, its my hobby, so it is no problem, but after so much effort I mostly limit myself to a short note: this bug has been solved. But mostly I give an example in the readme or on my site.
 
The Double bug is bad! When a string (LPCHR) contains the maximum number of decimal fraction digits in a certain sequence the algorithm FO used is simply wrong. Copy and paste this in your editor:
 
Const a =  -1.79769313486232e+308
 
Results of floating-point calculations are often stored in strings (for instance a representation in a control). Than used for a calculation the string is converted back using Val or ValDbl. In certain situation this fails and crashes GB, for the exact same reason. The problem: GFA-BASIC uses a general exported DLL function for these conversions (both at editing, compiling and runtime). How do you know when your conversion is wrong or right?? You cannot. Therefore you MUST use the latest update.

20 October 2009

EXE behaves different

"My program runs fine in the IDE, but it freezes when the compiled EXE is started." What is happening?
In the past ten years this message pops-up once and a while. At the same time this message is never repeated. The developer reviews his or her program and recognizes a programming bug. Then after the bug is solved, the program runs without any problem. Until now I never (!) encountered a compiler bug that resulted in an erroneous EXE.
Then, how is it possible that the EXE behaves different than the IDE-program? Some things to consider.
  1. The most logical error is a programming error (90% chance).
    I noticed a problem in user-defined types, for instance a member array isn't properly dimensioned, or a Boolean member is used (see the Help File for notes on using Booleans in a Type). Check your Types, but even better review the initialization code of your program.
  2. In Mysterious Errors I described a theory about errors that cannot be reproduced. But again, the programmer is to blame.
  3. The compiler inserts Trace-code between each code line in the IDE. You can disable this with $StepOff and get the same compiler result as an EXE.
  4. An EXE might behave different because the Compiler setting  Full Branch Optimization for EXE is enabled. This is the only option that produces a different result. (1% chance).
  5. Make sure you are aware of the ByRef Bug for Sub procedures. Insert ByVal and ByRef in the procedure headings. Better: use Sub only for event subs.
All in all, look for the problem in your code, not in the compiler. If an EXE works with one OS, but doesn't with another, than we have a possible bug!

14 October 2009

The Sub-ByRef flaw

A call to a Sub procedure using by reference arguments might not update the correct variable. To understand this behavior be sure to understand the differences between ByVal and ByRef arguments. This is explained in the next tip 'Passing values ByRef or ByVal'. Here you have learned that a Sub by default takes arguments as ByRef, making ByRef optional. Unfortunately this is not always true.

When an argument is passed by reference, the procedure is passed the address of the argument variable. When the argument is a global variable the address of the variable is passed as expected. In the following situation the ByRef clause is omitted and the global variable is updated to 100 correctly.

Global a% = 50
MySub(a%)
Print a%   ' 100
Sub MySub(b%)
  b% = 100
EndSub

When a second Sub is called from within MySub passing b% by reference, b% is  not updated as expected. The variable b% is passed by value, rather than by reference!

Sub MySub(b%)
  b% = 100 : NextSub(b%) : Print b%  // still 100
EndSub
Sub NextSub(c%)
  c% = 200    // unexpected: c% is ByVal argument
EndSub

Why this is happening is unclear, but it is easily repaired by using ByRef explicitly in Sub headings.

Sub NextSub(ByRef c%)

09 September 2009

Don't use Mul Command

Sometimes a compiler bug pops up. Here is one: the integer math command Mul int, const is buggy! So don't use it. For instance:

Mul j%, 2

Replace it with a mathematical instruction like:

j% = j% *2

or use the function variant:

j% = Mul(j%,2)

This bug is reported when j% is a ByRef parameter of a subroutine. I verified it and it is a bug indeed. I'm not aware of problems in other situation.