08 December 2009
Why you should use the latest update
13 November 2009
Is GFABASIC 32 compatible to GFABASIC 16?
29 October 2009
Modify the Windows style
$Export $Export Proc Modify* Procedure ModifyStyle(hWnd As Handle, lRemove As Long, lAdd As Long) Local Long Style = GetWindowLong(hWnd, GWL_STYLE) Style &= ~lRemove Style |= lAdd ~SetWindowLong(hWnd, GWL_STYLE, Style) ~SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME) EndProc Procedure ModifyExStyle(hWnd As Handle, lRemove As Long, lAdd As Long) Local Long ExStyle = GetWindowLong(hWnd, GWL_EXSTYLE) ExStyle &= ~lRemove ExStyle |= lAdd ~SetWindowLong(hWnd, GWL_EXSTYLE, ExStyle) ~SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME) EndProc
As you can see, I collected these in a $Library (.lg32). I'm a big fan of libraries and whenever possible I put general subroutines in a compiled module.
I'm aware of some problems with loading of LG32 files, I had them in the past as well. However, and I don't know why, I don't have them anymore ...
26 October 2009
LabelAddr in a GLL
The final solution I developed is based on the MS Detours technique from MS Research Labs. However, not even this highly tricky technique worked for me. The GLL still couldn't be unloaded using the Extension Manager Dialog box. Then, I adapted the Detour technique and configured it in such a way that unloading the GLL isn't a problem anymore. Almost two years ago I first started to study the Detours technique, and finally I came up with a solution to full fill my needs.
The adapted method is based on a GFA-BASIC 32 assembly routine, which is injected into the main window procedure of Gfa_hWnd inside the code of the IDE. To complete my mission I had to figure out the address of a label inside a GFA-BASIC subroutine. The injected code has to jump (jmp) to the label's address. However, the addresses of labels in GLLs (GFA Editor Extensions) are not known in advance.
Both the ProcAddr() and LabelAddr() functions are filled in by the compiler at compile time. In addition, a relocation-table is stored with the compiled program. In contrast with loading an EXE, which is performed by the OS, the addresses in a GLL are relocated by the GFA-BASIC 32 IDE when the GLL is loaded. The IDE is capable of adjusting the ProcAddr addresses inside the GLL, but it doesn't adjust the hardcoded LabelAddr values.
It takes a trick to obtain the relocated address of a label. The GLL is loaded into memory and addresses are calculated using an offset. When we know the offset value the hardcoded label addresses can be calculated. The function Gfa_LabelOffset in the next code, does just this. Since the value is constant for the entire GLL, it is stored in a static variable.
Function Gfa_LabelOffset() As Long Static LabelOffset% = 0 If LabelOffset% == 0 GetRegs : 1 LabelOffset% = _EIP - LabelAddr(1) EndIf Return LabelOffset% EndFunc
The function can be used to obtain the real location of a label as follows:
Local stubAddr% = LabelAddr(stub) + Gfa_LabelOffset stub:
The LabelAddr(stub) returns the hardcoded value set by the compiler. The real location is then adjusted by the offset value used to relocate all addresses in the GLL.
20 October 2009
EXE behaves different
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.
- 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. - In Mysterious Errors I described a theory about errors that cannot be reproduced. But again, the programmer is to blame.
- 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.
- 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).
- 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.
14 October 2009
Difference between _Message and _MessageProc
There are two somewhat strange, certainly badly documented, event subs for forms: the Form_Message and Form_MessageProc event. Although they sound much alike, they are quite different. Both events are invoked from inside the window procedure of the form. However _Message() is called only for a handful of messages, but _MessageProc() is called for all messages, regardless of their origin. These subs are defined as:
Sub frm1_Message(hWnd%, Mess%, wParam%, lParam%)
Sub frm1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)The _MessageProc event sub With the _MessageProc Sub you can actually filter or influence the behavior of the GB32 window procedure for the Form window class. The _MessageProc is called before GB32 handles the message itself (more or less, maybe later more on this). The sub obtains six message parameters. The first four are the same as defined for a Windows API window procedure. Every message, whether it is obtained from the message queue (the posted messages) or by a direct call from any other source (for instance through SendMessage), is handled in the window procedure. The hWnd parameter is the window to which the message is sent (we already know the objects name: frm and we could obtain the window handle from the object, that would have lowered the number of parameters by one). The Msg parameter is the message number—which is usually a constant such as WM_ACTIVATEAPP or WM_PAINT. The wParam and lParam parameters differ for each message, as does the return value; you must look up the specific message to see what they mean. Often, wParam or the return value is ignored, but not always. The _MessageProc() event sub has two additional parameters (ByRef) that allow you to return a value. For instance, when you want GB32 not to handle a certain message you can set the ValidRet? Boolean variable to True and provide a return value by setting RetVal%. What value RetVal must have is defined in the Windows API SDK. It often says something like: "If you handle this message return zero (or..)". Now let us look at an example. Suppose you want to store the window coordinates of OpenW #1 in the register so the application can use these values to open at the same place. In GB32 you must then handle the sub events Form_ReSize and Form_Moved to store the coordinates. As an alternative you could use Form_MessageProc and handle the WM_EXITSIZEMOVE message as follows:
Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, _ lParam%, Retval%, ValidRet?) Local Int x, y, w, h Switch Mess Case WM_EXITSIZEMOVE GetWinRect hwnd, x, y, w, h SaveSetting "MyComp", "ThisApp", "Position", Mkl$(x, y, w, h) ValiRet? = True : RetVal = 0 EndSwitch EndSubNote the Form_MessageProc() actually _is_ the subclass window procedure for the GB32 windows (Form, Dialog, OpenW, ChildW, ParentW). Subclassing is a built-in feature of GFA-BASIC 32. As such the the OCX control Form is perfectly suited to write custom controls.
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%)
Loading a library: fail or success?
When the $Library directive is parsed (triggered when the line is changed) the specified library is loaded and the exported items are added to the Imports tab in the sidebar. When the file is indeed located and opened a message of success is written to Debug Output Window saying "Loading lg32filename".
Any errors caused by the $Library are then due to a corrupt lg32 file.
The "Load lg32 error" message
Programs that contain references to libraries using the $Library directive may occasionally show a "Load lg32 error: filename.lg32" in the status bar. This message is generated when the specified library can't be located at the specified path or, when a path is omitted, in the current active directory, 'My Documents\lg32', or in directories that are searched using the SearchPath API function.
This message should therefore be called: "Lg32 file not found".
Reload project to reset current directory
When a lg32 can't be found the most obvious reason is an unexpected active directory. Sometimes when working at a project the active directory is changed and the project's directory isn't the current any longer. To check for this, simply reload the project, because this sets the current active directory back to the project's folder.
Set default path for Libraries
An undocumented feature is the possibility to set a default path for libraries in the registry. The registry key HKEY_CURRENT_USER\Software\GFA\Basic must contain the key "lg32Path" specifying the default path for the libraries. The key is not present at default, so you must add the key yourself.
07 October 2009
Hooking to handle Gfa_hWnd messages?
In the Why you cannot subclass Gfa_hWnd I explained why the Gfa_hWnd cannot be subclassed to extend the editor's functionality.
Still, I need to intercept and modify messages for the main IDE window Gfa_hWnd. Some kind of subclassing is required. The other feature Windows offers is the 'hooking', known as Windows Hooks. "Hooking is a sort of subclassing, only it isn't associated with a single window, but with a thread or even the whole system. It's a kind of filter of Windows' messages that allow you to override, or add functionalities when a particular message is received." Sounds promising, doesn't it?
Hooking Example
In fact, I did create a test program. First, two hooks are installed, the WH_GETMESSAGE and the WH_CALLWNDPROC hooks. There are other hooks, but these seem to be the most logical ones. Just to show how hooking might be done in GFA-BASIC 32, I present some code.
After declaring two global Handle variables, the hooks are initialized as follows:
Global Handle hGetMsgHook, hCallWndHook ' Process or modify all messages (of any type) for the system whenever a ' GetMessage or a PeekMessage function is called (WH_GETMESSAGE). hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE , ProcAddr( GetMsgProc), App.hInstance, GetCurrentThreadId()) ' Process or modify all messages (of any type) whenever a SendMessage ' function is called (WH_CALLWNDPROC). hCallWndHook = SetWindowsHookEx(WH_CALLWNDPROC , ProcAddr( CallWndProc), App.hInstance, GetCurrentThreadId())
From this code you can see that both hooks are thread wide, meaning all messages for the thread are passed on to the hook filter functions GetMsgProc() and CallWndProc(). This is one of the reasons I didn't use the hook technique to 'subclass' the Gfa_hWnd window. Later more.
The application defined hook filter functions are defined as:
' WH_GETMESSAGE Hook Function GetMsgProc(ByVal idHook As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Dim msg As Pointer MSG Pointer(msg) = lParam If msg.hwnd == GfahWnd && msg.mess > 0 && msg.mess <> 275 Trace msg.mess EndIf Return CallNextHookEx(hGetMsgHook, idHook, wParam, lParam) End Function ' ' WH_CALLWNDPROC Hook Function CallWndProc(ByVal idHook As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Dim msg As Pointer MSG Pointer(msg) = lParam If msg.hwnd == GfahWnd && msg.mess > 0 && msg.mess <> 275 Trace msg.mess EndIf Return CallNextHookEx(hGetMsgHook, idHook, wParam, lParam) End Function
The GfahWnd variable contains the Gfa_hWnd value and MSG is an API user-defined type to store the message parameters. All well known I presume.
Is hooking a solution?
Does Windows Hooking offer a solution to my problem? The answer is no. Subclassing goes further than hooking. Subclassing functions can return values in response to messages, hooking filter functions cannot. Another issue is the use of thread wide filter functions, that are called for every message that is sent through SendMessage or retrieved with GetMessage. Not only do these hook functions significantly drain on system performance, they might also interfere with the application at hand, that is, the program currently being developed and run in the IDE.
I need another solution.
05 October 2009
Why you cannot subclass Gfa_hWnd
In the course of interpreting the disassembly of both the GfaWin32.exe and the run-time I created quite some editor extension procedures. Most of them are rudimentary and should be developed further, but some of them are very useful (at least to me). I always hoped to put them together in one single GLL file and make that GLL available. However, I never came to do that. I had a problem I didn't understand, and thus didn't know how to solve. After unloading that GLL the IDE crashed.
To add new functionality a Windows developer will most likely start by subclassing the window. In fact, subclassing is the most elementary of all modification techniques. I started by subclassing both IDE windows, the main IDE window Gfa_hWnd and the editor window Gfa_hWndEd. The subclassing was performed in the Gfa_Init() procedure and undone in Gfa_Exit(), which seemed logical at the time. However, as said, this caused a (fatal) problem that at that time I couldn't resolve and I reserved it for a later moment to handle. I was convinced it was something I simply overlooked and at that stage it wasn't really an obstacle. The GLL was automatically loaded when the IDE was started and unloaded when it was closed. I never unloaded the GLL by hand using the Editor Extension Manager dialog box.
Sometimes I was annoyed by the problem and tried to find out the cause of it. It turned out that when the GLL was unloaded in the course of closing the IDE, the un-subclassing was performed after the main window was closed. So, the Gfa_hWnd window didn't exist anymore and re-installing the old window procedure didn't cause any problem. But, when I unloaded the GLL when the IDE was active, using the Extension dialog box, it always crashed the IDE. The undoing of the subclassing of Gfa_hWnd was the problem. In fact, you cannot subclass the Gfa_hWnd in a GLL.
My problem was that I didn't understand exactly what I was doing when I was subclassing a window. Let me illustrate by describing the path a WM_COMMAND (a posted) message follows to where it gets actually handled. Here is what happens when you select the GFA-BASIC 32's Editor Extension Manager dialog box, either from the menu-bar or through a shortcut First the OS puts a WM_COMMAND message in the application queue. The program obtains the message in an endless GetMessage loop and dispatches the message to the window procedure (note: the one I tried to subclass). The window procedure invokes the modal dialog box and a new message loop is started to handle the dialog box. Now, after subclassing, the OS invokes the new window procedure rather than the old one, and requires the new procedure to execute the old window procedure using the CallWindowProc() API. So, the old window procedure, which handled the WM_COMMAND, is called from the new window procedure located in the GLL. In fact, when the dialog box is put on the screen and a modal message loop is started, the program is still executing the new window procedure inside the GLL! After closing the dialog box the program will want to return to the location it was called from, the place where CallWindowProc() API was invoked. You see the problem now?
The old window procedure was invoked from inside the new window procedure (located in the GLL), which was just unloaded using the Editor Extension Manager dialog box. There is no place to return to! Ergo, the most elementary technique, subclassing a window to add new functionality, is not possible for Gfa_hWnd in an editor extension.
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.
18 August 2009
Obtaining the name of the actual procedure (2)
Does a compiled GFA-BASIC 32 application contain debug information? I got interested and disassembled a compiled (EXE) GFA-BASIC 32 application.
I immediately noted why Avira anti-virus complained about compiled GFA-BASIC 32 programs in the past. The GFA-BASIC 32 EXE application start-up code is quite different from ‘normal’ C/C++, VB, and Pascal applications. In fact, there is no such start-up code! The compiled program immediately calls the exported DLL function ‘GfaWin23_5()’ from the GfaWin23.Ocx runtime. The DLL function calculates the start-address of the main program part and executes it. The entire program is executed from inside the GfaWin23_5() function which contains all start-up and program-exit instructions.
My main concern was ‘how is the stack frame of a compiled procedure initialized?’ More on stack frames see http://gfabasic32.blogspot.com/2009/08/obtaining-name-of-actual-procedure.html.
It turns out that the runtime DLL contains two additional INITPROC() functions; I called them INITPROC_EXE() because the call to INITPROC() in the debug version has been replaced to a call to a EXE specific initialization procedure. It still creates a stack frame, but it is a bit smaller because it lacks debug information. In addition, the EXE doesn’t contain symbol information whatsoever. There is no way an EXE can return information about symbols; procedure and variable names and their locations.
15 August 2009
Obtaining the name of the actual procedure
14 August 2009
After a good holiday
I hope you had a good holiday, we had. We rented a house at the border of a fjord in Norway, got ourselves a small boat and went out fishing everyday. We definitely want to get a small “hytte” over there.
During the holidays I got some mail at gfabasic32@gmail.com. Some were personal opinions about GFA-BASIC in general and some about GFA-BASIC 32 specifically. Mostly I reply these messages as soon as possible, but sometimes I save them for further examination. In general everybody gets a reply. If you didn’t got one yet, please repost your message.
A new season, a new start. Hope to post more blog entries this season.
27 June 2009
Mysterious errors
18 June 2009
Patching the GfaWin23.Ocx DLL
It is not a good idea to patch the GfaWin23.Ocx DLL. For instance, recently someone wanted to use the properties and methods of GFA-BASIC's RichEdit Ocx for the newer RichEdit control version 4.0 from MsftEdit.DLL. To accomplish this task the runtime DLL was patched by replacing some ASCII text using a HEX-file-editor. However the GfaWIn23.Ocx is updated regularly and patching each new update isn't an ideal solution to add new features to the BASIC. This got me thinking about adding new functionality to the runtime. In case of the newer RichEdit control I considered three options.
1 Use the WrapRichEd command
Consider the purpose of the patch. By forcing GFA-BASIC 32 to load a different richedit DLL and changing the class name used to create the RichEdit Ocx control, GFA-BASIC 32 will create the newer control when it invokes the statement Ocx RichEdit. Then, the properties and methods of the RichEdit COM interface can be used to manage the control. (Note most properties and methods are simply wrappers for the SendMessage(hwndEd, EM_xxx, wParam, lParam) function. These wrapper functions use an internal structure with state information GFA-BASIC uses to optimize the performance of the COM functions.)
Ok, back in 1999 the first GFA-BASIC 32 beta didn't contain the Ocx command. To wrap a control up in a COM interface GFA-BASIC 32 support(ed) the WrapCtrlName statements, like WrapRichEd. This command assigns a COM interface to a child control. The child control might have been created using a GFA-BASIC control command, like the general Control creation command, or simply by using an API function like CreateWindowEx(). You can still find examples of the use of the Wrap-commands in the \Samples directory. In general it is used as follows:
Dim rtb1 As RichEdit RichEditCtrl "", 101, x, y, w, h WrapRichEd 101, V:rtb1, "rtb1"
This encapsulates a RichEdit control with ID = 101 in an Ocx variable rtb1 and sets it event name to "rtb1".
Back to the issue at hand. The window class name of the newer RichEdit control is "RICHEDIT50W'. The current implementation of the Ocx RichEdit (or RichEditCtrl) statement is to load RichEd32.DLL and to create a control with the classname "RichEdit20A". To get a new control we need to load the MsftEdit.DLL first and then create a control of this class using the general command Control. Like this
Global Handle hMsftEdit = LoadLibrary("msftedit.dll") Assert hMsftEdit Global Dim rtb As RichEdit Control "", 3, "RichEdit50w", WS_BORDER, 100, 70, 100, 284 WrapRichEd 3, V:rtb, "rtbev"
The Ocx is now accessible by its variable name rtb and the event subs have the format Sub rtbev_Event(). Great!? Well, this process continues to work until you invoke a window-style property for the RichEdit Ocx, like rtb.BorderStyle=. In this case the control is destroyed and recreated, a process completely hidden for the developer. When the richedit control is recreated, the 'normal' GFA-BASIC 32 DLL code for the creation of a RichEdit Ocx is invoked, which then creates a "RichEdit20A" class control. Implicitly, the version 4.0 richedit control is destroyed and a richedit control of an older version is created. (The properties that cause a recreation of the RichEdit control are .HideSelection, .MultiLine, .ScrollBars, .BorderStyle, .TabStop ). Unless you give up these properties, this doesn't seem an ideal technique.
2 Poke the DLL (Advanced)
Under conditions this is a valid way to go. Only the loaded DLL is affected. The code is modified at runtime, for instance in the initialization process of your program. Such a modification isn't permanent, once your program has finished, the DLL is unloaded and the next time the original GfaWin23.Ocx is loaded again. When you insist on patching the DLL poking at runtime is definitely worth a shot. The API functions to use are VirtualProtextEx() and WriteProcessMemory(). VirtualProtextEx() is required before poking, because in this particular case the data section – which is read-only - of the DLL has to be patched.
In general you can use the addresses as they are showed in a HEX-file editor and poke those addresses. However, the system doesn't guarantee that a DLL is loaded at the preferred base address. In case multiple DLLs require the same base-address Windows may relocate one of the DLLs giving it a new address in (virtual) memory. You can invoke an additional LoadLibray("GfaWin23.Ocx") or GetModuleHandle() to obtain the base-address, of course you must decrement the DLL count immediately by invoking FreeLibrary(). Maybe I come back on this in some other post.
3 Send messages on your own (Preferred method)
When a GFA-BASIC 32 program requires a new or custom control it should proceed the API way. Point. Load the DLL and create a control using Control or CreateWindowEx(). Then send messages to manage the control and use the parent form's _Message sub event to respond to WM_COMMAND messages. When the custom control sends WM_NOTIFY messages use _MessageProc. More on _Message(Proc) see http://gfabasic32.googlepages.com/faqforms
To utilize GFA-BASIC 32 to the most, you can write wrapper functions and put them in a $Library. Most C/C++ include files for custom controls also present C/C++ macros that invoke the SendMessage() API to manage the custom control. You can easily convert them to Function calls and put them in a library.
25 May 2009
Update: Build 1165
Recently I uploaded a new update of GFA-BASIC 32 to http://GFABASIC32.googlepages.com/. The update includes both the GfaWin32.exe, the IDE, and the runtime GfaWin23.Ocx. Both got build number 1165, which you can check from within the Explorer (right-click), and for the runtime by using DllVersion$. For instance: Trace DllVersion$ ' "2.30 Build 1165" Debugging takes time It has been a while since the last update, but I had to do a lot of disassembling. Well, not exactly the disassembling-process itself because that task is performed by a program, but the interpretation of the disassembly. To be able to understand and interpret a disassembly you need to be able to guess what is going on so that you can recognize patterns. To understand the editor part of the disassembly you need to know how an editor is programmed, to understand a DLL-disassembly you are required to have knowledge of DLL programming, etc. Also, to understand the implementation of the COM controls (OCX), you are required to understand the concept of COM and how to develop OCX controls. So, each time I start interpreting a new part of the disassembly I must study the concepts of that particular issue before I can actually begin. To get acquainted with new information some time may pass.
Urgency due to Val-bug But it is time for an update now. The update contains things I have done in the past year, but it got some urgency due to the Val() bug. This bug was first reported in 2001, but I never got to it.
What is going on? A string having 15 digits and starting with the digits "281479…." was converted to the floating-point value 402…. The position of the decimal point and whether the value is positive or negative isn't important. The function responsible for the conversion isn't only invoked by Val() or ValDbl(), but is heavily used by the IDE (both editor and compiler) as well. I'm pretty sure I located the bug and solved it. I'm also pretty sure the string must fulfill the conditions mentioned to get wrongly converted. However, the chance that the string fulfills the first condition (having 15 digits) is rather big, because the floating-point conversion function Str$() often returns a string with 15 digits when the number of digits in the fractional part is indefinitely. However the number must actually start with the mentioned digits to return a wrong value. Over the years the problems with Val() are reported on a regular basis, so this situation occurs. Reason enough to update.
14 May 2009
Atari ST RIP
It has been a while since my last posting. For one a ST->GFA32 porting project came across which turned out to be not portable after all. It gave me an opportunity to actually use an Atari ST again, put a floppy disk in its built-in disk at the side and listen to those cranky noises when it loads. Wow, I actually thought I missed it, but I didn't. In my mind those Atari ST days have became more romanced than they were. The magic of the Atari ST lies in its innovative OS (GEM), but the innovation was magic because this was never seen before in those days.
The Atari 1040ST still stands next to me and each time I enter my room and I look at it, I don't long for those days anymore. The last years I seriously concerned buying an old Atari ST for the sake of old times. However, now it is here, my memories have vanished and are replaced by those that made me take the step to MS-DOS and Windows. The only thing that remains are the heartbeats I recall having when I first got GFA-BASIC to work and produced my first GEM program. I think these beats were the loudest ever heard as a result of a piece of software I created. That must explain romancing the Atari ST. For me, the most valuable lesson learned is to let the Atari ST rest in peace.
22 April 2009
The Command.Picture property
cmd.Picture = ImageListToolbar.ListImages(61).PictureBut I assumed wrongly. The .Picture property of the ImageList returned the handle of the masked bitmap only. So, to give a Command Ocx a transparent image from a ImageList Ocx use the .ExtractIcon method.
cmd.Picture = ImageListToolbar.ListImages(61).ExtractIconThe Command.Picture = pict pseudo-implementation is (without error and IsNothing checks) as follows: type = pict.get_Type handle = pict.get_Handle SendMessage(Command.hWnd, BM_SETIMAGE, (type=1? IMAGE_BITMAP: IMAGE_ICON), handle) Giving the Command.Picture property a Picture object containing an icon will result in a transparent image in a Button control.
08 April 2009
ANSI API functions and RichEdit
Previous RichEdit control versions (3.0, 2.0) support two window classes "RichEdit20A" for ANSI systems and "RichEdit20W" for UNICODE systems. When you were compiling a program (in C/C++) for a UNICODE program you would use the "RichEdit20W" class in the CreateWindowExW() API function. GFA-BASIC 32 uses the "RichEdit20A" window class to create a RichEdit Ocx using CreateWindowExA. The properties and methods of the RichEdit Ocx are implemented by sending control messages using SendMessageA().
After patching the rich edit control is created by invoking CreateWindowExA and passing a pointer to an ANSI string containing "RICHEDIT50W". How can that work? The new control only supports wide character coding, how can it accept an ANSI string? I think I found the answer. ANSI API functions are redirected to the wide (UNICODE) variant.
When a program is specifically created as a UNICODE application (which is possible only for NT 4.0 and higher) it uses the UNICODE versions of the system API functions. All API functions have an ANSI and a UNICODE variant with different(!) entry points in the system DLLs. For instance, the SendMessage() API comes in two variants: SendMessageA() and SendMessageW(), two different functions. The xW() system functions expect strings formatted in UNICODE format. On UNICODE Windows systems (thus everything above Windows 95/98/Me) the ANSI versions of the API functions first translate a string parameter to UNICODE and then call the xW() version passing the newly created UNICODE string. This makes ANSI programs quite slow when API functions use strings (upto 3 times, see "Under the Hood 1997" by Matt Pietrek).
GFA-BASIC 32 is an ANSI program and invokes only the xA() versions of the Windows API. Therefor, on Windows NT 4 and above any string passed is converted to UNICODE before handled in the xW() version of the function. (GFA-BASIC 32 is not prepared for pure UNICODE systems, it had to run on Winddows 95 as well.) Back to the question, why does the MsftEdit.Dll accept an ANSI string in CreateWindowExA()?Because in turn the CreateWindowExA() calls CreateWindowExW() on any 'not Win95/98/Me' system passing the classname "RICHEDIT50W" as a UNICODE string. This means, according to my findings that the MsftEdit.dll won't run under Windows 95 when used from the patched GFA-BASIC 32 Ocx-dll. These Windows systems don't redirect to a wide version of the API function.
BTW It might be useful to know how GFA-BASIC loads the appropriate richedit dll. Just before a RichEdit Ocx control is created, GFA-BASIC 32 loads the appropriate library, as follows: If hLibRichEd == 0 hLibRichEd = LoadLibraryA("RichEd20.dll") If hLibRichEd == 0 hLibRichEd = LoadLibraryA("RichEd32.dll") If hLibRichEd == 0 Then RaiseErr "OutOfMemory" Endif Endif Then "RichEdit20A" is used in a CreateWindowExA() API call to create a control window of the this class.
04 April 2009
GetFirstVisible method not implemented
Local ListItem li // Set li = ListView.GetFirstVisible replacement: Set li = ListView.ListItem(ListView.TopIndex + 1)
03 April 2009
Pointer and Gfa_Var object
- GFA-BASIC 32 is capable of generating two types of code to access data; directly and indirectly using C-like pointer code.
- A Pointer [To] type variable is 32-bits variable without a VarPtr address. The data memory address is set using Pointer=. Other than this, GFA-BASIC 32 treats the pointer as a normal data type.
- All ByRef subroutine arguments are Pointer To variables, whose VarPtr-address is set by the caller (instruction that executes the subroutine).
Because of the duality, GFA-BASIC 32 needs to handle a Pointer To variable differently from normal variables. A normal variable has a name and memory location, in fact the variable-name is an alias for a data location. As such a variable name is not known after compiling, all references to the variable name are removed and replaced by assembler instruction theat memory loaction. Therefor a normal variable has only one 'address', either returned by VarPtr (V:) or ArrPtr. A Pointer (32-bits wide) on the other hand is split in two separate units. The address the pointer is stored and the VarPtr address it is pointing to. A Pointer variable has two addresses. This is refelected in the Gfa_Var object. A Gfa_Var object is an item of Gfa_Vars collection, a collection of variables of a certain subroutine. The Gfa_Var has two properties that illustrate this concept:
Gfa_Var.VarPtr Gfa_Var.Addr
In case of a normal variable both properties return the VarPtr address, In case of a pointer the .Addr property returns the address of the Pointer variable and the .VarPtr property the address set using Pointer()=. To help investigate I wrote a small procedure to show the Gfa_Var properties in the Debug Output window. A call to ShowVar is done by including a copy of a Gfa_Vars collection item. In case of the example below, I inspected a global user-defined-type declared as Foo. To obtain a collection of global variables you either use Gfa_Globals or Gfa_Vars(""). To inspect a local variable, execute ShowVar from inside a procedure and use Gfa_Vars0!varname.
ShowVar Gfa_Vars("")!Foo, V:foo, ArrPtr(foo), _ "- Global Foo As Tfoo:" Proc ShowVar(vGfaVar As Gfa_Var, vp%, ap%, comment$) Debug comment$ Debug "Variable: "#39 & vGfaVar.Name & #39 & _ ", TypeName: "#39 & vGfaVar.TypeName & #39 & _ ", Sub: "#39 & vGfaVar.PName & #39 Trace Hex(vGfaVar.Type) Trace vGfaVar.Addr Debug "ArrPtr(" & vGfaVar.Name & ") = " ap Debug "VarPtr(" & vGfaVar.Name & ") = " vp Trace vGfaVar.VarPtr Trace vGfaVar.Size Trace vGfaVar.Len Trace vGfaVar.IsTyped Debug EndProc
31 March 2009
ByRef parameters are Pointers
Function CompareDates(ByVal lngParam1 As ListItem, ByVal lngParam2 As ListItem, ByVal iCol As Long) As LongWhy does it need to be declared as ByVal, rather than as ByRef? Because it isn't all that simple (understatement). Some data types force GFA-BASIC 32 to generate pointer code by default, these include user-defined-types (Type) and Ocx/COM objects. GFA-BASIC 32 generates pointer-to code for the ListItem COM object by default. When the parameter would be declared ByRef, the value passed by Windows would force GFA-BASIC to generate code that uses lngParam as a pointer to a Listitem pointer. Hence Excpetion Errors! Ok enough for now, but its not end!
Pointers are pointers indeed!
Local p1 As Register Pointer To Double ' double *p1;The syntax control accepts it, but the compiler doesn't put p1 in a register. Schade (unfortunately). On the other hand the speed increase would be minimal, it is the difference of using a register directly: mov dpt [esi], value ; set first long pointed to in esi and of first copying the pointer to eax from the its (stack) position: mov eax, dpt 116[ebx] ; move pointer content mov dpt [eax], value ; set first long to value GFA also optimizes te code by keeping the pointer in eax when the pointer is accessed multiple times in a row (see for yourself). Now we are where I want it to be. The compiler generates assembler code to access the data of a variable declared with Pointer [To] through a pointer, like C. For instance the second long eax points to is accessed using mov dpt 4[eax], value ; set second long And this is completely different from the code used to access a non-pointer variable. As a conclusion I will summarize how to treat a pointer in GFA compared to C:
' Declare and initialize a double Dim dbl As Double = 3.1 ' double dbl = 3.1; ' Declare an unitialized pointer to double Dim p As Pointer To Double ' double *p; ' Initialize the pointer Pointer(p) = V:dbl ' p = &dbl; ' Assign a value to dbl through p: p = 7.2 ' *p = 7.2
In the next post I discuss the ByRef and Pointer commands.
30 March 2009
Sort a ListView Ocx
' GFA32 argument ByRef, not by value! Sub ListView1_ColumnClick(ColumnHeader As ColumnHeader) Dim lngItem As Long ' Note: ' ColumnHeaders collection is 1-based ' ListItem.SubItems(iCol) is 0-based ' ListView.Sort iCol is 0-based Dim iSubItem As Long = ColumnHeader.Index - 1 ' 1-based 'Handle User click on column header If ColumnHeader.Text = "Name" Then 'User clicked on Name header 'ListView1.Sorted = True 'Use default sorting to sort the 'ListView1.SortKey = 0 'items in the list ListView1.Sort iSubItem, 0 ' 0-based Else 'ListView1.Sorted = False 'User clicked on the Date header 'Use our sort routine to sort by date SendMessage ListView1.hWnd, LVM_SORTITEMS, iSubItem, ProcAddr(CompareDates) End If 'Refresh the ListView before writing the data ListView1.Refresh ' MS/VB 'Loop through the items in the List to print them out in sorted order. 'NOTE: You are looping through the ListView control because when 'sorting by date the ListItems collection won't be sorted. For lngItem = 1 To ListView1.ListItems.Count 'ListView_GetListItem lngItem, ListView1.hWnd, strName, dDate 'Print ListView1.ListItems(lngItem).AllText Next ' GFA32 - The ListItems collection is sorted as well. Dim li As ListItem For Each li In ListView1.ListItems Print li.AllText Next End SubTo show the differences with VB the code is heavily commented. The clue is located in the next code line:
SendMessage ListView1.hWnd, LVM_SORTITEMS, iSubItem, ProcAddr(CompareDates)The .hWnd property returns the window handle of the ListView Ocx. The wParam argument specifies the corrected column number and the lParam argument holds the pointer to the function to compare two ListView items. The CompareDates function is declared as:
Function CompareDates(ByVal lngParam1 As ListItem, _ ByVal lngParam2 As ListItem, ByVal iCol As Long) As LongThe first two long integer arguments receive the lParam member of the LV_ITEM structure, which is in GFA-BASIC 32 a pointer to a ListItem object. The compare function is a callback function. It is called from inside Windows and never from a GFA-BASIC 32 statement. GFA-BASIC 32 cannot check the function parameters against the data types passed when called. When the program is executing the lParam members are simply copied to the first arguments of the compare function. Because we know the value points to a ListItem object, we cast the 32-bits value to a Listitem. The GFA-BASIC 32 compare function is much simpler, convert the text of the ListItems to apropriate data type and compare:
Function CompareDates(ByVal lngParam1 As ListItem, _ ByVal lngParam2 As ListItem, ByVal iCol As Long) As Long Dim dDate1 As Date, dDate2 As Date ' MS/VB way: 'Obtain the item names and dates corresponding to the 'input parameters °ListView_GetItemData lngParam1, hWnd, strName1, dDate1 °ListView_GetItemData lngParam2, hWnd, strName2, dDate2 ' GFA32 way. The LV_ITEM.lParam is passed to the compare ' function. The lParam contains the address of the ListItem ' object, which we intelligently casted to a ListItem object! dDate1 = ValDate(lngParam1.SubItems(iCol)) dDate2 = ValDate(lngParam2.SubItems(iCol)) 'Compare the dates 'Return 0 ==> Less Than ' 1 ==> Equal ' 2 ==> Greater Than If dDate1 < dDate2 Then CompareDates = 0 ElseIf dDate1 = dDate2 Then CompareDates = 1 Else CompareDates = 2 End If End Function