29 January 2012

Gfa_Setting() returns extra #0

Gfa_Setting is available as a statement and as a function. They are used as follows:

' Set/Create a subkey
Gfa_Setting("MySubKey") = mysetting$
' Get subkey value ($)
mysetting$ = Gfa_Setting("MySubKey")
' Delete a subkey
Gfa_Setting("MySubKey") = ""

The Gfa_Setting(subkey$) reads a value from the subkey under HKEY_CURRENT_USER\Software\GFA\Basic using the RegQueryValueEx() API function that retrieves the data for a specified value name associated with an open registry key. The advantage of using this API is that it doesn't require a data type. Even better, the function returns a code indicating the type of data stored in the specified value. The possible type codes are:

Const REG_NONE = 0
Const REG_SZ = 1
Const REG_EXPAND_SZ = 2
Const REG_BINARY = 3
Const REG_DWORD = 4
Const REG_DWORD_LITTLE_ENDIAN = 4
Const REG_DWORD_BIG_ENDIAN = 5
Const REG_LINK = 6
Const REG_MULTI_SZ = 7
etc..

After invoking RegQueryValueEx GB32 checks the value in the type parameter and than decides how to copy the data to the GB32 variable used in the var$ = Gfa_Setting(subkey$) code. When the type value returned is REG_SZ or REG_EXPAND_SZ or REG_BINARY then GB32 uses the StrPeek(addr, size) function to create and copy the data to the string. The size of the data is also returned by RegQueryValueEx. Simple enough. But there is a catch that is overlooked:

"If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, then the data will also include the size of the terminating null character or characters."

GB32 uses the size value to invoke StrPeek and stores the terminating #0 character inside the string and sets the length of the string to the size of data + 1 for the #0 character. GB32 uses the same function for Gfa_BinSetting() and does not care for the value of the size.

Now, when you apply a string concatenation (str & str2) the second string doesn't seem to be appended to the first string. The including #0 character in the last position of the string makes the use of string functions awkward. When you pass that string to a function that expects a C-string the string data is read to the first #0 character. Internally, and externally on the API level, many functions expect a C-string and stop processing the string at first occurrence of a #0 character.  

For now, you best use ZTrim() on the return value of Gfa_Setting(). It seems not to predict how many #0 characters will be included. See underlined words in the quotation.

' Get subkey string value
mysetting$ = ZTrim(Gfa_Setting("MySubKey"))

20 January 2012

Gfa_Line doesn't always redraw the caret

The GFA-BASIC implementation of the editor-extension contains a large set of Gfa_* functions and commands. However, in contrast with the term 'editor extension', the Gfa_* statements are not restricted to the (text) editor only. The GFA-BASIC IDE application itself is divided into several entities such as an application part, a form-editor, a compiler, and a text-editor. Only a part of the Gfa_ statements apply to the text-editor only, and the most important terms in this respect are 'text-selection' and 'insertion point'.

The active point
The text-selection represents a selection in the code text in the editor window. Note that a selection can be a single point (the insertion point) or a contiguous range of text. Using Gfa_ commands you can move the insertion point, control which text is selected, and modify the contents of the selection. When the text-selection is restricted to a single point, Gfa_Line and Gfa_Col return its position. Also, since there is no selection, Gfa_Line equals Gfa_SelLine and Gfa_Col equals Gfa_SelCol.

Whether or not the text is selected, the Gfa_Line and Gfa_Col always indicate the active point. This is the point where all other text actions are performed. When a range of text is selected the insertion point can be located at the beginning or at the end of the selection.

Command Description
Gfa_Line Gets/sets the line where the insertion point is.
Gfa_Col Gets/sets the column where the insertion point is.
Gfa_SelLine Gets/sets the line at the bottom of a selection. The bottom line is not necessarily at the active end of the selection.
Gfa_SelCol Gets/sets the column at the bottom of a selection. The bottom line is not necessarily at the active end of the selection.

Moving the caret
The insertion point is represented by a caret, is a blinking line, block, or bitmap in the client area of a window. The caret typically indicates the place at which text (or graphics) will be inserted. The caret is a system resource and controlled by invoking Windows system API calls such as ShowCaret() and SetCaretPosition(). Each time the caret is moved SetCaretPosition() is invoked.
Programmatically, you move the caret using one of the following Gfa_ commands:

Command Key Press
Gfa_Left [count ] LEFT ARROW
Gfa_Right [count] RIGHT ARROW
Gfa_Down [count] DOWN ARROW
Gfa_Up [count] UP ARROW
Gfa_PageDown PAGE DOWN
Gfa_PageUp PAGE UP

count is optional. A Long that specifies the number of times you want to repeat this action. The default is one.

  • When executing one of these commands (or pressing the key(s)) the current selection is canceled before the action is performed.
  • These are relative movements. They move the caret relative to the current active insertion point represented by Gfa_Col and Gfa_Line.
  • Many key press actions are not represented by a Gfa_ command. For instance: word-left, word-right; end-of-line, start-of-line; etc.

So, how do you move the caret to an absolute position, rather than to a relative one? Within a text line the Gfa_Col = newcolumn command will do the trick. It cancels any selection and updates the caret position. By assigning 0 to Gfa_Col the caret moves to the start-of-the-line and by setting it to some large number (_maxInt) is moves to the end-of-the-line. Consequently, you expect Gfa_Line to do the same for lines. Well it does modify the current line, but it sometimes 'forget" to update the caret visually. Meaning, it forgets to put the caret into its new position.

Gfa_Line = doesn't redraw caret
The Gfa_Line = command differs in two aspects from all other commands. First it doesn't cancel a selection, and, secondly, it doesn't always redraw the caret in it's new position. It does however change the current line used for text operations, which is important because some text operations are line oriented. So, without displaying the caret at the new position (combined with a possible scrolling) the current active line can be changed to perform actions on the text line (Gfa_Update, Gfa_Text = , delete and insert lines, setting a bookmark, clipboard actions, etc). The problem is that the editor extension implementation lacks a command to update the caret explicitly.

Workaround to redraw caret
To explicitly make the caret visible after invoking Gfa_Line = we must take into account that as portion of the text maybe selected. So, first we must cancel any selection and after invoking Gfa_Line =  we must redraw the caret ourselves.

Global Gfa_SetCaret As Pointer Byte
Pointer(Gfa_SetCaret) = 4ドルDC41C
Global Const Ed_SetCaretPos = 409ドルB99


' Cancel selection (if any) Gfa_Col = 0 ' Set/Goto new line Gfa_Line = iGotoLine ' Set global flag (when in OnWMCommand) Gfa_SetCaret = 1 ' Or call directly ~StdCall(Ed_SetCaretPos)()

Most (if not all) IDE commands are processed in the IDE's OnWMCommand() function. This is a central routine to execute all accelerator key commands, menu commands, and the GLL's key and menu events. Besides this, it adds the commands to the UNDO-buffer and stores them in a keyboard macro when Gfa_IsRecording = True. At the end of IDE's OnWMCommand() function the global variable Gfa_IsCaret is checked to see if any of the executed commands did set this flag (to have the caret redrawn). If so, before returning from OnWMCommand() the IDE's Ed_SetCaretPos() function is invoked to redraw the caret and Gfa_SetCaret is cleared.

Now you have some background knowledge of Gfa_Line you have the tools to properly redraw the caret.

14 November 2011

GFA-BASIC 16 Bit Revival

The Frenchman Michel Goux has assembled all tools for a proper installation of GFA-BASIC for Windows 16 Bit (also known as Windows 3.1). All tools like IDE, Compiler, Linker, ExtTool, etc are to be downloaded into one new package. The package also includes tips for running the 16-bits version on more recent OS's like Vista-32 and Windows 7-32.

Unfortunately, it will not run on 64-bit systems? Maybe some have a solution for that? 

Go to the Michel's GfaWin16 download page.

You can also check-out my GFA-BASIC 16 page for downloading additional stuff.

08 November 2011

Apply XP Visual Styles

 

Running GFA-BASIC 32 under XP or better (Vista/7) it applies the Windows XP Visual Styles. The controls are drawn using a new look using a newer common control library. The IDE uses the newer look because of the presence of manifest file in the same directory as the GfaWin32.exe. When you create an EXE for your own GFA-BASIC application you must not forget to include a manifest file using the same name as your application.

Go to the CodeProject site for a brief introduction on Applying Windows XP Visual Styles.

For an implementation of a GFA-BASIC 32 project go to Peter Heinzig site for an example http://www.peterheinzig.de

02 November 2011

Dim ... As New Interface?

What does New mean when used in a declaration statement? Why is New limited to only 8 so called OCX types? What are OCX types anyway? Confused? Let us continue the story on COM.

OCX Objects
By now you know that in GFA-BASIC 32 OCX windows are, in fact, normal  windows and controls wrapped up in a COM Object. For instance, the very familiar OpenW # command still creates an overlapped application window, but it now also creates a COM wrapper of data type Form. Not only creates OpenW a window using the CreateWindowEx() API, but also allocates an additional block of memory  (using malloc) to store all COM related information for the Form object type.

A GFA-BASIC OCX object is a window wrapped in a COM object. An OCX object requires a window handle.

Information about the OCX 
An OCX object is also called an ActiveX control (hence the window handle). GFA-BASIC OCX objects must behave as proper ActiveX objects as described in the OLE/COM documentation. (Actually, OLE is an old name. On the way it changed to COM.)
To use/control a COM object it must somewhere describe (publish) its functions, called properties and methods. This is done by including a type library with a COM object. The type library also specifies whether the COM object supports the creation of an instance of that object. This literally means that the COM object provides a class (function) doing a malloc() to allocate the memory to store the object.

A type library specifies the properties and methods of a COM object (interface), and whether it can be created on behalf of the client (coclass).

Declaring a COM Object
If you have ever used OleView.exe to examine GFA-BASIC's COM objects, you may have noticed the term interface. The interface specifies the name of the COM type and the functions used to access it. The interface name always starts with a capital I - like IForm, ICollection, etc. When used in code the capital I is lost (VB convention, duplicated by GB32).

All the interfaces defined in the type library from the GFA-BASIC Ocx runtime DLL  are added to the GFA-BASIC 32 internal list of data types. (This list is accessible through the Gfa_Types collection.) The list of data types contains all primary types like Bool, Byte, Card, Int32, Long, Currency, String, etc. All names stored in the type-list can be used in a declaration command like Dim, Global, and Local.

There is small problem though. GFA-BASIC 32 doesn't add the COM types by loading them from the type library. The addition of COM types like Collection, DisAsm, TextBox, etc. is hard-coded into the IDE code. Unfortunately! Otherwise we could have easily added third-party COM references to our applications.

To understand how GFA-BASIC handles a declared COM data type we can compare a COM object type to a String data type. For instance

Dim MyName As String
Dim frm As Form

These declarations don't do much. MyName and frm are variable-names and both are an alias for a 4-byte memory location. Initially, the value stored at the addresses is zero (pointer is Null), meaning no additional memory has been allocated for these variables. The pointers remain Null until something is assigned to the variables. Because MyName is of type String, GFA-BASIC knows how to do that and how to interpret the pointer at the location MyName.

The same is true for COM data types. Declaring a COM data type does not create the memory for it. The frm variable references some memory-address, but it does not actually create a window on the screen. First the window must be created and then the variable can be assigned an object of type Form. For COM objects a special command is invented to assign one COM object to another Set:

OpenW 1
Set frm = Win_1

Most GFA-BASIC 32 COM objects are created implicitly. OpenW creates a Form object, OCX TextBox creates a TextBox object, and so on. In addition, OCX controls that require multiple subitems create collections. A ToolBar OCX creates a Buttons collection of Button items.
For implicitly created objects GFA-BASIC 32 is responsible for memory allocation, initialization, and releasing memory.

Some COM types are created explicitly when some data is loaded. The LoadCursor() function creates a MouseCursor object and LoadPicture() creates a Picture object. These functions can be seen as 'a kind of New'.

The New keyword
The New keyword is used with some COM types that are available at your request. These types are independent of any OCX type. The New keyword allows you to create an instance of the object while declaring it. In GFA-BASIC 32 there are only 8 of these COM types allowed.

Dim f As New Font ' IFont2
Dim sf As New StdFont ' IStdFont
Dim p As New Picture ' IPicture2
Dim sp As New StdPicture ' IStdPicture
Dim cd As New CommDlg 
Dim col As New Collection
Dim dis As New DisAsm
Dim iml As New ImageList
  • Font (IFont2) and Picture (IPicture2) are GFA-BASIC implementations of the standard COM types.
  • StdFont and StdPicture are the types from the standard OLE libraries. Font and StdFont are 'compatible'. A variable of type Font can be set to an instance of StdFont and vice versa. The same is true for Picture and StdPicture.
  • The CommDlg and ImageList objects can be created using the OCX command as well.
  • Once the object created with New goes out of scope the object is automatically released.
  • Objects can be released explicitly by setting the variable to Nothing.

Other objects are single-instance objects created at runtime. GFA-BASIC 32 prohibits the creation of additional, new objects of these types. There is only one instance of App, Screen, Printer, ClipBoard, Code4, Debug, and Err. The single-instance objects are released when the program is terminated.

23 October 2011

Cleanup after an exception

In the previous post Don't use END to terminate your application I discussed why End messes up the memory available to an application. The same is true for Assert and Stop (when you use to terminate the application).

Main exception handler
These commands raise an exception caught by the nearest exception handler. This might be a custom Try/Catch handler in your program, but most often it passed on to the applications main exception handler. The main part of a GB32 application is surrounded with a GB32 generated application-exception-handler that releases file handles and memory associated with GB32 data types. The OCX forms/controls are not part of the cleaning process. To properly release all COM object memory all forms have to be closed by an internal function Destroy_Form(). The forms in turn are responsible for releasing the OCX objects of the child windows (controls).

Custom handler
When you use a custom structured exception handling (Try/Catch) in your application the exceptions might be caught by your application's defined custom handler. Now the exception is not passed on to the main application-handler and no memory and file handles are released. When you are put in this situation, you can select the clean-up command from the IDE's menu to close all forms and file handles.

Cleanup opened Forms and files
GB32 includes a library function that can cleanup non-closed forms and files after an application terminates abruptly. The DLL-function is located in the runtime and is exported by ordinal #49. The function is automatically invoked when you RUN a program inside the IDE. But you can also invoke it by selecting 'Clean up' (shortcut Ctrl-L) from the IDE's menubar. The menuitem  executes the Gfa_CleanUp command that invokes the DLL cleanup function.

Why would you use Gfa_CleanUp when the same function is executed at the next RUN of your code? Well, programs that are terminated improperly (END, ASSERT, or any exception the program causes) leave with invalid memory-pointers and Gfa_CleanUp might fail (mostly likely it will).

Gfa_CleanUp executes within a exception handler itself and it first calls DLL function #49 to close the forms. If it returns properly it calls exported function #119 which cleans the file handlers. In short, this is what Gfa_CleanUp looks like:

Try
  G49_CleanUpForms()
  G119_CleanUpFiles()
Catch
EndCatch

When the G49_CleanUpForms() DLL function causes an exception the G119_CleanUpFiles() is never executed!

The G49_CleanUpForms() simply enumerates over the Forms collection to call each Destroy_Form() internal function. This function is responsible for deleting form-specific COM objects for Fonts, MouseCursors, Pictures, Icons, AutoRedraw bitmaps, Menus, controls, graphic settings, end other related COM objects. This is a very large function responsible for properly clearing (in the right order) all created COM objects. When something didn't work correctly in your application and it was terminated in an unstable state, it is most likely this function might fail. This might leave behind all kinds of allocated COM memory. In addition, the function might have generated an exception, most likely of an invalid memory pointer, and from the pseudo code above you then see that the file handles aren't released at all.

In conclusion,

  1. When you set up a new application use a proper message loop and try to created one main-form that owns other forms (set the Form.Owned property for the second and later forms in the Properties window). Closing the main-form will close all other forms.
  2. Don't use END and be careful with ASSERT. When an application terminates improperly try using invoke Gfa_CleanUp.
  3. Fix an exception error and make sure it won't happen again. Then restart the IDE to get rid of memory left-overs.

16 October 2011

Don't use END to terminate your application

Using the End statement is like waking up from a nightmare. After multiple 'runs' in the IDE the GFA-BASIC 32 IDE might stop working at all and crashes eventually. End does not behave as it did in previous versions of GFA-BASIC where it was safe to use it as the application termination command. In GB32 you should never use End to terminate a program. Here is why.

End is defined as to terminate the execution of an application immediately. End is never required by itself but may be placed anywhere in a procedure to end code execution, close files opened with the Open statement and to clear variables.

The End statement stops code execution abruptly, without invoking the any window events, or any other code. Code you have placed in the form_Destroy, form_Close, etc, is not executed. GB32 data types referencing allocated memory at runtime (strings, arrays, hash tables) are properly destroyed, and files opened using the Open statement are closed. Object references held by other programs are invalidated.

The End statement provides a way to force your program to halt. For normal termination of a GB32 program, you should close all forms. Then when you use the 'Do : Sleep : Until Me Is Nothing' loop your program closes as soon as there are no more forms open. This is the only proper way to run a program!

Note The same is true for Quit, Assert, and Stop. These commands do not terminate your application properly.

It is an exception dude
For the curious. When GB32 encounters the End, Assert, and Stop commands GB32 it inserts code that raises an exception event: GB32 inserts a call to the RaiseException() API. An “exception” is an event that is unexpected or disrupts the ability of the process to proceed normally. However, GB32 supports structured exception handling (Try/Catch) the exception is caught by the nearest exception handler. In most cases, and certainly when End is used to terminate an application, the exception is caught by GB32's main-program exception handler. This handler is implicitly inserted around the code in the main program part of a GB32 program. Each unhandled exception is eventually handled by this main exception handler. The handler does nothing more than clearing variables and closing file handles.

So, when GB32 suddenly stops working, make sure your code let the OCX forms and controls properly close their windows in a natural order.

Also Known As bugs
NOTE – Don't blame GB32 too soon! Their are little or no bugs in the memory handling of GB32. Without pointing any fingers..., it turns out that most GB32 blamers are frustrated programmers that can't get their program(s) to work. Often the program structure is wrong or the programmer is under-qualified. This then leads to mysterious mistakes, AKA GB32 bugs.

05 September 2011

CreateObject peculiarities

Why CreateObject isn't fully compatible to VB(Script).

Introduction
The GFA-BASIC 32 function CreateObject() creates and then references a COM automation object. Usually the function is used to control a target application (or documents or other objects supported by the target application). Controlling a target application through COM (or OLE) is called OLE Automation.

Understanding OLE Automation
OLE Automation is an OLE service for integrating applications. It enables an application to expose its functionality (MS Word), or to control the functionality of other applications on the same computer or across networks. As a result, applications can be automated and integrated with programming code, creating virtually endless possibilities. OLE Automation is the umbrella term for the process by which an OLE Automation controller sends instructions to an OLE Automation server (using the functionality exposed by the OLE Automation server), where they are run.

CreateObject returns a reference to an object determined by the class argument.

Syntax: Set object = CreateObject(class)

Depending on class, the reference may be to an existing object or to a newly created object. The object reference returned by CreateObject is (usually) assigned to an object variable of type Object. Assigning a COM object to a variable differs from assigning a value or variable to another variable. It always requires the Set statement (as opposed to the Let statement that is implicitly used by other assignments).

Note - Using CreateObject (or GetObject) to set the Object-variable to the target application, or some object that the target application supports, establishes a reference to the target application's Application object. Then the object variable in the sourcecode can use the methods and properties of the corresponding object in the target application.

The ProgID of an automation object
To create an automation object the class argument of CreateObject(class) can be a string of the form "appName.objectType". A string using this format is called a ProgID.
A ProgID is a string that gets converted to a CLSID (a 128-bits GUID value) by looking up the string in the register. Note that the automation object can only be found when it has registered itself using the same string format "appName.objectType". This happen,s for instance, with MS Excel which registers itself as "Excel.Application". And Internet Explorer as "InternetExplorer.Application". These applications can be referenced through automation as follows:  

Dim obj As Object
Set obj = CreateObject("Excel.Application")

The class argument
In GFA-BASIC 32 the class argument may also specify CLSID either as a string (1) or as GUID constant (2). For instance:

Dim obj As Object
Set obj = CreateObject("7b55b57b-cd72-449d-82eb-a02e0964e3eb")

GUID gd = 7b55b57b-cd72-449d-82eb-a02e0964e3eb Set obj = CreateObject(gd)

Remarkable features of GFA-BASIC's CreateObject() are:

  • The class$ argument can specify a GUID value as a string.
  • The parameter can specify the address of GUID user-defined type. (The GFA-BASIC statement GUID name = xxx..-..-.._..-...x defines a constant (Long) as the address to a GUID value.
  • The syntax does not support the second optional parameter server as is described in Help documentation.
  • The language locale of the target application can be set prior to invoking CreateObject() using Mode(Lang) = .
  • The CreateObject() function is not 100% VB(Script) compatible.

CreateObject 's compatibility
In COM terms CreateObject() connects to a coclass at run time using the IDispatch interface (late binding). The Object variable type performs property and method operations using the IDispatch interface functions. Once you’ve created a new object and returned a reference to it, you can work with the object in GFA-BASIC in the same way you would work with any other object. That is, you can set and retrieve the object’s properties and apply its methods. To be an automation object the object must support IUnknown and IDispatch. All COM objects support IUnknown, because that defines them as a COM object. See this post on COM interfaces.
The IDispatch interface defines late binding and allows a client to call the methods and properties on the server. Consequently, without supporting an IDispatch interface a server cannot be called an automation controller. GFA-BASIC 32 consequently asks the automation object for an IDispatch interface and only connects to the coclass when it acknowledges that it supports an IDispatch interface. Otherwise it generates a runtime error.

VB(Script) are wrong
However VB and VBScript implement CreateObject differently, more flexible maybe, but essentially wrong. At the heart of creating an object is the OLE function CoCreateInstance(). It takes a pointer to the interface the program wants to use to communicate with the object. Since 'OLE automation' is required, it seems logical to pass the GUID for the IDispatch interface (as GFA-BASIC 32 does). But VB(Script) doesn't. The VB-CreateObject passes the GUID for IUnknown which every COM object supports, because IUnknown is what an object makes a COM object in the first place! So in VB CreateObject always works, even when it shouldn't. But the object reference returned by VB's CreateObject is not guaranteed to support IDispatch.

Porting VB to GB
This causes trouble when porting a VB program to GB. Implicitly CreateObject calls the object's QueryInterface(), asking it if it supports IDispatch. A correct behavior for QueryInterface(0 would be to reply for each interface it supports. Badly written OLE automation objects sometimes forget to reply for all interfaces it supports (especially IDispatch). One of them is Microsoft's type-library-reader-tool  "TLI.TypeLibInfo". CreateObject fails with GB where VB does return a reference:

Set TLInfo = CreateObject("TLI.TypeLibInfo")

When GB's CreateObject doesn't work where VB(Script) works, don't blame GFA-BASIC 32, but the COM programmer instead. BTW in these cases you must use the CoCreateInstance() API.

15 July 2011

COM (OOP) in GB32 - IUnknown (1)

In this part I'll show you the layout of a COM class as it could be implemented in GFA-BASIC 32. Note that we restrict ourselves to a minimalist COM object that supports the IUnknown interface only. We will not yet create properties and methods.
The GFA-BASIC 32 approach is loosely based on the article series COM in plain C by Jeff Glatt, specifically Part 1 and Part 2. However, in the first step we are not going to use type libraries, we are not going to store the COM object in a DLL, and we are not going to define another COM object that creates the one we define. COM is merely a binary standard; it dictates how to layout an array of function pointers and where to put the address of this array (of function pointers). It also dictates how to add reference counting. To comply to this standard a COM object must at least contain three pointers to pre-defined functions, also known as the IUnknown interface. In GFA-BASIC 32 this could look like this:
// IUnknownBlog.g32 17.07.2011
Debug.Show
// Our COM object: an implementation of IUnknown
GUID IID_IUnknownImpl = 9578fdab-97cb-4322-99e4-699abd26be1d
Type IUnknownImpl
 lpVtbl As Pointer IUnknownImplVtbl
 RefCount As Int
EndType
// VTABLE (an array of function pointers)
Type IUnknownImplVtbl
 QueryInterface As Long
 AddRef As Long
 Release As Long
EndType
Static IUnknownImplVtbl As IUnknownImplVtbl
With IUnknownImplVtbl
 .QueryInterface = ProcAddr(IUnknownImplVtbl_QueryInterface)
 .AddRef = ProcAddr(IUnknownImplVtbl_AddRef)
 .Release = ProcAddr(IUnknownImplVtbl_Release)
EndWith
// First create a heap allocated instance
// of our implementation of IUnknownImpl
Dim pIUnk As Pointer IUnknownImpl
Pointer pIUnk = mAlloc(SizeOf(IUnknownImpl))
Pointer pIUnk.lpVtbl = *IUnknownImplVtbl
pIUnk.RefCount = 1
Dim obIUnk As Object
{V:obIUnk} = V:pIUnk
Trace obIUnk
Dim o As Object // assign to other
Set o = obIUnk // AddRef() call
// two calls to Release
/*** END ***/
GUID IID_IUnknown = 00000000-0000-0000-c000-000000000046
Global Const E_NOTIMPL = 0x80004001
Global Const E_NOINTERFACE = 0x80004002
Declare Function IsEqualGUID Lib "ole32" (ByVal prguid1 As Long, ByVal prguid2 As Long) As Bool
Function IUnknownImplVtbl_QueryInterface( _
 ByRef this As IUnknownImpl, riid%, ppv%) As Long Naked
 Trace Hex(*this)
 {ppv} = Null
 If IsEqualGUID(riid, IID_IUnknownImpl) || _
 IsEqualGUID(riid, IID_IUnknown)
 {ppv} = *this
 IUnknownImplVtbl_AddRef(this)
 Return S_OK
 EndIf
 Return E_NOINTERFACE
EndFunc
Function IUnknownImplVtbl_AddRef(ByRef this As IUnknownImpl) As Long Naked
 Trace Hex(*this)
 this.RefCount++
 Return this.RefCount
EndFunc
Function IUnknownImplVtbl_Release(ByRef this As IUnknownImpl) As Long Naked
 Trace Hex(*this)
 this.RefCount--
 If this.RefCount == 0 Then ~mFree(*this)
 Return this.RefCount
EndFunc
Copy it to a new GFA-BASIC 32 application and save as IUnknownBlog.g32. Try to run it; it should compile and run flawlessly. Our first minimalist COM object/class has been defined and is up and running. Of course it doesn't do anything, but the we have an object that integrates with GFA-BASIC 32 and fully complies to the COM binary standard.
Assign to Object
Let us take a brief look at the code. Since this project isn't meant for the beginner, I'll walk you through it in big steps.
The Object data type holds a pointer to a piece of memory of at least 4 (lpVtbl) bytes. Mostly it contains another integer for a reference count. In GFA-BASIC 32 the Ocx variables always define their count in the second slot, we will use that as well.
To put a memory address in an Object variable we usually use Set obj2 = obj2. GFA-BASIC 32 checks for two proper COM object types at compile-time. Since we have mAlloc-ed address only this syntax wouldn't work. We must write it to Object directly. For the same reason we set the RefCount to 1 by hand.
The array of functions (VTABLE) is stored in a Type and shared with all instances of our custom COM object. Therefor a static (global) variable is used and initialized once. Each new COM object should hold the vtable-address in its lpVtbl member.
_QueryInterface, _AddRef, and _Release
The application COM functions _QueryInterface, _AddRef, and _Release are never called directly, but always by GFA or another COM object. They clutter up our application code and are in fact just boiler plate code. We must do something about that, for instance put them in a $Library. Also note the Naked attribute. These functions are never executed in the context of the application and need no TRACE code and Try/Catch-exception handler. They should be as naked as possible to gain the best performance.
The Trace *this commands in the code are to verify the address passed. Also note the type of the this pointer. COM passes the address of the COM object by reference and by adding the correct type into the function declaration we can access its members directly. All other parameters are simple placeholders for addresses we don't use or pass on.
In the next part we will implement an IDispatch object and try to integrate the COM code more into GFA-BASIC 32.

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.

14 May 2011

Visual Styles, Ocx Button, and ForeColor

The BUTTON window class is the base windows class for the Command, Frame, Option, and CheckBox Ocx controls. These different OCX controls are created by passing a special BUTTON window style (BS_) in the style parameter of the CreateWindowExA() Windows API.

GFABASIC 32 Ocx BUTTON window Style
Command BS_PUSHBUTTON, BS_DEFPUSHBUTTON
CheckBox BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE, and BS_AUTO3STATE
Option BS_RADIOBUTTON and BS_AUTORADIOBUTTON
Frame BS_GROUPBOX

As you can see the names of the Ocx controls conform to VB control names, which was one of the main changes in the GFA-BASIC 32 development. Another thing adapted from VB is the ability to change the controls behavior and appearance through properties. These are either changed in the Properties sidebar in the form-editor or in source code using the COM properties interfaces. In this case it doesn't matter how the appearance is changed, but how GFA-BASIC 32 implements the VB compatible behavior.

First of all, the implementation is VB compatible, but totally different. In most case (if not all) the GB32 implementation of properties and methods is much faster and cleaner. VB and VB.NET are snakes compared to the fast performance of GB32 applications. Currently, performance doesn't count as strong as in previous decades, but still. Maybe with Windows 7 for Tablets performance and size counts again. Who knows?

BUTTON drawing
The BUTTON control sends the WM_CTLCOLORBTN message to the parent window of a button when the button is about to be drawn. By responding to this message, the parent window can set a button's text and background colors. In GFA-BASIC 32 applications without the Visual Styles enabled this works as documented. However, when an application is accompanied with a manifest the new Visual Styles are applied and take precedence over the GFA-BASIC implementation.

When the Windows OS encounters a manifest file, the OS uses a different version of the common control library and globally subclasses all Windows controls, including the 'old standard' controls like the BUTTON class. GFA-BASIC 32 wasn't developed when the Visual Styles were around and uses a speedy drawing algorithm based on the classic behavior of the BUTTON class drawing. As a result, the Visual Styles improvement is not applied to the text color of the BUTTON class.

ForeColor cannot be set
Thus, the text color of Command, CheckBox, Option, and Frame Ocx cannot be changed by changing their ForeColor property when using a manifest file.
When you really need to change the text color I'm not sure how to proceed, so you are your own there...

10 May 2011

ShowFolders Sample

The ShowFolders method of the CommDlg Ocx object keeps raising questions. Therefor an example of an extended browse for folders dialog box.

The API behind the dialog box of the ShowFolders method is the SHBrowseForFolder() Shell function. Actually, there are two styles of dialog box available. The older style is displayed by default and is displayed using the BIF_ constants specified in the previous blog CommDlg.ShowFolders. However, the list of constants is a subset of the total number of flags. To specify a dialog box using the newer style, you should pass the BIF_USENEWUI flag in the ShowFolders method.

image

The newer style provides a number of additional features, including drag-and-drop capability within the dialog box, reordering, deletion, shortcut menus, the ability to create new folders, and other shortcut menu commands. Initially, it is larger than the older dialog box, but can be resized by the user.
The dialog box can be displayed using the following code.

Public Const BIF_RETURNONLYFSDIRS = 0x0001
Public Const BIF_DONTGOBELOWDOMAIN = 0x0002
Public Const BIF_STATUSTEXT = 0x0004
Public Const BIF_RETURNFSANCESTORS = 0x0008
Public Const BIF_EDITBOX = 0x0010
Public Const BIF_VALIDATE = 0x0020
Public Const BIF_NEWDIALOGSTYLE = 0x0040
Public Const BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX)
Public Const BIF_NONEWFOLDERBUTTON = 0x0200
Public Const BIF_BROWSEFORCOMPUTER = 0x1000
Public Const BIF_BROWSEFORPRINTER = 0x2000
Public Const BIF_BROWSEINCLUDEFILES = 0x4000
Global cd As New CommDlg
cd.Title = "GFA-BASIC32 ShowFolders Demo"
cd.ShowFolders BIF_USENEWUI
If Len(cd.FileName) Then _
 MsgBox "Folder Selected: " & cd.FileName

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

10 April 2011

An Array of Pointer To

The "Pointer To" data type isn't a key feature of (GFA-)BASIC. A BASIC programmer should not be bothered with pointers, at least that is the consensus. If you like to know more about pointers in GFA-BASIC check out ByRef arguments are Pointer To variables.

Introduction
One of the many variants of "Pointer To" I never considered; a declaration of an array of type "As Pointer To type" . For example, what does this do?

Dim a(1) As Pointer Large

It turns out that (with Option Base 0) the array contains two elements (0,1) of int32 data type. Two Longs are allocated and not two Large variables (int64). The same is true of all other types. Due to the Pointer keyword the elements are only 32-bit wide and are initialized to Null.

Code
This time I don't present a smoking explanation but sample code only. It contains 3 examples of 3 different array types, but they are all of Pointer To. See what happens.

' Essentially, it is an array of 32-bit integers
' to hold addresses of memory locations.
' The array's data type determines how to use
' the address stored in the elements.
' Example 1 - Array as a Pointer To Large
Dim a(1) As Pointer Large
Trace ArraySize(a()) ' 8 bytes, thus 2 * 4 bytes
' Assign memory locations to the elements:
Dim il As Large = 9, jl As Large = 10
Pointer(a(0)) = V:il ' set array-elem to address of i
Pointer(a(1)) = V:jl
' Due to "As Pointer To Large" GFA performs
' an indirect access using the addresses stored in
' array elements. It does NOT directly return the
' values stored in a(0) or a(1) (which are memory locations).
Trace a(0) ' = 9
Trace a(1) ' = 10
' How does GFA do this?
' Well, let us see what is actually stored in a(0) and a(1).
' We use ArrayAddr() to obtain the addresses of the array elements and then Lpeek the address stored there and then use Large{adr} to obtain the value.
Dim adr As Int
adr = Int{ArrayAddr(a()) + 0} ' a(0)
Trace Large{adr} ' = 9
adr = Int{ArrayAddr(a()) + 4} ' a(1)
Trace Large{adr} ' = 10
' Example 2 - Array as a Pointer To String
' Again an array of 32-bit integers, but now they must
' be set to point to the string's descriptor.
Dim s(1) As Pointer String, st As String = "Hello"
Trace ArraySize(s()) ' still 8 bytes
Pointer(s(0)) = *st ' assign string descriptors
Pointer(s(1)) = *st ' assign string descriptors
' See how GFA correctly uses the addresses to
' obtain the strings:
Trace s(0) ' = Hello
Trace s(1) ' = Hello
' Internally handled as:
Trace Char{{{ArrayAddr(s()) + 0 }}}
Trace Char{{{ArrayAddr(s()) + 4 }}}
' Example 3 - Array as a Pointer To UDT
' Again an array of 32-bit integers, but now they must
' be set to point to instances of ArrayDesc type.
Dim ad(1) As Pointer ArrayDesc
Trace ArraySize(ad()) ' still 8 bytes
' Use ArrayDesc of previously declared arrays
Pointer(ad(0)) = *a() ' assign addr of ArrayDesc a()
Pointer(ad(1)) = *s() ' assign addr of ArrayDesc s()
' See how GFA correctly uses the addresses
Trace ad(0).ptype ' 37 (24ドル + 1ドル)
Trace ad(1).ptype ' 73 (48ドル + 1ドル)
' Internally handled as:
Trace Int{{ArrayAddr(ad()) + 0 } + 4}
Trace Int{{ArrayAddr(ad()) + 4 } + 4}
Type ArrayDesc
 -Int Magic ' 4 Byte ASCII
 -Int ptype ' vtType
 -Int size ' size of the datatype
 -Int dimCnt ' number of dimensions
 -Int dimCnt2 ' Erase and $ArrayChk
 -Int paddr ' ArrayAddr()
 -Int corr ' correction value
 -Int paddrCorr ' void*addrCorr;
 -Int anzElem ' number of elements
 -Int sizeArr ' size in bytes ArraySize()
 -Int Idx(3) '[3][1]; //Open Array
EndType

Note - A declaration of an array creates an array descriptor which the array variable references. Operations on the variable are performed using the content of the descriptor. This is not about the array descriptor, but it illustrates what is allocated with Dim. The UDT ArrayDesc is used as an example for an array of Pointer To UDT.

Conclusion
GFA-BASIC 32 returns the content of array elements correctly, but this only works when the array elements are assigned the addresses of memory locations that need to be read using the arrays data type.

09 April 2011

Status of GFA-BASIC 32

It has been a while, but I certainly didn't give up on GFA-BASIC 32. In fact, I've done some fundamental research this winter to figure out a (new) direction for GFA-BASIC 32.

COM is a must
Each new Windows OS provides more COM based interfaces, rather than new DLL function APIs. A modern language must support the use of COM interfaces, and I've done a lot of research in that area. The first step in the process should be the use of non-ActiveX COM libraries. This requires adding COM references to a GFA-BASIC 32 project. Once this is completed, the language should have the ability to provide COM classes as well. So, in the next stage, GFA-BASIC 32 must support object-oriented structures like VB's Class/End Class. Finally, research must be done to evaluate the opportunity to add third party ActiveX controls.

Maintaining GFA-BASIC
This is not the only thing that is on my mind. I often get questions or receive bug reports that require a lot of time to investigate. I only have a heavily commented disassembly, I still don't understand all of it. Once I found a bug or improvement it needs to be implemented. Well, patching is an art in itself and often can be done in multiple ways. Sometimes I need to acquaint myself with entirely new ways of hacking. This costs too much time, so I need to figure out how to proceed with that. My first priority is to implement COM support.

Assemble in GFA-BASIC?
But, only recently (!) I realized I could maybe copy the disassembly to GFA-BASIC 32 and use GFA-BASIC's assembler to re-create the executable (GfaWin32,exe). I made some trials and it might actually work! That would mean I could edit the (assembly) code of the IDE directly inside GFA and then recompile (F5). Wow, maybe I give this my number one priority ...

Next blog entry
One of the questions I received contained a statement I wasn't familiar with. The code contained a declaration of an array of type "Pointer To Int". I had no idea what that meant. Do you? If not check out the next blog.

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

22 November 2010

ImageList Ocx Part 2

In the introduction to the GFA-BASIC 32 ImageList Ocx (1) the emphasis was placed on the creation of the image list common control, which – as we saw - is delayed to the moment the first picture is added to the control. All images have the same size. The size is determined by the first picture's height and width, or the size values set using the .ImageWidth and .ImageHeight properties of the ImageList Ocx. The .ColorFormat property specifies how the pictures are added and whether or not a mask is generated. The color for the mask is specified using the .MaskColor property.

After the creation
Once the underlying image list common control is created, the .ColorFormat, .ImageWidth and .ImageHeight properties fail to accept a new value. These properties raise the E_FAIL COM error. This is also demonstrated when you use the 'ImageList Data' dialog box to add images to an ImageList Ocx. The upper left controls contain the options to create the image list common control. You may specify the required size and color format. The default settings are zero for both the size properties, and the 0 (device dependent/mask included) ColorFormat property.
After adding an image these control are disabled, because ImageList_Create() has been invoked using the settings from these controls.
[image17.png]
Using masks in GFA-BASIC 32 is a bit confusing because its setting is the opposite of Windows API.

Masks in GFA-BASIC 32
By default GFA-BASIC 32 creates a mask for each and every picture you add to the ImageList control. Whether or not the mask is added to the underlying image list common control is determined by the .ColorFormat property value.
In contrast with the API specifications, GFA-BASIC 32 uses a reversed setting for the mask option. The flags argument in the ImageList_Create() function specifies the mask using the ILC_MASK bit value. In GFA-BASIC 32 you don't explicitly specify a mask by setting bit 1 to 1; in GFA-BASIC the .ColorFormat value must hold a zero value in bit 1.

.ColorFormat = 0 '(default) create mask
.ColorFormat |= 1 ' no mask


Just before creating the image list common control GFA-BASIC inverts the first bit and includes it in the flags parameter passed to the ImageList_Create() function.
However, when you explicitly exclude a mask (by setting .ColorFormat |= 1) GFA-BASIC 32 will continue to create masks. However, when picture and the mask are added they are simply ignored by the image list common control.
Note Excluding a mask will result in loss of data when you add an icon to the image list. Therefore GFA-BASIC 32 uses a mask at all times.

Purpose of UseMaskColor and MaskColor Properties
When you add an icon into the list, GFA-BASIC uses its internal mask. If a bitmap or a metafile is added, a mask is created in memory. When .UseMaskColor == 0 the mask is simply a black monochrome bitmap of the same size, all pixel bits are set to zero. However, when you set .UseMaskColor to a nonzero value, the pixels in the image that have the same color value as the .MaskColor setting have a corresponding white color in the monochrome mask bitmap. This opens up the possibility to draw the image transparently. How the image is drawn later depends on more factors than the availability of a mask bitmap only, however.
You can change both UseMaskColor and the MaskColor properties before adding the next image to the list. That way you can have GFA-BASIC generate the correct mask for the image to add. (Each image might have a different transparency color.)
UseMaskColor and MaskColor are a per image setting when adding a Picture to the list.

The BackColor Property
The only property we didn't discuss so far. The .BackColor property isn't used in the creation of the image list common control and it isn't used in the addition of images to the list. The .BackColor property setting is used when we want to get something out of the image list control. It is used in drawing operations and when a Picture object is retrieved.
The .BackColor is used in the ListImage.Draw method. The .Draw method is simply a wrapper around the ImageList_Draw(himl, i, hdc, x, y, fStyle) API, which draws an imagelist item in the specified device context. The fStyle parameter is used to set the drawing style and only when fStyle = ILD_NORMAL (0) the image is drawn using the background color. You can experiment with this, it speaks for itself.
Note The .BackColor value is passed to the underlying common control using ImageList_SetBkColor() API.

The .BackColor setting is also used when the ImageList control is used with other controls (that can't be bound to the ImageList control). You can assign the ListImage.Picture object of the ListImage item to the .Picture property of another control. For example, the following code assigns the Picture object of the first ListImage object in a ListImages collection to the Picture property of a newly created StatusBar panel:

Dim pn As Panel
Set pn = sb.Panels.Add() ' Add a new Panel object.
Set pn.Picture = im1.ListImages(1).Picture 

However, how is this Picture created? That is , how are single Pictures extracted from the image list control? Can this be done by invoking some ImageList_Xxx() API function? The answer is no, the Picture property is a GFA-BASIC 32 specific implementation which uses the .BackColor setting.

The Picture property With .Picture (Get-property) GFA-BASIC allows to create a new bitmap from the image list (and its mask). It creates always a 24 bits DIB memory bitmap to draw the image using ImageList_Draw(). The size of the DIB is ImageWidth x ImageHeight and the background is cleared using the color from .BackColor setting. Then, GFA-BASIC uses the ImageList_Draw() API to draw the specified image using the API background color for the image list (which is also set with .BackColor property!). The net result is a bitmap with a background color .BackColor.
Since the .Picture property returns a 24 bit DIB only, possibly transparent, the new obtained image form the ImageList Ocx might be quite different from what first was added. The .Picture property only returns device independent bitmaps or icons. Any jpg and metafile images are converted to a DIB.

ExtractIcon
When you require a transparent image for some other control that accepts an icon use the .ExtractIcon property instead. This will return a real Windows icon resource which is transparent by default.
Subscribe to: Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /