Last update Sun Mar 4 11:58:08 2018

Using Handle Pointers

Handle pointers are a special data type that support virtual memory management in 16-bit compilations. They let a data structure use as much as 16KB of memory and your program use as much as 16MB. Handle pointers are an alternative to DOS extenders that you can use in code for all members of the Intel 8088 family of processors.

Handle pointers are a Digital Mars C++ extension to the normal far pointer type. __handle pointers provide access to memory that can be accessed only through indirection (a handle). Currently, handle pointers support the use of expanded (EMS or LIMS) memory. A handle pointer differs from a standard far pointer in that it is assumed to contain handle information in its segment address as well as a normal pointer offset. When memory is accessed through a handle pointer, the handle is automatically dereferenced to ensure that the information is obtained from the correct place. In the case of expanded memory, any page mapping that may be required is carried out automatically.

For information about other pointer types, see Mixing Languages and Compiling Code.

What's in This Chapter

About Handle Pointers

Handle pointers are instances of a type called a handle. You use a handle instead of an ordinary pointer to refer to a data structure. Handle pointers point to data in some external memory area, such as expanded memory, extended memory, or disk.

When your program needs the data, the handle is dereferenced (that is, converted into an ordinary pointer), and the data it points to is read into conventional memory.

Features of the handle pointer include:

Note: The keyword __handle is ignored in compilations using the NT (-mn) and OS/2 2.0 (-mf) memory models.

How handle pointers work

Handle pointers work by storing data outside the DOS memory area and reading it into conventional memory only when it is needed. When you declare a handle, the compiler stores the object it refers to in a page. In order of preference pages are stored in expanded memory, extended memory, or disk. There is a maximum of four pages, and each can hold 16KB. A single page can hold several different variables.

When you access a handle's data, your program reads the page that contains the data into conventional memory. For example, suppose a, b, c, i, j, and k are handles, and n1 and n2 are ordinary pointers. If you refer to i, the program swaps its page into conventional memory (see Figure 21-1) and converts i to a far pointer. If the page is stored on disk, your program automatically reads it in. The page that contains i is now called a physical page since it is in conventional memory. The other page is called a logical page since it is not currently loaded into conventional memory. With our compilers, the conversion is performed automatically; you don't need to call a function.

Figure 21-1 Accessing the handle i,

Conventional Memory     Handle Space (on disk or expanded memory)
Physical page 2         Logical page 1
*i: 89.32               *a: 100
*j: 78.29               *b: 3.5E12
*k: 102.14              *c: "Hello\n"

Data Segment
n1: "John"
n2: "Maria"

When you refer to a handle in another page, your program swaps that page into memory, swapping the other page out if necessary. In the above example, when your code references c, the program swaps i's page to disk and swaps c's page into memory. The result is illustrated in Figure 21-2.

Figure 21-2 Accessing the handle c

Conventionl Memory      Handle Space (on disk or expanded memory)
Physical page 1         Logical page 2
*a: 100                 *i: 89.32
*b: 3.5E12              *j: 78.29
*c: "Hello\n"           *k: 102.14

Data Segment
n1: "John"
n2: "Maria"

The handle format

The handle type is a 32-bit type in 16-bit compilations. The high 16 bits point to the page and the low 16 bits point to an offset in the page. To convert a handle to a far pointer, the offset is added to the address of the physical page. Handles are unique; no two handles can refer to the same location in handle space.

To distinguish between a handle that holds an actual handle and one that holds a converted far pointer, the compiler reserves the values 0xFE00 to 0xFFFF as page addresses. Since those are the segment addresses for the ROM BIOS, it is unlikely any program would store data there. The compiler treats a far pointer with a segment address less than 0xFE00 as an ordinary far pointer. It treats a far pointer whose segment address is greater than 0xFE00 as a handle pointing to logical page (segment -0xFE00). However, a variable explicitly declared as a far pointer that has a segment address greater than 0xFE00 is still treated as a far pointer and can access the ROM BIOS area.

Using Handles

Declaring a handle pointer is like declaring a far pointer. To declare a handle pointer, use the __handle keyword. For example, this statement declares h to be a handle to an integer:
int __handle *h;
Use a handle as any other pointer. For example:
*h = 3;
printf(" h=% d\ n", *h);
Your program doesn't need to perform any special initialization to use handles. The compiler adds initialization code (in c.asm) automatically. You must be sure your program frees memory when it exits; otherwise, it will be unavailable to other programs until the machine is re-booted. To free up the memory your program uses, be sure to call exit() at all the places where your program could end. Handles use extended memory, which DOS does not automatically free up. You might want to use special C++ error handling to make sure that exit() is called even when your program terminates abnormally (due to a system error or pressing Control-C, for example).

In comparisons and arithmetic operations, handles are treated like far pointers. In all arithmetic and the comparisons <, <=, >=, and >, the compiler uses only the 16-bit offset into the page. When testing for equality or inequality (== or !=), the compilers use the full 32-bit value.

Dynamically allocating memory

If you want to create a handle that refers to dynamically allocated memory, use the functions in handle.h, such as handle_malloc(), handle_realloc(), and handle_free(). When you try to allocate memory, these functions check a table stored in physical memory to see if there is space available in an existing page. If there is not enough expanded memory available, or if you try to allocate a block larger than 16KB, the handle functions attempt to allocate the memory from conventional memory.

Dereferencing handles

Handles are converted to far pointers whenever you dereference a handle or cast a handle to a far pointer. The compiler performs the conversion with a library routine, which swaps the logical page into memory and returns a far pointer into the page. For example, the following operations all convert a handle to a far pointer:
int __handle *h;
struct A __handle *h2;
int far *f;
int i;
extern void func(int far *pi);

 * These operations convert
 * handles to far pointers.
f = h;
*h = i;
h[3] = *f;
i = *(h + 6);
h2->b = i;
h = (int far *) h;

 * This operation performs no conversion.
h = f;
The compiler avoids converting a handle when it can use a previous conversion. In the following code, for example, h needs to be converted only once:
struct { int a, b; } __handle *h;
h->a = 1;
h->b = 2;
The compiler converts the code to:
struct { int a, b; } __handle *h, far *p;
p = h;
p->a = 1;
p->b = 2;
The compiler can't use the result of a previous conversion if:

Optimizing handle code

If you dereference a handle and then call a function that you know does not dereference handles, you can make sure the handle isn't converted unnecessarily by optimizing your code yourself. This code, for example, converts h twice:
int __handle *h;
*h = 1;         /* Convert h once  */
func();         /* A function call */
*h = 2;         /* Convert h twice */
This optimized code dereferences h only once:
int __handle *h, far *f;
f = h;          /* Convert h once */
*f = 1;
func();         /* A function call */
*f = 2;
Make sure you don't use more than four dereferenced handles at once. The handle implementation use a maximum of four pages. For more information, see the section "Debugging programs that use handles" later in this chapter.

Tips for Using Handle Pointers Efficiently

Although using handles gives you access to a large amount of memory, it can also make your program slower and larger. Your program may frequently swap pages in and out of memory and it will contain additional code to dereference handles. Here are some suggestions for making your program more efficient:

Porting code with handles

To port code that uses handles to a compiler that doesn't implement handles, define __handle to be nothing, like this:
#define __handle
All handles become regular pointers.

If you use the dynamic allocation functions in handle. h, define NO_HANDLE to be 1 before you #include handle.h, like this:

#define NO_HANDLE 1
#include <handle.h>
The functions in handle.h will call their equivalents in the standard library, such as malloc(), realloc(), and free().

Debugging programs that use handles

Here is a list of points to watch out for: Here are some techniques to deal with the problems outlined above:
Home | Runtime Library | IDDE Reference | STL | Search | Download | Forums