The HLStruct Class for WinAPI structs

In this page I use following convention:

(You will see colors only if you are using a CSS1-capable browser.)

A quick example
Using strings
Using nested structs
Using arrays
WinAPI Data Types
Function reference
Copyright notice and disclaimer


A quick example (for Win32)

As example we will use the simple structure RECT and call the GetClientRect() function.

1. Lookup the structure and the function

In the Win32 Programmer's Reference you will find the function and the structure described in the C language:
    BOOL GetClientRect (
      HWND hWnd,      // handle of window
      LPRECT lpRect   // address of structure for client coordinates
    );

    typedef struct _RECT {    // rc
      LONG left;
      LONG top;
      LONG right;
      LONG bottom;
    } RECT;
(You'll find a Windows-Help version of the Win32 Programmer's Reference in the HELP directory of your VdB 7.01 installation: WIN32.HLP. Use the Index to search for GetClientRect and RECT. )

2. Extern the function

When externing the function, the argument taking the address of the struct must be declared as LPSTRUCT. LPSTRUCT is defined in hlstruct.cch.

    #include "hlstruct.cch"
    extern CLOGICAL GetClientRect (CHANDLE, LPSTRUCT) USER32

3. Translate the C struct into a VdB class

Derive your VdB class from HLStruct and use the HLS-macros:
    CLASS RECT OF HLStruct
       HLS_LONG("left")
       HLS_LONG("top")
       HLS_LONG("right")
       HLS_LONG("bottom")
    ENDCLASS
For most C types you can find the appropriate HLS-macro just by prepending HLS_ to the C type name. Always translate the entire C prototype, don't skip the members you don't need. If you don't know how to translate a member, try with HLS_INT first.

4. Call the function

Create a HLSRECT object and initialize its members. (In the case of GetClientRect() you really don't need to initialize members, but we do it here to illustrate the concept.)

Call the put() method of the class. Pass the result of put() to the LPSTRUCT parameter of the WinAPI function.

Call the get() method of the class. Access the class properties to gather what information you need.

Release the object.

    LOCAL rect
    rect = new RECT ()
    rect.left = 0
    rect.top = 0
    rect.right = 0
    rect.bottom = 0
    GetClientRect (form.hwnd, rect.put ())
    rect.get ()
    ? "Form size in pixels: ", rect.right, rect.bottom
    rect.release ()
Always pair calls to put() and get() even when you need no answer from the function. The class needs this to do its housekeeping.

Always release the object when you are done. If you don't release you will leak memory.


Using strings

Strings contained in a struct can be of 2 types:

Strings referred to by pointer

This is the more common type. These strings have a C type of LP?STR. All you have to do is to use the corresponding HLS-macro.

A typical struct using strings referred to by pointers is the DOCINFO struct:

    typedef struct {    // di
        int     cbSize;
        LPCTSTR lpszDocName;
        LPCTSTR lpszOutput;
        LPCTSTR lpszDatatype;   // Windows 95 only; ignored on Windows NT
        DWORD   fwType;         // Windows 95 only; ignored on Windows NT
    } DOCINFO;
This would translate to the VdB class:
    #include "hlstruct.cch"

    CLASS DOCINFO OF HLStruct
       HLS_INT("cbSize")
       HLS_LPCTSTR("lpszDocName")
       HLS_LPCTSTR("lpszOutput")
       HLS_LPCTSTR("lpszDatatype")
       HLS_DWORD("fwType")
    ENDCLASS

Inlined Strings

Inlined strings have a C type of ?CHAR [size]. Use the corresponding HLS-macro passing the size as second argument.

A typical struct containing an inlined string is the DEVMODE struct. CCHDEVICENAME is a constant defined in a Windows header file.

    #define CCHDEVICENAME 32

    typedef struct _devicemode {    // dvmd
        BCHAR  dmDeviceName[CCHDEVICENAME];
        WORD   dmSpecVersion;
        WORD   dmDriverVersion;
        WORD   dmSize;
        ... more members ...
    } DEVMODE;
This would translate to the VdB class:
    #include "hlstruct.cch"
    #define CCHDEVICENAME 32

    CLASS DEVMODE OF HLStruct
       HLS_BCHAR("dmDeviceName", CCHDEVICENAME)
       HLS_WORD("dmSpecVersion")
       HLS_WORD("dmDriverVersion")
       HLS_WORD("dmSize")
       ... more members ...
    ENDCLASS

Unicode and ANSI

VdB 7 internally uses Unicode strings. The HLStruct class translates transparently from the internal Unicode to the external ANSI representation where appropriate.

Windows NT provides two versions of every WinAPI function that uses strings or structs containing strings. The "W" version takes Unicode strings whereas the "A" version takes ANSI strings. Win95 provides both versions too, but the "W" functions are just a fake, they simply don't work.

If you want your programs to run on Win95 and NT you should always call the "A" function (and skip to Using Nested Structs).

If you support NT only you may call the "W" version. If you are going to call only "W" functions, #define UNICODE at the top of HLSTRUCT.CCH (and skip to Using Nested Structs).

If you are going to mix calls to "A" and "W" functions, don't #define UNICODE but use the HLS_LPWSTRand HLS_WCHAR macros for structs you pass to "W" functions, and HLS_LPTSTRand HLS_TCHARmacros for structs you pass to "A" functions. If you are going to pass the same WinAPI struct to both a "W" and an "A" function, you must define two different classes one for each function.


Using Nested structs

Nested structs are structs inside a struct. Nested structs can be of one of the following types:

The second parameter to each of these macros is an object of the nested class.

Access the members of the nested struct with:

    oStruct.oNestedStruct.Member

Nested structs referred to by pointer

The CHOOSEFONT struct contains a pointer to a LOGFONT struct:
    typedef struct {    // cf
        DWORD        lStructSize;
        HWND         hwndOwner;
        HDC          hDC;
        LPLOGFONT    lpLogFont;
        INT          iPointSize;
        ... more members ...
    } CHOOSEFONT;
This translates to the VdB class:
    #include "hlstruct.cch"

    CLASS CHOOSEFONT OF HLStruct
       HLS_DWORD("lStructSize")
       HLS_HWND("hwndOwner")
       HLS_HDC("hDC")
       HLS_LPSTRUCT("lpLogFont", new LOGFONT ())    && pointer to LOGFONT struct
       HLS_INT("iPointSize")
       ... more members ...

       this.lStructSize = this.GetSize ()
    ENDCLASS
(You also see how you can conveniently initialize a struct member with the struct size. This is required for many WinAPI structs.)

To create an object of class CHOOSEFONT and initialize some LOGFONT members:

    oChooseFont = new CHOOSEFONT ()
    oChooseFont.hwndOwner = _app.framewin.hwnd
    oChooseFont.lpLogFont.lfFaceName = "Arial"
    oChooseFont.lpLogFont.lfHeight   = 12

Inlined nested structs

The PAINSTRUCT struct is a typical example of a struct containing an inlined (RECT) struct.
    typedef struct tagPAINTSTRUCT { // ps
        HDC  hdc;
        BOOL fErase;
        RECT rcPaint;
        BOOL fRestore;
        BOOL fIncUpdate;
        BYTE rgbReserved[32];
    } PAINTSTRUCT;
This translates to the VdB class:
    #include "hlstruct.cch"

    CLASS PAINTSTRUCT OF HLStruct
       HLS_HDC("hdc")
       HLS_BOOL("fErase")
       HLS_INLINEDSTRUCT("rcPaint", new RECT ())
       HLS_BOOL("fRestore")
       HLS_BOOL("fIncUpdate")
       HLS_BYTES("rgbReserved", 16)
    ENDCLASS
Use the HLS_BYTESmacro for unstructured data that is not characters.

Nested structs referred to by global handle

This is a very rare case. An example of this pattern is the PRINTDLG struct:
    typedef struct tagPD {  // pd
        DWORD     lStructSize;
        HWND      hwndOwner;
        HANDLE    hDevMode;
        HANDLE    hDevNames;
        HDC       hDC;
        ... more members ...
    } PRINTDLG;
    #include "hlstruct.cch"

    CLASS PRINTDLG (oDevMode, oDevNames) OF HLStruct
       HLS_DWORD("lStructSize")
       HLS_HWND("hwndOwner")
       HLS_HGLOBALSTRUCT("hDevMode", oDevMode)   && global memory handle
       HLS_HGLOBALSTRUCT("hDevNames", oDevNames) && global memory handle
       HLS_HDC("hDC")
       ... more members ...

       this.lStructSize = this.GetSize ()
    ENDCLASS
oDevMode and oDevNames are pre-instantiated objects of class DEVMODE and DEVNAMES.


Using Arrays

Arrays are structs. If you name a struct member with a numeric value instead of a string, you can afterwards access it as:
    oStruct [nName]
To build an array of integers use:
    #include "hlstruct.cch"

    CLASS INTARRAY (nElements) OF HLStruct
       LOCAL i
       FOR i = 0 TO nElements - 1
          HLS_INT(i)
       NEXT
    ENDCLASS

    LOCAL a ; a = new INTARRAY (3)
    a [0] = 1
    a [1] = 3
    a [2] = 5
You can number your elements starting by 0 or 1 (or any other number) just as you like it, or even number them backwards. A number is just a name for an element. In the WinAPI struct all members come in the order of their definition.


WinAPI Data Types

This is an introduction to WinAPI data types. It helps you select the appropriate HLS-macro where in doubt.

Introduction

The Windows OS is written in C, so its natural that its interfaces are described in C notation too. The Windows API is explained in the Win32 Programmer's Reference (32 bit API) and the Windows 3.1 SDK (16 bit API) help files. Furthermore there is a machine-readable form of the WinAPI description for the use of compilers and other tools. The machine-readable interface is a set of so-called C "include files" or "header files" (usually with a filename of *.H).

The Win32 Programmer's Reference is included with the VdB 7 product. The Windows 3.1 SDK help file is not included with the VdB 5.6 product. The original header files are not included with either product. VdB 7 includes a VdB-ified version of the header files, but much information was lost in the conversion, so they are near to worthless. To get the Windows 3.1 SDK and the original header files you have to find a copy of Borland C++ or Borland Delphi or MS Visual C++.

Basic C Data Types and Sizes

To introduce you to basic C data types, I quote from section 2.2 of the "New Testament" [KR88]:

"There are only a few basic data types in C:

char
a single byte, capable of holding one character in the local character set.
int
an integer, typically reflecting the natural size of integers on the host machine.
float
single-precision floating point.
double
double-precision floating point.

In addition, there are a number of qualifiers that can be applied to these basic types. short and long apply to integers:

    short int sh;
    long int counter;
The word int can be omitted in such declarations and typically is.

The intent is that short and long should provide different lengths of integers where practical; int will normally be the natural size for a particular machine. short is often 16 bits, long 32 bits, and int either 16 or 32 bits. Each compiler is free to choose appropriate sizes for its own hardware, subject only to the restriction that shorts and ints are at least 16 bits, longs are at least 32 bits, and short is no longer than int, which is no longer than long.

The qualifier signed or unsigned may be applied to char or any integer. unsigned numbers are always positive or zero, and obey the laws of arithmetic modulo 2n, where n is the number of bits in the type. So, for instance, if chars are 8 bits, unsigned char variables have values between 0 and 255, while signed chars have values between -128 and 127 (in a two´s complement machine). Whether plain chars are signed or unsigned is machine-dependent, but printable characters are always positive.

The type long double specifies extended-precision floating point. As with integers, the sizes of floating-point objects are implementation-defined; float, double and long double could represent one, two or three distinct sizes."

C Declarations

In C you must declare a variable before you can use it. Once declared the variable doesn't change type. You declare a variable like this:

    int i;
Note the characteristic notation of C; you first say what you are going to declare and then name it: "I declare an int that I will call i".

The advantage of declaring a variable, which by far outweighs the trouble of doing it, is that the compiler will catch the most common errors for you. If you misspell CustonerId, the compiler will tell you that there ain't no CustonerId.

If you code something like this:

    int i;
    int length;
    i = 1;
    lenght = strlen (i);
The compiler will tell you that you are trying to take the length of a string but are not passing a string as argument to the function and that there is no variable lenght. The compiler will not produce an executable file before all these errors are fixed.

On the other hand, VdB will happily compile:

    replace custom->id with CustonerId
    i = 1
    length = len (i)
and not until you run the program and trigger these program lines, you will be told, that there is no CustonerId and there is a data type mismatch. So you must make sure you run every line of your code before deploying. A tedious task.

C Pointers

In addition to these data types, we have a pointer type. Again, a C pointer points to a particular kind of object. Eg. you may have a pointer to an int or a pointer to a double.

You declare a pointer with the unary operator *.

    int *ip;
    double *dp;
We just declared two variables: ip as a pointer to an int type, dp as a pointer to a double.

C Typedefs

"C provides a facility called typedef for creating new data type names. For example, the declaration
    typedef int Length;
makes the name Length a synonym for int. The type Length can be used in declarations in exactly the same way as the type int can be:" [KR88]
    int len1;
    Length len2;
Again, note the characteristic C notation: "I want to typedef the data type int to be called Length".

Typedefs can be found in great abundance in the WinAPI header files. This is just an excerpt from the WinAPI 16 windows.h file:

    typedef int                 BOOL;
    typedef unsigned char       BYTE;
    typedef unsigned short      WORD;
    typedef unsigned long       DWORD;
    typedef unsigned short      USHORT;
    typedef unsigned int        UINT;
    typedef signed long         LONG;
    typedef char *              LPSTR;
    typedef const char *        LPCSTR;
We see that the WinAPI data type WORD is an alias for the C type unsigned short and DWORD is just an unsigned long.

C Structures

"A structure is a collection of one or more variables, possibly of different types, grouped together under a single name for convenient handling." [KR88]

Like simple variables, C structures must be declared before using them. This declares a structure point, with two elements of type int:

    struct point {
       int x;
       int y;
    };
C compilers handle structures in a way completely distinct from how an interpreter like VdB handles them. A VdB object has a list of member names attached to it that contains the address where VdB can find each member in memory. Members of one and the same object can be stored very far away from each other. VdB does a lookup of the properties name against the object's member list at runtime. This design is flexible in that it allows you to add members at runtime: simply add another entry to the member list. It is also very slow compared to the approach of a compiler.

A compiler stores all members of a struct in adiacent memory. This means: the x member of struct point will start at struct offset 0. Assuming an int is 4 bytes long, the y member will start at struct offset 4 and the whole struct point will have a size of 8 bytes. The compiler computes the relative offsets of the members and only those offsets are stored in the executable code. You will find no structure member names in compiled C code but you will find plenty of them in a VdB "compiled" program.

This is the reason why you cannot leave out any member from a C structure declaration. If you do, all following members will change their offset. Because the WinAPI function to which you pass the structure accesses its members through offsets it will not find the right ones any more.

How to convert a WinAPI structure to a VdB HLStruct

Lookup the function in the WinAPI reference. Use the Win32 Programmer's Reference for WinAPI 32 structs and the Windows 3.1 SDK help file for WinAPI 16 structs. To make portable structs you have to consult both. Then convert all structure members to the appropriate HLS-macro. This is a table of all HLS-macros and the WinAPI data type they represent.

Data type    
name
Description     Win 16
API size    
Win 32
API size    
 
HLS-macro
char signed integer 8 8 HLS_CHAR
short signed integer 16 16 HLS_SHORT
int signed integer 16 32 HLS_INT
long signed integer 32 32 HLS_LONG
LONG signed integer 32 32 HLS_LONG
UCHAR unsigned integer 8 8 HLS_UCHAR
USHORT unsigned integer 16 16 HLS_USHORT
UINT unsigned integer 16 32 HLS_UINT
ULONG unsigned integer 32 32 HLS_ULONG
BYTE unsigned integer 8 8 HLS_BYTE
WORD unsigned integer 16 16 HLS_WORD
DWORD unsigned integer 32 32 HLS_DWORD
HANDLE unsigned integer 16 32 HLS_HANDLE
HWND unsigned integer 16 32 HLS_HWND
HDC unsigned integer 16 32 HLS_HDC
HGLOBAL unsigned integer 16 32 HLS_HGLOBAL
HINSTANCE unsigned integer 16 32 HLS_HINSTANCE
HMODULE unsigned integer 16 32 HLS_HMODULE
BOOLEAN unsigned char 8 8 HLS_BOOLEAN
BOOL signed integer 16 32 HLS_BOOL
WPARAM unsigned integer 16 32 HLS_WPARAM
LPARAM signed integer 32 32 HLS_LPARAM
LRESULT signed integer 32 32 HLS_LRESULT
 
LPFN? long pointer to function 32 32 HLS_LPFN
LP?STR long pointer to string 32 32 HLS_LP?STR
 
?CHAR [number] inlined string number number HLS_?CHARS

? is a joker for zero or more characters. Note the plural in the HLS_?CHARSmacros. You have to specify number as second parameter to the HLS?_CHARS macros.

Data types like LP? but not LP?STR or LPFN? are long pointers to some data type. eg. LPDWORD is a long pointer to a DWORD. LPSOMENAME is most likely a long pointer to a SOMENAME structure. You have to lookup and convert that structure first, then use the HLS_LPSTRUCT macro to insert a pointer to the struct.

Data types like SOMENAME FAR * are long pointers to structures. See above.

For so-called Hook-Functions use HLS_LPHOOKor HLS_LPFN. You cannot access hook functions from VdB but must still declare them in your structs.

If you still dont find your data type here you must scan the Windows header files. Most likely you will find a typedef to a known data type. Use the HLS-macro for that data type then.

Making Portable Structs

If you are going to make the structure portable between 16 and 32 bit, you need to consult both, the Win32 Programmer's Reference and the Windows 3.1 SDK help file.

Some data type sizes change between WinAPI 16 and WinAPI 32, notably the int type and the ubiquitous HANDLE types.

Where the declaration of a structure member differs, use these macros. They are declared in a way that will get you the right sizes when compilig for either OS.

Win 32 API type    
 
Win 16 API type     HLS-macro
LONG int HLS_INT
LONG short HLS_INT
ULONG UINT HLS_UINT
ULONG USHORT HLS_UINT
DWORD UINT HLS_UINT
DWORD USHORT HLS_UINT
LPCSTR LPSTR HLS_LPCSTR
LPTSTR LPSTR HLS_LPTSTR
LPCTSTR LPSTR HLS_LPCTSTR
TCHAR []CHAR [] HLS_TCHAR
BCHAR []CHAR [] HLS_BCHAR

File Based Structures

Elements of file based structures usually do not change in size going from the Win16API to the Win32API. Therefore you should use only these HLS-macros when declaring file based structures:


Function Reference

A reference of the functions of class HLStruct.

Entries are in the form

    FunctionName [(Param1 [, Param2, [...]])] [ : ReturnValue ]
Functions not mentioned here are reserved. Do not call them.

Release

Releases all allocated memory. You must call this when you are done.

GetSize : nSize

Returns the size of the WinAPI struct in bytes.

GetFieldCount : nFieldCount

Returns the number of fields in the struct (useful with arrays).

Put : nAddress

Assembles the WinAPI struct from the HLStruct's properties and returns its address. You must pass the address to the WinAPI function requiring the struct. Always call get() after you call put().

Get

Scans the contents of the WinAPI struct into the HLStruct's properties.

ToString : cString

Writes the structs data into a VdB string. Use this in conjunction with the VdB function fwrite() if you have to write a struct to a file.

FromString (cString)

Reads the structs data from a VdB string. Use this in conjunction with the VdB function fread() to read a struct from a file.

ToStatus : cStatus

Converts the structs data into a sequence of printable characters. Use this to write the structs data into a non-binary stream.

FromStatus (cStatus)

Reads the structs data from a sequence of printable characters generated by ToStatus().

ToINI (cPath, cSection, cEntry)

Writes the structs data to a Windows INI file. Uses ToStatus().

FromINI (cPath, cSection, cEntry) : lFound

Reads the structs data from a Windows INI file. Returns .t. if the entry was found. Does nothing and returns .f. if the entry was not found. Uses FromStatus().

Malloc (nSize)

Allocates nSize bytes for the WinAPI structure. Use in those cases where the WinAPI wants to append more data at the end of the structure (eg. DEVMODE, DEVNAMES, PRINTER_INFO_?).


Bibliography

[KR88]
Kernighan and Ritchie, The C Programming Language Second Edition, Prentice Hall, 1988, ISBN 0-13-110362-8


Copyright notice and disclaimer:

© 1998 Marcello Perathoner

This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software.

The author is not obliged to fix bugs, write upgrades nor to provide any upgraded version of this software under these same terms.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
  2. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software.
  3. If you redistribute any of the included source code, you must include this notice. If you redistribute modified sources, I would appreciate that you document your changes.

I wrote this software; it does not include third-party code.

If you use this software in a product, I would appreciate not receiving lengthy legal documents to sign.

Visual dBASE and Borland are registered trademarks of Inprise Corp.
Microsoft and Windows are either registered trademarks or trademarks of Microsoft Corporation


Marcello Perathoner / JLSeagull@csi.com / revised June 22, 1998