When writing programs, there are user-defined commands and functions which may be used again and again in different projects and, rather than having to copy and paste the code into each new project you create that will use it (or having to update all projects using that code when revisions/bug fixes are made), it may be possible to put the commands and functions in a Library which can be called from each program that requires them.
A library is simply a list of variables, functions, types, and declarations, some or all of which you wish to allow access to from the program into which the library is loaded. It should be noted that not all functions have to be visible; there may be some which are library specific and only work when called by other library functions, or others to which you do not wish to allow public access; the same applies to internal variables.
For a library to be created, it must be compiled into a LG32-format library, which can be done through usual compiler form opened by the standard Ctrl-E key shortcut; however, before it is compiled, those variables, functions, types, and declarations you wish to make accessible must first be specified and the command used to do that is $Export. The $Export command takes the following form:
$Export type name ["description"]
The type parameter defines the type of the element to export. The type is one of the predefined export names (of which only the first three letters need be entered) in the list below:
Var | specifies a global variable |
Con[st] | specifies a constant |
Enu[m] | specifies a constant (same as Con) |
Sub | specifies a Sub routine |
Pro[cedure] | a Procedure |
Fun[ction] | a Function to export. |
Dec[lare] | specifies a Declare to export |
Typ[e] | specifies a user defined type. |
name is the identifying label of the element to export, and must be the same as it is declared in the library. To prevent the programmer from exporting each element by name, wildcards (* and ?) can be used. This is very useful when many constants starting with same sequence of characters are to be exported - for example, all constants starting with the tag TBOX_ can be exported by entering $Export Con TBOX_*, and all API Declarations can be similarly exported by typing in $Export Dec *.
Finally, description is an optional field for entering a description of the element which is shown under the Imports tab and, when the exported feature is selected in the Import tab, will be displayed in the IDE status bar. The description can also contain a link to an HTML-formatted help document by incorporating a link to that document in the format #10">htmlfile#marker"#10 where htmlfile is the document name and marker the section inside the document which relates to the feature, and this link is activated by pressing F1 when the feature is selected. The HTML document will usually be shown in your default browser, but a specific application can be used instead by adding its file path to the registry key "\\HKLM \Software\GFA\Basic\HtmlView".
As of version 2.5, the new PeekView for imports reduces the need for providing help on each exported item. For exported constants PeekView displays their value, so there is no need to explicitly provide a description for each exported constant. The same is true for exported Declares and Types, they don't need a description since PeekView collects all information available for these items. For Declares PeekView presents a prototype (syntax) and for Types PeekView displays the entire user-defined type. There is nothing that prevents you from providing additional information in the description part of the $Export clause, it will be displayed as well.
Also added in version 2.5, the description part of the $Export clause can now contain multiple lines separated by a LF (#10) or CR (#13). All these lines are displayed by PeekView. For instance, the following $Export statement comes from gfawinx.lib.g32.
$Export Function Wide "(vnt As Variant, Optional fNullTerm As Bool = True) As String" #10 _
"Returns UNICODE16 in a String, by default terminated with 2 null-bytes." #10 _
"Converts any data type that can be coerced to a Variant." #13 _
"Usage: uni$ = Wide(value)"
This command exports a function named Wide and adds a description for the exported item. The description starts with a replication of the parameters and return type. Note that PeekView creates its own prototype for the function and that this will lead to two almost identical lines in PeekView's popup window. The difference between these lines is the lack of variable names in PeekView's prototype. The parameter names are discarded when compiled and aren't visible in a program that uses the library.
To save space, more than one element of the same type can be exported in a single line, by separating the different elements with commas: for example, $Export Fun Function1 "This is function 1", Function2 "This is function 2".
The $Export command has one other function as well: it can be used to give the Library a name and, to do this, it is entered in the following format, where 'libname' is the desired name for the library:
$Export "libname"
To load a library, the $Library command is used within the main program, in the following format:
$Library "name"
"name" can be a complete filepath, a path originating in the application's current directory or the library name with or without the .lg32 extension; all include files can be inserted without a path: for instance $Library "gdip.inc" // definitions for gdiplus.
Libraries are searched for (1) in the current program's directory, (2) in My Documents\lg32, and (3)in the paths stored in the registerkey "lg32paths". The Extra tab in the properties dialog box allows to specify multiple paths each separated by a semicolon for this registerkey.
Note that you may omit the ending extension '.lg32', also multiple dots in the filename are allowed. For include files make sure the $Library paths are set using the Properties dialog box.
By convention, a $Library statement should be placed at the beginning of the program listing, but in reality, the statement can be placed anywhere.
When a program line containing a $Library statement is completed (the cursor is moved to another line and the line processed), the library is loaded into memory (see the report on the Debug screen) and remains there whilst the IDE is open; similarly, when a file containing such a statement is loaded or merged into an existing file, then the library is immediately loaded into memory. If loaded successfully, all accessible elements are then listed under the 'Imports' tab of the sidebar of the IDE, along with their 'description' tags (if any).
Finally, when the main program is compiled, all elements contained in the library file are included within the resulting executable.
Note: Removing the $Library statement from a program listing will not remove the library from memory; in addition, updating and recompiling the actual library file itself will not change the version loaded whilst the IDE stays open. The only known way of removing or refreshing the library data is to save your work, close and then re-open the IDE.
The GFABASIC installation comes with pre-defined libraries covering everything from Windows constants and APIs to specialised GFA-specific libraries such as gfawinx.lg32 and Direct2D.lg32.
The pre-defined library - or include - files, named *.inc.g32, can be found in the Include Directory within the GFABASIC installation and, after version 2.5, have been updated to the Win SDK version 7; along with the include files, you will find their respective .g32 files.
Include files can be edited but, due to limited access rights of the Program Files(x86) directory, they can only be editted with Administrator rights. If there are any errors or omissions in the include files, please send details here.
For a more detailed description of this type of file, see here.
Note Avoid using Win32API.inc, it is incomplete, old, and contains many errors.
Following the v2.5 update, the Win32 API declarations and definitions are now spread over separate include files that are named according SDK documentation. When the SDK documents a certain API to reside in Winuser.h then the GB equivalent is declared in winuser.inc.lg32, etc.
In addition, as of version 2.5, the *.inc.g32 files in the Include directory no longer contain exported functions or procedures. Functions and procedures are executable code and will add to your resulting executable, as will declarations (created using Declare); Constant and Type definitions won't. The Windows macros that are normally spread throughout different include files are now collected into one library file winmacros.lib.lg32; the Windows macros are implemented using Naked functions and are highly optimized for code size and performance.
Inside the Include directory you'll find an additional library file gfawinx.g32/gfawinx.lg32 which extends the GFA-BASIC 32 functionality with new functions and 'statements'. This file will get a status aparte in new updates. Take a look inside gfawinx.g32 for the currently supported functions like Wide(), Ansi(), StrToArr(), Replace(), ErrStr(), etc.
This library file includes enhanced drawing and text rendering command which use Microsoft's proprietory Direct2D functions as an alternative to GFABASIC's older and blockier GDI based graphic commands. For a full list of available commands and functions, see here.
As explained at the beginning of this page, OCX objects are only partially supported by LG32 libraries: Properties can be set, Methods can be employed BUT Event handling must always take place in the main program. A workaround to this can be to employ dummy Event handling subs in the main program which call functions within the library as shown in the following simple example:
$Library "OCXLib.lg32"
Trial
Sub frm1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)
MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)
EndSub
Sub Screen_KeyPreview(hWnd%, uMsg%, wParam%, lParam%, Cancel?)
KeyPreview(hWnd%, uMsg%, wParam%, lParam%, Cancel?)
EndSub
$Export Proc Trial
$Export Func MessageProc
$Export Func KeyPreview
Procedure Trial
Form frm1 = "", 10, 10, 400, 400
Ocx ComboBox cmb = "", 10, 10, 100, 15
cmb.AddItem "Left", 0 : cmb.AddItem "Centred", 1
Do : Sleep : Until frm1 Is Nothing
EndProcedure
Function MessageProc(hWnd%, Mess%, wParam%, lParam%, ByRef retval%, ByRef ValidRet?) As Long
If Mess% = WM_COMMAND
If HiWord(wParam%) = CBN_SELCHANGE And lParam% = cmb.hWnd Then Message "Selection Changed"
EndIf
If Mess% = WM_DESTROY Then Message "Form Destroyed"
EndFunction
Procedure KeyPreview(hWnd%, uMsg%, wParam%, lParam%, ByRef Cancel?)
// Prevents a capital A being entered into the ComboBox
If GetParent(hWnd%) = cmb.hWnd And uMsg% = WM_CHAR And wParam% = 65 Then Debug "Hi" : Cancel? = True
EndFunction
It is possible to sub-class objects to handle form events internally, but this does not always work well for OCX objects, especially complex ones like ComboBoxes which contain an Edit, a Button and a ListBox control, ListViews which have a separate ColumnHeaders collection, TreeViews, and so on - this is probably because GFABASIC has already sub-classed the controls internally and additional sub-classing causes conflicts (GFABASIC uses the old style of sub-classing using SetWindowsLong, not the more modern APIs which are more flexible). Just for interest sake, the following is an example of how to combine sub-classing the form with using Screen_KeyPreview in the main program:
$Library "OCXLib.lg32"
Trial
Sub frm1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)
Trace Mess%
EndSub
Sub Screen_KeyPreview(hWnd%, uMsg%, wParam%, lParam%, Cancel?)
KeyPreview(hWnd%, uMsg%, wParam%, lParam%, Cancel?)
EndSub
$Export Proc Trial
$Export Func KeyPreview
Global cmbhwnd%, rethwnd%
Procedure Trial
Form frm1 = "", 10, 10, 400, 400
rethwnd% = SetWindowLong(frm1.hWnd, GWL_WNDPROC, ProcAddr(MyWndProc))
Ocx ComboBox cmb = "", 10, 10, 100, 15
cmb.AddItem "Left", 0 : cmb.AddItem "Centred", 1
// Sub-classing the ComboBox results in a fatal error
// cmbhwnd% = SetWindowLong(cmb.hWnd, GWL_WNDPROC, ProcAddr(MyWndProc))
Do : Sleep : Until frm1 Is Nothing
EndProcedure
Function MyWndProc(hWnd%, Mess%, wParam%, lParam%) As Long
If Mess% = WM_COMMAND
If HiWord(wParam%) = CBN_SELCHANGE And lParam% = cmb.hWnd Then Message "Selection Changed"
EndIf
If Mess% = WM_DESTROY Then Message "Form Destroyed"
Return CallWindowProc(rethwnd%, hWnd, Mess%, wParam, lParam)
EndFunction
Function CmbWndProc(hWnd%, Mess%, wParam%, lParam%) As Long
If Mess% = WM_CHAR Then Message("Key pressed")
Return CallWindowProc(cmbhwnd%, hWnd, Mess%, wParam, lParam)
EndFunction
Procedure KeyPreview(hWnd%, uMsg%, wParam%, lParam%, ByRef Cancel?)
// Prevents a capital A being entered into the ComboBox
If GetParent(hWnd%) = cmb.hWnd And uMsg% = WM_CHAR And wParam% = 65 Then Debug "Hi" : Cancel? = True
EndFunction
It appears that if you wish to use a totally library dependent event procedure, you would probably need to create the forms and controls from scratch using APIs. However, as GFABASIC handles all events regarding controls (including those created using APIs), this is a minefield and no successful comprehensive example has yet been seen. However, for those adventurous enough to try below is an annotated example of how to go about creating a form using APIs (using Unicode versions of the API), followed by some notes on the problems inherent is going down this path.
Declare Function CreateWindowExW Lib "user32" Alias "CreateWindowExW" _
(ByVal dwExStyle As Long, ByVal lpClassName As String, ByVal lpWindowName As String, _
ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, _
ByVal nHeight As Long, ByVal hWndParent As Long, ByVal hMenu As Long, _
ByVal hInstance As Long, lpParam As Long) As Long
Declare Function DefWindowProcW Lib "User32" Alias "DefWindowProcW" (ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function RegisterClassW Lib "user32" Alias "RegisterClassW" (Class As WNDCLASS) As Long
Declare Function SendMessageW Lib "User32" Alias "SendMessageW" (ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function UnregisterClassW Lib "user32" Alias "UnregisterClassW" (ByVal lpClassName As String, ByVal hInstance As Long) As Long
Type WNDCLASS
style As Long
lpfnwndproc As Long
cbClsextra As Long
cbWndExtra As Long
hInstance As Long
hIcon As Long
hCursor As Long
hbrBackground As Long
lpszMenuName As Long
lpszClassName As Long
End Type
FormRegister // Register the custom form
Global fhdl As Long = FormDraw // Create and Populate the new form
Global formclose? // Create flag to signal form closed
Do : Sleep : Until formclose?
FormUnRegister // UnRegister the custom form
Function FormDraw() As Int32
// Create the standard font
Local Long basefont = CreateFont(-11, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, "MS Shell Dlg")
// Create the form
Local Long wstyle = WS_POPUP Or WS_VISIBLE Or WS_BORDER Or WS_CAPTION Or WS_SYSMENU Or WS_MAXIMIZEBOX Or WS_MINIMIZEBOX
Local Long hdl = CreateWindowExW(WS_EX_APPWINDOW, "NewClass", StrW("Window"), wstyle, 100, 100, 250, 130, Screen.hWnd, 0, App.hInstance, 0)
// Create first label (ID 101 - see third parameter from the end)
Global Long lbl1 = CreateWindowExW(0, StrW("Static"), StrW(" Contents of 2nd Label:"), WS_CHILD | WS_VISIBLE | SS_LEFT, 10, 10, 120, 15, hdl, 101, 0, 0)
~SendMessage(lbl1, WM_SETFONT, basefont, True) /* Set font to MS Shell Dlg, Size 8 (GFA standard) - Forecolor and Backcolor determined in FormWndProc
// Create ComboBox with list of possiblities for the second label (ID 102)
Global Long cmb = CreateWindowExW(0, StrW("ComboBox"), "", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_SORT, 130, 4, 100, 15, hdl, 102, 0, 0)
~SendMessage(cmb, WM_SETFONT, basefont, True)
Local Long ci : Local a$
a$ = StrW("First Name") : ci = SendMessageW(cmb, CB_ADDSTRING, 0, V:a$) // Note the use of the Unicode API to send the Unicode value
~SendMessage(cmb, CB_SETITEMDATA, ci, 0) // Adds the Item Data for this entry
a$ = StrW("Middle Name") : ci = SendMessageW(cmb, CB_ADDSTRING, 0, V:a$) : ~SendMessage(cmb, CB_SETITEMDATA, ci, 1)
a$ = StrW("Surname") : ci = SendMessageW(cmb, CB_ADDSTRING, 0, V:a$) : ~SendMessage(cmb, CB_SETITEMDATA, ci, 2)
// Create second label (ID 103)
Global Long lbl2 = CreateWindowExW(0, StrW("Static"), StrW(" [2nd Label]"), WS_CHILD | WS_VISIBLE | SS_LEFT, 10, 35, 120, 15, hdl, 103, 0, 0)
~SendMessage(lbl2, WM_SETFONT, basefont, True) : Global LabelFilled? /* Determines whether the label has been populated
// Create TextBox to enter relevant value (ID 104) - A border is added in FormWndProc
Global Long txt = CreateWindowExW(0, StrW("Edit"), "", WS_CHILD | WS_VISIBLE , 133, 36, 94, 14, hdl, 104, 0, 0)
~SendMessage(txt, WM_SETFONT, basefont, True) : ~EnableWindow(txt, False) // Disable edit window until Label 2 has a title
// Create Command Button to close the form (ID 105)
Global Long cmd = CreateWindowExW(0, StrW("Button"), StrW("Close"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 75, 60, 100, 25, hdl, 105, 0, 0)
~SendMessage(cmd, WM_SETFONT, basefont, True)
Return hdl
EndFunction
Function FormRegister() As Int32
Dim a$ = "NewClass", hAtom As Long, wc As WNDCLASS
// Populate the WNDCLASS object to define the new Form Class
wc.style = 0
wc.lpfnwndproc = ProcAddr(FormWndProc) /* The Address of the procedure to recieve events
wc.cbClsextra = 0
wc.cbWndExtra = 0
wc.hInstance = App.hInstance
wc.hIcon = 0
wc.hCursor = 0
wc.hbrBackground = COLOR_WINDOW + 1
wc.lpszMenuName = 0
wc.lpszClassName = V:a$
// Register Class (This is the Unicode API)
hAtom = RegisterClassW(wc)
// If Registration fails, raise a message and end...
If hAtom = 0 Then Message("Form Registration Failed.") : End
// ...otherwise, return the Atom of the newly registered Class
Return hAtom
EndFunction
Procedure FormUnRegister
UnregisterClassW("NewClass", App.hInstance)
EndProcedure
Function FormWndProc(hWnd As Long, uMsg As Long, wParam As Long, lParam As Long) As Long
Local Long cs, hdc, id, mess, pen, tlen : Local a$
Select uMsg
Case WM_COMMAND // Many messages from Child Controls are sent by WM_COMMAND
id = LoWord(wParam), mess = HiWord(wParam) // Determine the object ID and message
Select mess
Case BN_CLICKED
~DestroyWindow(fhdl) // Close Form when Command Button clicked
Case CBN_SELCHANGE // Change in ComboBox selection
If id = 102 // Check which ComboBox has changed (example only as there is only one on this form)
cs = SendMessage(lParam, CB_GETCURSEL, 0, 0) // Get the current selection, ...
tlen = SendMessage(lParam, CB_GETLBTEXTLEN, cs, 0) // ...get the length of the List item, ...
a$ = Space(tlen * 2) // ...create the correct size buffer, ...
~SendMessageW(lParam, CB_GETLBTEXT, cs, V:a$) // ...retrieve the List item (note to Unicode API), ...
~EnableWindow(txt, True) // ...enable the TextBox, ...
LabelFilled? = True // ...mark that text has now been added, ...
~SendMessageW(lbl2, WM_SETTEXT, 0, V:a$) // ...set as text in Label 2 and ...
~SetFocus(txt) // ...navigate to the TextBox.
EndIf
EndSelect
Case WM_CTLCOLOREDIT // Draw the TextBox Border
hdc = GetDC(hWnd) // Get the DC for the main form, ...
pen = CreatePen(PS_SOLID, 1, SysCol(17)) // ... create a custom pen, ...
~SelectObject(hdc, pen) // ... link the pen to the DC, ...
~Rectangle(hdc, 130, 33, 230, 51) // ... draw the rectangle and ...
~DeleteObject(pen) // ... delete the pen
Case WM_CTLCOLORSTATIC // Set Label Fore and Back Colour
If lParam = lbl2 And Not LabelFilled? // If Label 2 and no value yet added
~SetTextColor(wParam, RGB(128, 128, 128)) // ... set Text colour to Grey
Else // Otherwise
~SetTextColor(wParam, 0) // ... set Text colour to Black
EndIf
~SetBkMode(wParam, TRANSPARENT) // Set Draw mode to Transparent
Return CreateSolidBrush(RGB(255, 255, 255)) // Set Backcolor to White
Case WM_DESTROY
formclose? = True
EndSelect
FormWndProc = DefWindowProcW(hWnd, uMsg, wParam, lParam)
EndFunction
Function StrW(vnt As Variant) As String
// Converts ANSI to Unicode - Acknowledgements to Sjouke Hamstra
Dim BSTR As Register Long : BSTR = {V:vnt + 8}
Return StrPeek(BSTR, {BSTR - 4}) & Chr(0)
EndFunction
It is possible to add tab stops and default buttons by adding the following message loop:
Type MSG
- Long hwnd, umsg, wparam, lparam, time
pt As POINT
EndType
Type POINT
- Long x, y
EndType
Global ms As MSG // Create a Message Object
While GetMessage(V:ms, fhdl, 0, 0) > 0
If !IsDialogMessage(fhdl, V:ms)
~TranslateMessage(V:ms)
~DispatchMessage(V:ms)
EndIf
Wend
However, this will cause a crash in any form with a DropDown ComboBox and is not how GFABASIC works anyway (it seems to use a customised function).
One final note: Forms created using APIs are not affected by and can not be assigned to Me or Output as these only accept GFABASIC form types; similarly, all drawing commands will not work as they are directed to the Me object. Instead, it is possible to use a global handle to hold the current window and use that to direct GDI APIs, similar to how the border for the TextBox was drawn in the example above.
LG32 Libraries are based upon C-type Libraries: they are libraries that contain an expanded instruction set of Functions and Commands and should not be confused with VB Modules, which are technically independent programs. Hence, there are restrictions on what can be done and what can be included within a library.
{Created by Sjouke Hamstra; Last updated: 24/02/2021 by James Gaite}