Paths and File Names
Observed online
Resources for the beginner
Graphics Drawing Rules
beginner's guide to API and DLL
Drawing IN MEMORY
Radiobuttons via API
NumbWord
Working with Comboboxes
Introduction -
Although the title seems to indicate that this is a beginners article, it is really far from it. The beginning programmer is not our target audience, since this person should be primarily concerned with learning the basics of Liberty Basic. Leveraging the internal working of an operating system is for the more seasoned programmer. So this article is intended for the intrepid Liberty Basic programmer who, having grasped a handle of the language, is ready to tread into the murky backwaters of the windows operating system.
Well, it really isn't that bad. Nothing so scary as murky backwaters here, just the fear of the unknown. So, lets get on with the journey...
Before we can begin we need to get a couple terms out of the way. When one
speaks of a DLL they are talking about a Dynamic Linked Library. If one were
to discuss an API they would be addressing the subject of the Application
Programmer's Interface (into Windows specifically). The terms API and DLL
are often used interchangeably, and an API is nothing more than a very complex
DLL. I will refer to both as a DLL henceforth.
Getting Down to Business -
Ok, a DLL is a Dynamic Linked Library, but what does that mean? In the simplest
of terms - a DLL is no more than a
set of EXTERNAL functions that can be called by a program
If you have been using Liberty Basic for any length of time, then you are
no doubt familiar with functions and sub
programs. You can write your own functions using LB - as example of what one
may look like followings:
Function Avg3(a, b, c)
'return average of three values
Avg3 = (a + b + c) / 3
end function
That is a simple function. It requires three arguments (those are the variables
in between parentheses). LB has
few variable types (actually only two: numeric and string) so we simply understand
that these are not strings (since
they don't have a $ suffix) so they are numeric.
Had I been writing this in a more strictly 'typed' language (a language that
requires I strictly follow the rules
regarding variable types and uses) I would have probably had to declare the
types of variables I was going to expect to
have passed into the function. In such a case the function might look like
this (this will not work in LB):
Function Avg3(a as integer, b as integer, c as integer)
'return average of three values
Avg3 = (a + b + c) / 3
end function
Although my program is not written in C code, a function in a strictly typed
language like C might look something like
the one above. This is important, because much of Windows is written in C
or C++. When you interface with DLLs that make
up the windows API, by calling its functions, then you must speak its language.
It expects you to declare the variables
types you are planning to pass.
There are a quite few possible variable types used passing values to external functions that Liberty Basic supports:
Numeric Data (assign any Liberty Basic numeric variable to any of these types)
----------------------------
double (a double float)
ulong (4 bytes) -
long (4 bytes)
short (2 bytes)
ushort, word (2 bytes)
String Data (assign any Liberty Basic string variable to this type)
----------------------------
ptr (4 bytes, long pointer, for passing strings)
Special Purpose
----------------------------
struct (4 bytes, long pointer, for passing structs)
boolean (true/false expression)
As you can see, there are many ways to pass a numeric values to the called
routine in a DLL. The called routine will
expect numeric data to be passed to it as a specific data type. You will have
to investigate your DLL documentation
to know exactly how to pass the numeric value. Passing it wrong usually causes
the program to fail.
Passing Lane Ahead -
Passing variables can get kind of messy when you want to pass strings. This
is the fault of C and C++ (as well as other
languages that are similar). They do not use strings the way BASIC does, but
rather refer to a string as an array of single characters. Also, due to this
limitation, they do not permit a string to be passed as a string as a string,
but rather as a pointer to the memory address containing the first character
of the string. Usually these strings are
terminated (in memory) by a null character. This can get a little confusing
to the Liberty Basic programmer venturing
into the world of DLLs for the first time.
What we need to do to tell a windows function about the string is pass a POINTER
to the string. This is the value of
the memory location where the string beings in memory. Don't forget, this
string must be null terminated. Let's
just peak into memory and see what a string looks like. This is our null terminated
string:
A$ = "Hello" + chr$(0)
Here it is represented in memory (at a fictitious memory address):
H e l l o null
1001 1002 1003 1004 1005 1006
Our String is represented to a windows DLL function by its starting address,
in this case the address 1001. If we want
to have the function work with our string we would pass a POINTER to the starting
address of our string. This pointer
is simply a numeric value that contains the starting address of our string
(that is 1001 in our case). We don't have to
know where our string begins in memory though, because Liberty Basic handles
that behind the scenes. We simply
pass A$ string as a type PTR (which means pointer).
We will hit pointers again shortly, but first, I want to make certain that
the point that I have alluded to it, is
made clear. A DLL (and the API by definition) is just a set of predefined
routines that are made available to
programmers for adding functionality to their applications. The routines are
external to the programming language from
which they are called. They are simply functions. They take arguments and
generally return a value. There are
many, many types of DLL and API calls. Some create windows, some destroy windows,
some handle fonts, some handle the hardware (Com ports, Printer ports) some
handle printing, some manage the Windows central message queue, some manipulate
graphics and others allow playing of music and video. The list goes on and
on.
Open and Closed Case -
There are two requirements in Liberty Basic that are involved in using a
DLL's function. You must first open the
DLL for use and then you must call the function by name, passing the correct
arguments. We have been discussing the
arguments - what they are and why they are the way the are. We will look even
further at some more advanced forms of arguments in a few moments. Now let
us look at what we must do to open the DLL for use. The OPEN statement in
Liberty Basic is used to open many objects, a DLL is just one of them. Every
DLL must be opened before a function can be called with in. Liberty Basic
version 3.x takes care of opening (and closing) several common DLL's that
are part of the Windows API so that you do not need to do this - the help
file calls this "Advanced DLL resolution". These are
DLL's and their associated handles are:
User32.dll - #user32 (the user DLL)
Kernel32.dll - #kernel32 (the OS kernel DLL)
GDI.dll - #gdi32 (Graphics DLL)
Winmm.dll - #winmm (Multimedia DLL)
Shell32.dll - #shell32 (the OS Shell DLL)
Comdlg32.dll - #comdlg32 (Common Dialog DLL)
Comctl32.dll - #comctl32 (Common Controls DLL)
IF you must open a DLL file that is not part of this list then you would use the following command syntax:
OPEN "filename.dll" for dll as #handle
Once it is open you are permitted to call the individual functions that are
in the DLL. Call these using the CALLDLL
command. There is an example of this in the section that follows. If you opened
a DLL for use in your code you must
also close it before your application terminates. To close a DLL file simply
use the CLOSE command and pass it the
handle of the DLL to close. Here is the official syntax of the command:
CLOSE #handle
Return to me -
As you have no doubt learned in your programming experience, Functions (in Liberty Basic) return values. In our example above (the function Avg3) it is understood that the return value will be of a numeric type. Because Liberty Basic does not require us to be specific about the number we can return almost anything. A floating point value (I would expect an average to be a floating point value), an integer, and double precision float, etc.
Going back to our strongly typed languages (such as `C') we are required
to specify the type of value that a function
will return. In my pseudo basic language I might write my function something
like the one below (note: this will not
run in LB):
Function Avg3(a as integer, b as integer, c as integer)as float
'return average of three values
Avg3 = (a + b + c) / 3
end function
The AS FLOAT at the end of the function specifies that the function will
return a floating point value. Liberty Basic
requires that when a DLL is called (using the command CALLDLL) that a value
be returned. The return value is
always passed as the last argument in the call.
If the function above were part of a DLL it would be called using the code
below (presuming the DLL had already been
opened - we will be examining this later):
Calldll #myLib, "Avg3", _
a as short, _
b as short, _
c as short, _
r as double
So Liberty Basic expects the calling DLL to have a return value. There are
no exceptions, unfortunately many DLL's do
not return a value of any kind. In these case we say that the return value
is of a type VOID - meaning it is nothing.
In such a case the last parameter passed would be something like `Result as
void'. Using this special variable type
(you can only use it for a return value) tells Liberty Basic that the DLL
does not pass a return value back. It is
important to know the DLL call and the arguments and return values. You can
not specify a return type of VOID simply
because you do not know what it returns. You must know and you must declare
it correctly.
A Pointer or Two -
Two different variable types pass pointers. They are PTR (used to pass a
string) and STRUCT (used to pass a
structure). It is highly inefficient for a program to pass whole arrays of
data to external functions. This would take
up large amounts of memory (in addition to the memory they currently occupy)
and would slow down the program. In
response to this problem most languages pass and return a pointer to the array
and not the entire array.
The workings of a pointer are both simple and confusing. To understand them
we have to understand how memory is managed. In our computer we have a vast
bank of small storage compartments. I like to think of these as little mail
boxes, kind of like the ones you would see in older hotels behind the front
desk. Row after row of little boxes, some
with notes in them, some with keys in them and some are empty. If we continue
the analogy we would notice that each
of these little compartments is numbered consecutively and in our computer
they are either empty or contain a single
number. The number represents a variable, either a character or a numeric.
(Technical Note: because LB uses floating point values for all numbers - more
than one of the little boxes is required to store the number. For the purposes
of this discussion we will ignore this fact and treat them as if can neatly
fit into a single box.)
An array or a string (remember that a string is simply an array of characters)
is said to occupy several consecutive
boxes. The first element (or character) is in the first box and the second
in the second and so on.
The example above shows these hotel mailbox (or pidgin hole) style boxes
with the string "Hello world" placed in it. It
was placed there with the following line of code:
MyString$ = "Hello world"
The string starts at box number 5 - we call box 5 the memory address of the
string. This is a simple representation of
memory. In real life we don't get to choose the memory locations of our variables,
and we don't even care what the
memory location they are in. This is all managed for us. What we do care about
is being able to pass the location
where an array begins. This is the job of the pointer. Pointers do not have
a great deal of use in the core language of Liberty Basic. We would not say
that variable `A' is a pointer as we would were we programming in C++. The
only usage of this value is as an argument to a DLL call.
MyString$ as PTR.
The PTR tells Liberty Basic that I do not want the value of the string, just
the address. That is 5 in this case. The
called DLL will be able to receive this value and go look at that location
in memory (the very same location we wrote to
when we placed "Hello world" into memory) and use and even alter
the variable. Well, I am sure that is clear as mud.
It is not necessary to fully understand the dynamics of the pointer to use
the pointer, but it helps to know to a degree
what is going on.
Getting Structured -
Pointers to strings have been discussed, but there is also another pointer
type called a STRUCT. Many languages
support formations such as Structs. They are sometimes call use defined variable
types or in other cases structures.
They are simple records of multiple variable types (elements) that are bound
together. Some languages let you
define the type and then create multiple variables of that type. For instance
the user defined type EMPLOYEE contains
the following elements:
Name
Identification Number
Start Date
End Date
Each of these elements are of a specific data type. The one named Name is
a string. Identification Number could be a
long integer. In Liberty Basic we must declare the data types for each element
as we define the element name. We
also must give the struct a name. The example above might look like the following
if we were to define it in Liberty
Basic code:
Struct Employee,
Name as string, _
Identifacation_Num as integer, _
Start_Date as char[10], _
End_Date as char[10]
The valid data types that can be declared are very similar to those used
when passing variables to DLL's as arguments.
One of the interesting items to note is that a struct can contain another
struct as shown by the list. Also a unique
variable type is available in this list that can not be used in calling DLL's,
and that is the type CHAR. It is used to
define fixed length strings. The example above used CHAR to define the date
elements as a 10 character string.
Additional characters can not be stored in this variable. Obviously the length
of the fixed length string is specified
by the value between the square brackets. Also note in the list below that
you may not use the variable type VOID in a
struct, as it has no meaning in this context. Here is the list of valid types:
Numeric Data (assign any Liberty Basic numeric variable to any of these types)
----------------------------
double (a double float)
ulong (4 bytes) -
long (4 bytes)
short (2 bytes)
ushort, word (2 bytes)
String Data (assign any Liberty Basic string variable to this type)
----------------------------
ptr (4 bytes, long pointer, for passing strings)
char (2 byte charater)
char[n] (4 byte pointer to fixed length string - specify
length as value n)
Special Purpose
----------------------------
struct (4 bytes, long pointer, for passing structs)
boolean (true/false expression)
Liberty Basic's treatment of structs (user defined data types) differs from
many languages. Most languages require
the programmer to first define the data type and then declare variables of
that type. In this way you could have
multiple instances of variables of the type EMPLOYEE Liberty Basic combines
the type creation and variable
creation together as a single step. The date type definition is the variable,
and there can only be ONE
instance of that variable.
User defined variable types are powerful (especially in database programming)
and add significant utility to a
language. Carl has said he will be looking into enhancing Liberty Basic to
allow multiple instances of a struct
(instead of just the one by definition). This will be a welcome addition.
Structs can be used both by the Liberty Basic core language and in calling
DLLs for passing and receiving complex data
structures. The single instance limitation does not give them as much utility
in the core language, but they are
indispensable in use with DLLs. Take for instance the call to get the window
dimensions of a window. It gets the
various dimensions returned in the form of a struct (data record).
CallDLL #user32, "GetWindowRect", hwnW As long, _
winrec As struct, _
result As long
Here is the definition of that struct that is used in the DLL call above:
Struct winrec, x1 As long, y1 As long, x2 As long, y2 As long
It is a record called "winrec" consisting of four values all of
which are long integers. This record is stored in memory
in much the same way an array will be stored - each data element in consecutive
memory locations. Since the language
has no way of knowing what the contents, size or make up of a struct will
be, it is difficult to pass these as whole
entities. Luckily, since they are stored in consecutive memory locations this
is not necessary. All that needs to
be passed is the beginning memory location of where the struct starts in memory.
Again this is a pointer. It
points to a memory location.
As I mentioned, the Struct command is useful in calling DLL's and also for
use with the core language. There are
several cases where one might intentionally use a struct in the course of
programming without ever calling a DLL. The
most obvious one to me currently is to supplement the lack of globals by creating
a global struct to hold all of your
global values. It might look like this:
Struct global, _
Name as string, _
Date as string, _
Counter as integer
These could be accessed, changed and utilized through out the program since
(as I recently learned) structs are global
in scope.
To set a value, or access a value in the struct we use the following command
syntax: struct_name.element.STRUCT. The word "STRUCT" in the syntax
is the keyword that defines the variable as an element of a struct. Below
is a couple
example of code that assigns a value to a element in the global struct mentioned
above - also shown is the code to
print an element from the same struct.
global.Name.struct = "Brad"
Print global.Counter.struct
There are a couple special functions that are used in conjunction with structs.
The first is used to retrieve the
contents of a memory location pointed to by an element of the struct. In the
struct above if, after assigning "Brad"
to the Name element, we simple printed the Name element we would get a number.
It is the memory location where the
string Name is stored. A pointer again. To print the value of the string (or
otherwise retrieve it) we need to use the
function Winstring. Winstring requires a pointer as its argument. According
to the help file it:
".returns a string when a function returns a pointer to a string. This
function is especially useful when retrieving
the text string from a STRUCT that has been altered by a function." Using
that function we could print the value
pointed to by the element Name in the global struct. Here is that code:
Print winstring(global.Name.struct)
Another useful technique is determining the length (size) of a struct using
the LEN() function. This is most often used
in conjunction with calling a DLL where the DLL requires the length (size)
of the struct that is being passed. The
following assigns the size of the Employee struct (mentioned earlier) to the
variable sizeofstruct:
sizeofsrtuct = len(Employee.struct)
Beyond the Basics -
Structs offer a great deal of power to the programmer and have opened up
many possibilities in the realm of calling
DLL's. Even more recent and powerful feature is the callback. I will touch
on this feature just briefly, as a
whole tutorial can be written about callbacks alone. They are by far the most
complex area of DLL interface in Liberty
Basic, but with the complexity comes immense power.
DLL's sometimes require a callback function to be passed to them in the call.
This is an address in memory of a
specific function that can be triggered by the DLL when an event is fired.
The function is a regular Liberty Basic
function. To setup a callback you must specify it (using the command Callback),
before you invoke the DLL call that
will require the callback. According to the help file the syntax is as follows:
callback addressPTR, functionName(type1, type2...), returnValue
Usage:
addressPTR - assigns a name to the memory address of the function.
functionName - is the name of the function in the Liberty BASIC program that
will be called by the API function.
(type1, type2...) - a comma-separated list of parameters, which will be specific
to the function used. The parameters must be valid data TYPES such as
"ulong" and "long".
returnValue - the type of return value is listed after the closing parenthesis.
The Liberty BASIC function may return a value to the calling function.
The way this works is that the DLL is passed the addressPTR which points to
a function that appears elsewhere in your
code. The helpfile has an example I recommend you look at if this is an area
that interests you. It demonstrates the
DLL call enumwindows which returns a callback to the calling program. Also
of interest and great utility is a DLL
written by Brent Thorn called WMLiberty.dll (get it at http://groups.yahoo.com/group/lbexp/files/).
It utilizes
the callback capability of Liberty Basic to trap messages in the windows message
queue and to trigger events in Liberty
Basic based on those messages. The function is more complex than I intend
to go into, but for the advanced Liberty Basic programmer looking to integrate
advanced functions (such as MDI, Key Capture, Control Subclassing) into your
applications this is the tool you are looking for.
Dennis Mckinney" (dlm81854@accs.net)
recently supplied an example of a callback being used to detect
the movement or resizing of a window. This example uses WMLiberty.dll to setup
the callback that triggers the event
handler.
'-- code -- nomainwin Open "WMLiberty.dll" For DLL As #wmlib open "Test Size / Move Message" for window as #1 print#1, "trapclose [quit]" hWin = hwnd(#1) hWin(0) = hWin Callback lpfnOnMove,OnMove(long,long,long,long),long CallDLL #wmlib, "SetWMHandler", _ hWin As long, _ _WM_EXITSIZEMOVE As _ long, _ lpfnOnMove As long, _ 0 As long, _ ret As long [loop] Scan GoTo [loop] [quit] close #1 close #wmlib end Function OnMove( hWnd, uMsg, wParam, lParam ) If uMsg = _WM_EXITSIZEMOVE AND hWnd = hWin(0) then Notice "Window Moved or Resized" End If End Function
Final Words -
So what I have covered is the dynamics of calling DLL functions from Liberty Basic. I have merely scratched the surface of the material. There is much more to learn. There is one aspect of call the DLL that I have not covered, and it is perhaps one of the more difficult areas of understanding, and that is converting the call (which is often documented in C or C++) to Liberty Basic format. This is another subject that could easily consume another article. The foundation has been laid here. I would welcome someone of greater experience who would write about this. For now let me point you to a few resources that you can use in at least understanding the Windows API calls.
Alyce Watson has written a marvelous ebook called Mastering Liberty Basic.
The new update for Liberty Basic 3.02 has just been released. It can be downloaded
from the website "Alyce's Restaurant" at http://iquizme.0catch.com/lb/
- it
is shareware, but is hands down the best, most authoritative resource for
Liberty Basic around.
There are several free sources of help files that cover the Windows API for
the programmer. Alyce has links to many of these on her website at http://iquizme.0catch.com/lb/api/index.html
. There you will find the file Win32api.zip - a downloadable reference. Another
excellent source of information is the Windows 32 SDK by Borland - it can
be downloaded from ftp://ftp.inprise.com/pub/bcppbuilder/techpubs/bcbhlp01.zip
. A great website called All API Net
(http://www.allapi.net/ ) contains references to hundreds of API calls all
formatted for Visual Basic. It also contains
an API viewer you can download. Don't forget that Microsoft has references
to the API calls also at its developers
support site called the MDDN - you will have to dig a bit, but this link will
get you started: http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
I personally use a reference book which explains the Windows 32bit API and
offers examples in Visual Basic 5. It is called Visual Basic 5.0 programmers guide to the WIN32 API by Danny Appleman.
I recently checked http://www.Amazon.com
and found that there is a new release of this book called Visual Basic Programmer's
Guide to the Win32 API, Published by Sams - ISBN: 0672315904