Visual dBASE can do almost whatever you can imagine, thanks to the richness of its functions and language. This richness comes from a substantial heritage brought by the former versions since its origins as the first databases language for PCs. The arrival of the latest Windows versions, the introduction of the OOP as the backbone for the data management and the powerful user interface, have endowed the product with the best capabilities to design excellent quality databases applications.
A Visual dBASE application is above all a Windows application. In other words, it complies to the rules and meet the requirements of this operating system. In order to keep a certain homogeneity and provide a good integration within the system, it is necessary for dBASE to use and run all the resources and libraries that this operating system provides to build an application. These resources are provided through the Windows Application programming interface commonly referred to as the Windows API.
Visual dBASE, through all of its up to date visual tools is able to perform most of the complex operations necessary to build such an application, while offering an excellent compromise between the complexity of API and the simplicity and richness of its capabilities. Being a Windows application, a Visual dBASE application already uses many functions from the Windows 32 API - but the main advantage of such a tool is that it can transform in a more accessible way a domain initially reserved to specialists; and especially, it allows us to concentrate our development efforts on the application characteristics, instead of the system programming.
In order to cope with particular situations, it happens sometimes that some options or functions are not directly available under the Visual dBASE tools or language. In that case, it is necessary to leave the user-friendly environment and dive into the mystery of Windows programming and its API 32 functions.
The contents of this article contain:
The Visual dBASE designers have introduced in the product a way to access the 32 bit functions contained in DLL files. Visual dBASE 7.01 can only access 32 bit libraries, as opposed to its eldest 5.7 that knows only about the 16 bit world, to keep compatibility with Win 3.1.
DLL files are binary files containing code and/or resources (bitmaps, icons) which can be shared by many applications at the same time. Windows itself is built around these DLLs which we can use in our own applications.
These functions are called in Visual dBASE “external functions”. The detailed use of such a function is in the on line help. Look for the word “extern” or key in help extern in the command window to find more information.
We are going to see how to use these functions and point specifically to those offered by the Win 32 API in order to understand their requirements and limitations. The purpose is, after all, to acquire a technique that will enable us to operate these Windows API 32 functions.
This is not the only difficulty when getting into the Win 32 API, because, generally, the key point will consist in finding, among the numerous available API functions, the one that will perform the desired action.
All versions of Visual dBASE 7.01 includes a Windows help file named “Win 32 Programmer's Reference”. I will refer to it as the “API 32 OLH” (on-line help).
We will first consider, as an example for this article, the API 32 function named : ShellExecute. This function can run commands and launch other applications from within Visual dBASE without viewing the DOS window that appears when executing the RUN() function.
Find this function in the API 32 help file. Look for ShellExecute. You will get the following page :
Let's look in detail at the different parts of this function, pointed in red :
The Windows programming interface can appear unfriendly at first glance, especially when compared to a high level programming language like Visual dBASE. Actually, Windows itself has been designed and built in C, initially to be used by C programmers. Thus, the main difficulty for using that API is to adapt to that language's particularities.
The purpose of this chapter is not to learn C, not even C++. The concern here is to get the few elements of that language that will enable us to use the API functions.
One main difference between C and Visual dBASE lies in the data types and structures. Unlike dBASE, C data variables are always declared with an explicit type. C also separates variables from pointers. Because of this it is important to understand the main differences between these 2 languages :
To get back to the data types, the Windows API types are based on the native C data types. These native C data types are lowercase. The derived types are generally uppercase.
A pointer can be explicitly declared in C; in that case, its name starts with an asterisk (*). In derived types, the * is generally contained in the type name, if it is a derived type like Windows ones. The type name often starts by LP, for Long Pointer.
Here are some examples of variables declarations in C :
char c = 'a'
// char variable, on 1 byte, initialized to the letter 'a'.
int *k
// Pointer on an integer
Example of C array :
short tab[2][3]
// Creates a 2 x 3 array, containing "short" elements
// tab is in fact a pointer on the first
// short of the memory buffer reserved for the array
Example of string in C :
char st[] = "Bonjour!"
// Creates a string, whose size is exactly
// its length + 1 (because of the Null at the end),
// and pointed to by the pointer st.
// This string is considered as fixed in memory.
char st_tab[256]
// Here, an array of 256 bytes is reserved to store,
// for instance, characters, maximum 255 + '\0'
The following table lists the basic variables
and pointer types used in Windows, that derive from C types :
Native C Type | Memory size (bytes) | Other equivalent types used in Windows (not exhaustive list) | Visual dBASE type | Variable declaration in function's prototype under Visual dBASE | Pointer declaration in function's prototype under Visual dBASE |
char | 1 | BYTE | Numeric | CCHAR | |
unsigned char | 1 | UCHAR | Numeric | CUCHAR | |
short | 2 | WORD | Numeric | CSHORT | CPTR SHORT |
unsigned short | 2 | USHORT | Numeric | CUSHORT | CPTR CUSHORT |
int | 4 | DWORD, HWND, BOOL, HDC | Numeric | CINT | CPTR CINT |
UINT | 4 | Numeric | CUINT | CPTR CUINT | |
long | 4 | LPVOID | Numeric | CLONG | CPTR CLONG |
unsigned long | 4 | ULONG | Numeric | CULONG | CPTR CULONG |
float | 4 | Numeric | CFLOAT | CPTR CFLOAT | |
double | 8 | Numeric | CDOUBLE | CPTR CDOUBLE | |
LDOUBLE | 10 | Numeric | CLDOUBLE | CPTR CLDOUBLE | |
LOGICAL | 1 | Logical | CLOGICAL | CPTR CLOGICAL | |
void | N/A | N/A | CVOID |
Some explanations about this table :
Windows uses, in the function prototypes declarations, many types derived from the direct C types in the previous table. All of these Windows types can be declared in the EXTERN function statement with Visual dBASE types.
One exception is however not currently supported
by Visual dBASE, it's the CALLBACK
type, that we will not talk about in this article. Roughly, it could be
an equivalent of the Visual dBASE Function Pointer (FP) type.
Strings
and pointers in C
A particular case concerns the strings. This chapter deals with character strings and pointers in C, and their use in Visual dBASE with the API 32 functions prototypes.
A character string is, obviously, a series of characters. These characters can be stored :
Run the following code to see how strings are stored in dBASE. Notice that j start at 0.
s = new String("Bonjour") for j = 0 to (s.Length*2)-1 ?s.getByte(j), chr(s.getByte(j)) next jWhen a character string must be supplied to an API 32 function, you must respect the exact expected type, Unicode or byte.
Note: The last character of a C string must be a chr(0), also called Nullchar, or '\0'. Visual dBASE takes into account this requirement when calling extern functions with a string passed as a parameter, but this rule must be kept in mind, especially when conversions Byte <-> Unicode take place.
A C String always consists of a series of CHAR or WORD (if the string is Unicode), that is “pointed to” by a C char or word pointer. This pointer is itself a variable that contains a memory address to the address of the first character of the pointed string. When you declare a string in C, you actually declare a pointer - a memory address - to that string. The length of this string is defined implicitly by the first Null in the string.
To use strings in external functions, two
keywords exist in Visual dBASE: CSTRING
and CPTR.
Both designate a pointer to a string, but here are the fundamental differences
between them :
CSTRING Declaration | CPTR Declaration |
automatic
implicit conversion from Unicode to Byte when calling the extern function.
The string can contain Null chars according to the OLH, but I experienced problems when Null chars are present. |
No
conversion takes place.
The string can contain Null chars. |
automatic
implicit conversion from Byte to Unicode when the extern function returns
a string.
The Null char rule applies also here, with the same problem experienced. |
The function return type cannot be CPTR. |
Let's go back to the ShellExecute function, seen in the API 32 functions presentation chapter. We are now going to prototype - that is to say declare this function before using it, in order to explicitly tell to Visual dBASE how it must call this function in a program.
Let's see first the general syntax of an extern function. The OLH, “extern” topic, proposes two possible wordings, but they can generalize to the second one :
In order, we find :
Fortunately, the job is almost already done. The _DBWINHOME\Include directory contains some prototype files, (files with extension .h), containing the declarations used by the API 32 functions.
Compile the program with those two lines of code and an error appears. This is due to a declaration that is incompatible, HWND , which causes the error. In fact, HWND is a Visual dBASE keyword, so there's a conflict.
The best solution is to replace the declaration HWND by HANDLE, that will give the correct result. So the corrected declaration becomes :
// Prototype
declaration for ShellExecute function
extern HINSTANCE ShellExecute(HANDLE, LPCSTR,
LPCSTR, LPCSTR, ;
LPCSTR, CINT) shell32 ;
from "ShellExecuteA"
Recompile. This time , it's correct. The function is now declared and ready to be used.
This problem due to HWND is the only one I have encountered up to now, all the other types of functions in the win32API.prg file work properly.
To illustrate what has been said earlier concerning the from keyword in the command, replace the function declaration by the following, that eliminates the from keyword :
// Prototype
declaration for function ShellExecuteA, without the keyword from
extern HINSTANCE ShellExecuteA(HANDLE, LPCSTR,
LPCSTR, LPCSTR, ;
LPCSTR, CINT) shell32
Compile : it's OK, the function must then be called by issuing a ShellExecuteA(<Parameters>) instead of a ShellExecute(<Parameters>).
In summary, the delicate step of prototyping the API function has been limited to :
Let's consider again the same function ShellExecute and the corresponding API OLH page. Now, we are going to examine the nShowCmd parameter. This parameter represents the way the new application's window will be opened. It can take the following values :
The file contains the following section
of declarations :
. . .
//
// ShowWindow()
Commands
//
#define SW_HIDE
0
#define SW_SHOWNORMAL
1
#define SW_NORMAL
1
#define SW_SHOWMINIMIZED
2
#define SW_SHOWMAXIMIZED
3
#define SW_MAXIMIZE
3
#define SW_SHOWNOACTIVATE
4
#define SW_SHOW
5
#define SW_MINIMIZE
6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA
8
#define SW_RESTORE
9
#define SW_SHOWDEFAULT
10
#define SW_MAX
10
. . .
For this example, a single line of code
in the header part of the source code is necessary to declare all the necessary
constants :
// Include
Winuser.h file
#include <Winuser.h>
An alternative to the use of include files for API constants and functions prototypes could be to declare directly these ones in the code, using #define primitives and available native Visual dBASE types. This is possible, but more complex because all the function and constant definitions need to be rewritten carefully, with a risk of error. The Visual dBASE include files can do all this stuff for us.
Finally, to close this chapter, purists
may have noticed that since the Winuser.h
file is included, the file Windef.h
doesn't need to be included anymore. In fact, the Winuser.h
file contains itself (take a look at the beginning of the file) an include
statement of the Windef.h
file. In our case, including the Winuser.h
file will declare simultaneously all the types and constants required by
ShellExecute.
Running
an API 32 function
Now, let's see how to execute an API function. We are going to run the Windows calculator. This application can be found in the Windows directory, called here C:\Windows; its name is : Calc.exe
Let's look again in the API OLH file for the ShellExecute function, especially the section that lists the parameters :
// Include
Winuser.h file
#include <Winuser.h>
// Prototype
declaration for ShellExecute function
extern HINSTANCE ShellExecute(HANDLE, LPCSTR,
LPCSTR, LPCSTR, ;
LPCSTR, CINT) shell32 ;
from "ShellExecuteA"
ShellExecute( _app.FrameWin.hwnd, "open", "calc.exe", Null, "C:\Windows", SW_SHOWNORMAL )
Run this program. The Windows calculator is displayed. This example has shown the use of parameters of different types, numeric, strings, API 32 constants.
Now remains a last parameter
type, whose existence has just been revealed previously : the C structures.
They are explained at the end of this article, because their handling requires
us to have acquired the basic notions developed up to here.
C
structures and Visual dBASE
If the C language has been considered for long as reference in software development before OOP languages were introduced, it's partly due to the power provided by structures.
To support the C structures, we will now consider the case of the API 32 function named GetVersionEx, function that can get the computer's Operating System version it's running on.
First, let's find the help page about this function in the API help file :
This function uses a single parameter called lpVersionInformation, which is a C pointer to a structure named OSVERSIONINFO. Click on this link OSVERSIONINFO shown in the parameter. You get the following screen :
Let's see now the different indications in red :
C Structure | Classes and Objects |
The
C structure is a static entity.
It's a list of variables or pointers of fixed length. They are placed one after the other in memory, in the order where they are declared. Their position in memory never changes. The memory space occupied by a C structure is fixed, and corresponds exactly to the sum of the variables sizes that compose it. |
As
opposed, an objet is entirely dynamic.
It contains elements (properties, methods) that can be modified, added, released, etc. ... The memory occupation of an object's members is not necessarily contiguous, as a dynamic memory space management is performed. The size of an object is variable, and depends upon its content. |
A structure can only contain variables and pointers. | An object can contain properties (that are variables or pointers), and methods. |
To better represent a structure, here is
the memory occupation of the OSVERSIONINFO
C
structure :
Memory occupation : Total = 148 bytes | Structure member |
Offset : n to n + 3 | dwOSVersionInfoSize |
Offset : n + 4 to n + 7 | dwMajorVersion |
Offset : n + 8 to n + 11 | dwMinorVersion |
Offset : n + 12 to n + 15 | dwBuildNumber |
Offset : n + 16 to n + 19 | dwPlatformId |
Offset : n + 20 to n + 147 | szCSDVersion |
Structures and classes being different by nature, it is not possible to use the second one to operate the first one.
Handling a structure's content with Visual dBASE requires access to a memory buffer, representing the structure's content (the members), in which changes can be made byte per byte.
Basically, the only mechanism supplied is to represent this memory buffer in Visual dBASE as a string. We can then, using the setByte and getByte string methods, read and modify the content of the string byte per byte. Even if this method can be used, it is very hard and heavy, and you need to spend a significant amount of time before getting to the appropriate result.
Fortunately, there is a much more efficient method.
Even if Visual dBASE doesn't know anything about C structures, there is a much more powerful tool : the classes and the objects.
We are now going to see how to simulate the behavior of a C structure, and in an OOP language like Visual dBASE, we'll replace structures by classes, and get the structures working as easily and flexibly as objects.
Among the tools and samples supplied with Visual dBASE 7.01, a set of files will help to achieve this goal. The files are the following :
Now, let's look at a real case. We are going to call the API 32 function GetVersionEx , and then access the OSVERSIONINFO structure members.
The described lines of code below are not shown in the order they would be in the source code, but it helps for better explanations and understanding.
First, the include files. A quick search
on the API constants used by GetVersionEx
and OSVERSIONINFO
shows that the following files are needed.
The file StructAPI.h
must also be included because we will use it with the Structure class :
// necessary include files
#include <Windef.h>
#include <Winbase.h>
#include <Structapi.h>
Then, the Structure class must be loaded, it is in the _DBWINHOME\Samples\Structure.prg file :
// load the Structure class
set procedure to '&_dbwinhome.samples\structure.prg'
additive
Now, let's see how to create the OSVERSIONINFO object equivalent to the C structure. It is an instance of the Structure class, defined in the _DBWINHOME\Samples\Structure.prg file just previously loaded. Then, the addMember method is used to declare the Structure members. This method uses as parameters :
// This class definition corresponds to
the C structure,
// type _OSVERSIONINFO. The addMember
method creates the structure
// members, using the member's type and
name.
class _OSVERSIONINFO of Structure
super::addMember( TYPE_DWORD,
"dwOSVersionInfoSize" )
super::addMember( TYPE_DWORD,
"dwMajorVersion" )
super::addMember( TYPE_DWORD,
"dwMinorVersion" )
super::addMember( TYPE_DWORD,
"dwBuildNumber" )
super::addMember( TYPE_DWORD,
"dwPlatformId" )
super::addMember( TYPE_STRING,
"szCSDVersion", 128 )
// The following line of code initializes the dwOSVersionInfoSize member
of
// the structure, that must,
as indicated in the API 32 help file, contain the
// structure's size.
// The Structure class length()
method returns the overall size of the structure,
// in bytes. When an object
is created using the _OSVERSIONINFO class, this member
// will be automatically
initialized.
super::setMember( "dwOSVersionInfoSize",
this.length( ))
endclass
As you can see, the last line of code in the _OSVERSIONINFO class calls the setMember method. Actually, the API help about OSVERSIONINFO indicates that the dwOSVersionInfoSize member must be set to the total size of the structure. By calling the length() method in the class constructor's code, the size is set automatically.
Now, the prototype of the GetVersionEx function can be written by copying the corresponding lines from the _DBWINHOME\Include\Win32api.prg file :
// GetVersionEx function prototype as it
is written in Win32api.prg
extern BOOL
GetVersionEx( LPSTRUCTURE ) kernel32 ;
from "GetVersionExA"
Some local variables are set up, especially
OSVERSIONINFO
, the structure object created from the _OSVERSIONINFO
class :
local OSVERSIONINFO, dwPlatformId
Now, the OSVERSIONINFO object is created as an instance of the _OSVERSIONINFO class :
// Creates an instance of the structure
object.
OSVERSIONINFO = new _OSVERSIONINFO( )
Now that the structure object OSVERSIONINFO is created, the function GetVersionEx can be called to update the content of the object.
NOTICE that the parameter passed in the function call is the .value property, as this string will represents the memory buffer where the C structure members are stored. :
// Call to the GetVersionEx function, input
parameter is OSVERSIONINFO.value,
// it will represent the contents of the
C structure in memory.
GetVersionEx( OSVERSIONINFO.value )
The remaining code simply displays the member's content using the getMember method, as they have been updated by GetVersionEx.
Here, a macro command, named LOWORD, that is declared in Windef.h, returns only the content of the 16 low order bits of a 32 bits word. Its equivalent HIWORD also exists for the 16 high order bits. Take a look at the file Windef.h, it contains a set of useful macros to split and merge binary words.
// Displays the content of the structure
members in the command window
? 'Major Version', OSVERSIONINFO.getMember(
'dwMajorVersion' )
? 'Minor Version', OSVERSIONINFO.getMember(
'dwMinorVersion' )
? 'Build Number', LOWORD( OSVERSIONINFO.getMember(
'dwBuildNumber' ))
dwPlatformId = OSVERSIONINFO.getMember( 'dwPlatformId'
)
do case
case dwPlatformId == VER_PLATFORM_WIN32s
? 'Win32s on Win 3.1'
case dwPlatformId == VER_PLATFORM_WIN32_WINDOWS
? 'Win32 on Windows 95'
case dwPlatformId == VER_PLATFORM_WIN32_NT
? 'Win32 on Windows NT'
endcase
The complete Visual dBASE program for this
example can be loaded using the link at the bottom of the page. Run this
program. In the command window, it displays the Windows version number,
its build number, and the Operating System version.
Creating
an executable including the structure files
You can use the method provided before in an executable program built with Visual dBASE. Just follow these guidelines :
Despite being an excellent starting point, the Structure class provided in Structure.prg suffers from a few gaps, that sometimes restricts its fields of application.
For this reason, I have created my own StructureEx class, compatible with the Structure class, but enhanced with the points listed below. This class can replace the Structure class.
Here are, in a few points, the added capabilities :
After all, using direct Windows API 32 calls from within Visual dBASE is not so a difficult task, as soon as you use a rational method. This article does not intend to be an exhaustive presentation of the topic, but rather a first approach to get started. Of course, other methods are possible, everyone is free to use his own; but I thought that this one, with the few notions of C language it requires, was worth being presented; due to its simple implementation.
Visual dBASE shows once again, that, thanks to its OOP language, it can adapt to a particular situation for which it has not especially been designed for.
Anyway, this Windows API can remain sometimes difficult to approach, due to its initial design features.
Now, it's your turn !
Nicolas Martin