www.digitalmars.com [Home] [Search] [CTG] [RTL] [UGR]

Win32 Programming Guidelines

This chapter describes how to use the compiler to create Win32 and Win32s applications. For information about:

What's in This Chapter

Some Basic Information

The following will help you understand the information in this chapter. For machines based on the Intel 80x86 architecture, a 32-bit operating system improves the performance of data manipulation and calculations because it avoids the overhead of loading segment registers. Applications that are data-, memory-, or calculation-intensive may perform significantly better in 32-bit mode.

About Win32s

Win32s is an NT emulator or operating system extension that allows you to run Win32 applications on a Windows 3.1 host. The "s" in Win32s stands for Win32 subset: Win32s supports almost all Win32 API features.

Note: Win32s applications built with C++ Version 7 will only run on version 1.15 and above of Win32s.

Advantages of Win32s over Win16

Win32s, included with Digital Mars C++, gives you the following advantages over conventional Win16 applications: Win32s lets your Win32 applications make 32-bit calls to Windows 3.1, where graphics and windowing operations are performed. Win32s consists of a virtual device driver (VxD) and a set of dynamic link libraries (DLLs) that extend Windows to support 32-bit applications.

Key Win32s features

Many features are available through the Win32s Applications Programming Interface (API), such as:

Building Win32 and Win32s Applications

Digital Mars C++ includes a set of tools that make it easy to create and debug Win32 and Win32s applications from either the command line or within the integrated environment. By setting switches, all the toolsó compiler, linker, resource compiler, and resource editorsó generate the correct type of 32-bit application. The following is basic information useful for building Win32 and Win32s applications: The OPTLINK linker does not support COFF format .lib or .obj files produced by Microsoft tools.

Note: To developers using NT: OPTLINK (the linker) does not accept COFF resource files, but does accept standard Microsoft resources files.

Compiling Win32 Executables

This section explains how to create various kinds of Win32 executables.

Compiling with MFC 3.x

If your Win32 application uses MFC 3.x libraries, see the "READ ME" file included with the distribution for a list of the options, libraries, and #defines you need to build various targets with the latest MFC release. Note that, for all MFC 3.x libraries, you need to compile with the NT (-mn) memory model.

Compiling Win32 DLLs

To compile Win32 DLLs, use these compiler options:
	-mn -WD 
To write a Win32 DLL, you need to specify the entry point DllMain(). When the compiler detects a call to DllMain() in a 32-bit compilation, it generates a reference to the _acrtused_ dll startup function, and links with the appropriate startup code.

Unlike previous versions of C++, this startup code calls static constructors and performs other initialization steps before calling DllMain().

Converting older Win32 DLLs

To convert Win32 DLLs built with previous versions of C++ to work with this version, make the changes listed below: Note: Win32 DLLs compiled with previous versions of C++, in which the entry point is specified with #pragma startaddress, need to be rewritten to call DllMain() instead. Otherwise any static constructors will not be called, and any run-time library calls will not work as expected because proper initialization has not taken place.

Building Console Applications

A console application is a character mode application that runs under Windows 95 or NT. Digital Mars C++ supports console mode applications.

To compile console applications, use the -mn compiler option, and specify the SUBSYSTEM CONSOLE directive in the .def file.

Startup code for console applications

When the compiler detects a call to main() in a 32-bit compilation, it generates a reference to the _acrtused_ con startup function, and links with the appropriate startup code. You no longer need to explicitly link in cc.obj, as with previous versions of C++; it is now part of snn.lib.

Compiling Win32s Executables

To compile Win32s executables, use the -mn and -WA compiler options. Note: HP Dashboard does not support Win32s. If you try to debug a Win32s application under Dashboard, the debugger receives a GPF message from the application being debugged.

Compiling Win32s DLLs

To compile Win32s executables, use the -mn and -WA compiler options.

Note: To create libraries from Windows NT System DLLs (kernel32.dll, for example), use the IMPLIB utility and specify the /system switch. IMPLIB is available only from the command line, not via the IDDE.

Win32 Definition File Directives

This section lists the directives used in definition files for Win32 programs. For descriptions of these directives see Chapter 11, "Definition File Directives."
	NAME name [BASE=number] 

	LIBRARY name [ BASE= number | PROCESSINIT |

	DESCRIPTION 'descriptive line' 


	HEAPSIZE [number, commit] 

	STACKSIZE [number, commit] 

	STUB 'filespec' 


	VERSION number[. number] 



	{   name [CLASS 'class'] 

	IMPORTS { [internal=] externalfile. func } 

	EXPORTS { parms [=intname] [@number [NONAME]] 

Recompiling Win16 Applications for Win32

To convert to a Win32 or Win32s application to a Win16 application created with the C/C++ compilers, try these suggestions: Note: For more tips on how to solve specific problems that might occur when porting 16-bit code to Win32, see Chapter A, "Porting Guidelines."

Converting Non-Digital Mars Win16 Applications to Win32 Applications

The following information will help you convert 16-bit programs developed with other programming tools to be Win32 or Win32s applications.

Start with a bug-free Win16 application. Be sure your Win16 application compiles and runs without any compiler or linker errors or warnings.

Convert the application to be a Win16 application. For instructions on porting code from Borland or Microsoft, see Chapter 22, "Switching to Digital Mars C++." Be sure the converted program compiles and runs with Digital Mars C++ without any errors or warnings.

Save the original source code and makefile, preferably in another project directory. Now that the application is a Digital Mars Win16 application, convert it to Win32, following the hints above.

Finally, after a clean build, preferably with no warnings, test the application.

If you are targeting both Win32 and Win32s, you should test these applications thoroughly in both environments, as there are a number of significant differences between Win32 and Win32s.

Win16 Programming Guidelines

This chapter describes how to use the compiler and linker to create Windows 3.x applications and DLLs. For the compiler, it explains how to choose memory models, and select the entry and exit code (if any) you want to generate for functions. For the linker, it describes switches and definition (.def) file statements for linking Windows objects.

For information about:

What's in This Chapter

Compiling Windows Programs

This section describes how to choose memory models and use prolog and epilog code when compiling Windows programs with C++.

Choosing a memory model

You can use these memory models in Windows programs: We recommend compiling Windows applications with the Large model, as this minimizes the problems associated with mixed-model programming. Windows 3.0 and later eliminate any advantage to using the Medium memory model. It is especially important to use the Large model when compiling DLLs, since some functions in the run-time library work correctly only in DLLs that are compiled with the large model. Also, Digital Mars C++ only supports MFC in the Large model.

In general, if in doubt about what model to use, use the Large model. Specify the Large model with the -ml compiler option (see Chapter 2, "Compiling Code"), or use the Memory Models subpage on the Build page of the IDDE's Project Settings dialog box. (The compiler will use the Small model unless you specify otherwise.)

Note: You cannot compile Windows 3.x programs with the Tiny, DOSX, Phar Lap, or NT memory models.

Windows applications usually consist of several source files. Always compile all the files in a Windows application with the same (preferably Large) memory model if possible, or explicitly declare a type for each pointer in a function prototype. If you are mixing near and far data references, make sure that all declarations match their corresponding definitions, or hard-to-find bugs can result.

For more information, see the section "Fine-Tuning with Mixed Model Programming" in Chapter 7, "Choosing a Memory Model."

Choosing entry and exit (prolog/epilog) code

C++ generates different types of entry code (the prolog) and exit code (the epilog) for functions in Windows applications. Different types of prolog/epilog code yield different instructions in the generated code and affect the size of the executable.

You can choose the type of entry/ exit code you need from the Windows Prolog/ Epilog subpage on the Build page of the IDDE's Project Settings dialog box, or with the -W command line option. See "-W Compile for Microsoft Windows" in Chapter 2, "Compiling Code," for details about the -W option and modifiers.

When you select the No Prolog/ Epilog option (-W-or -W0), the compiler does not generate any prolog or epilog code.

Note: For compatibility with Microsoft C and C++, Digital Mars C++ supports the extension __declspec(naked), which tells the compiler not to generate prolog/ epilog code for individual functions. For information see Chapter 3, "Digital Mars C++ Language Implementation."

When you select the Full Prolog/ Epilog for all far Functions option (-W or -W1), the compiler generates one type of prolog and epilog for all far functions.

When you select the Reduced Prolog/ Epilog for non-exported far Functions option (-W2), two types of prolog and epilog result. All exported far functions have the prolog and epilog shown for the Full Prolog/ Epilog option.

When you select the Smart Callbacks -Load DS from SS for far Functions option (-W3), the compiler compiles far functions with a smart prolog and epilog that loads the data segment from the stack segment.

For more information on Windows entry and exit code, see Chapters 7 and 19 in Programming Windows 3.1 by Charles Petzold (3rd Ed.).

Warning: Only use the "Smart Callbacks" option with applications in which the data segment is the same as the stack segment (DS== SS). Do not use it with DLLs.

When you select the Windows Protected Mode Executable option (-WA), the compiler generates a protected mode Windows application with callback functions marked with __export.

When you select the Windows Protected Mode DLL option (-WD), the compiler generates a protected mode Windows DLL with callback and exported functions marked with __export.

Recommendations for using the prolog/ epilog options

The following combinations of options are recommended: You should avoid these combinations of options: If your program defines a virtual member function that you need to call from a DLL: A virtual function is a callback function. Callback functions cannot be called directly from a program; they can only be called via a pointer returned by the Windows API function MakeProcInstance(). They also must be compiled with a Windows prolog/ epilog (-W), which is impractical for virtual functions. Therefore, do not reference static or global data in a virtual function meant to be called from a DLL, or else use near pointers.

Using the -Wb option

The -Wb option (assume DS != DGROUP) causes the compiler to issue a warning whenever a segment fixup is done to DGROUP. -Wb does not affect code generation; it only reports when a DGROUP segment fixup is generated. If your program is using an alternate data segment, a segment fixup to DGROUP could be a program bug.

In Large model code, DGROUP fixup can be triggered by constructs like this:

	static int x;
	static int *px = &x; /* DGROUP fixup */ 
	static char *p = "abc"; /* DGROUP fixup */ 
To eliminate the fixup, make the pointers near pointers:
	static int __near *px = (int __near *)&x;
	static char __near *p = "abc"; 
Using near pointers will result in DGROUP relative fixups, not DGROUP segment fixups. Alternatively, the pointers can be initialized at run time, as the compiler generates code that uses DS to initialize segment values.

Eliminating DGROUP segment fixups is useful for:

The -Wb option can be useful in tracking down hard to find bugs in the kinds of programs listed above.

Note: -Wb will cause the compiler to generate errors if you are using __loadds (because DS is reloaded from DGROUP), or if you are using -Wd (load DS from DGROUP) for Windows prologs.

Compatibility with Microsoft

The table below list Microsoft C Version 7 and Visual C++ options for generating prolog/ epilog code, and their Digital Mars C++ equivalents:
	Table 17-1 VC++ options and SC++ equivalents 

	Microsoft Digital Mars Result

	-Gw -W	Full Windows prolog/ epilog for far functions. 
	-Au -mwu Assume DS!= SS and load DS on entry to each function. 
	-Aw -mw Assume DS!= SS. 
	-GA -WA Optimized protected mode Windows application. 
	-GD -WD Optimized protected mode Windows DLL. 
	-GEa -Wa Load DS from AX. 
	-GEd -Wd Load DS from DGROUP. 
	-GEe -We Emit EXPDEF records for all exported functions. 
	-GEf -W-r Create prolog/ epilog code for all far functions. 
	-GEm -Wm Add inc BP/ dec BP to prolog/ epilog for far functions. 
	-GEr -W2V Real mode, reduced prolog for non-exported functions. 
	-GEs -Ws Load DS from SS. 
	-Gq -Wtxme Reduced prolog/ epilog. Equivalent to 
		-GW for MSC V6. (Digital Mars C/C++ 
		generates a full prolog/ epilog for 
		__far __export functions; MSC V6 
		does not.) 
	-GW -Wtxmev -D_ WINDOWS
		Reduced prolog/ epilog for real mode 
		Windows functions. 

Recompiling MFC 2.5 Code for Digital Mars C++

When recompiling MFC 2.5 code for Digital Mars C++, use the compiler options listed below for the libraries linked with:
	Table 17-2 Compiler options for MFC 2.5 compilations 
	Use these options... With these libraries...

	-WA -ml lafxcw 
	-WA -ml -D_DEBUG lafxcwd 
	-WD-r -ml -D_USRDLL lafxdw 
	-WD-r -ml -D_DEBUG -D_USRDLL lafxdwd 
	-WA-r -ml -D_DEBUG -D_AFXDLL smfc25d (for Windows application) 
	-WD-r -ml -D_DEBUG -D_AFXDLL smfc25d (for Windows DLL) 
	-WA-r -ml -D_AFXDLL smfc25 (for Windows application) 
	-WD-r -ml -D_AFXDLL smfc25 (for Windows DLL) 
	-WA-r -ml -D_DEBUG -D_AFXDLL smfco25d, mfco250d (for Windows application) 
	-WD-r -ml -D_DEBUG -D_AFXDLL smfco25d, mfco250d (for Windows DLL) 
	-WA-r -ml -D_AFXDLL smfco25 (for Windows application) 
	-WD-r -ml -D_AFXDLL smfco25 (for Windows DLL) 
	-WA-r -ml -D_AFXDLL smfcd25 (for Windows application) 
	-WD-r -ml -D_ AFXDLL smfcd25 (for Windows DLL) 
	-WA-r -ml -D_ DEBUG -D_ AFXDLL smfcd25d (for Windows application) 
	-WD-r -ml -D_ DEBUG -D_ AFXDLL smfcd25d (for Windows DLL) 
You might also need the following options: Note: The following libraries are only necessary if your application or DLL is using the DLL version of MFC and uses the OLE 2.0 and ODBC MFC classes: smfcd25[d].lib smfco25[d].lib mfco250[d].lib mfcd250[d].lib

For more information on the using the MFC 2.5 libraries, see the "read me" file SRC\MFC16\mfcsrc.txt in the distribution.

Win16 Definition File Directives

This section describes the directives you use in definition files for Win16 programs. For descriptions of these directives see Chapter 11, "Definition File Directives."


	DESCRIPTION 'descriptive line' 


	HEAPSIZE [number | MAXVAL] 




	STACKSIZE number 

	STUB 'filespec' 



	{   name [CLASS 'class'] 

	IMPORTS { [internal=] externalfile.func } 

	EXPORTS { extname [= intname] [@ number [RESIDENTNAME | NONAME]] 
		[parms] [NODATA] } 

DOS Programming Guidelines

This chapter describes how to use the compiler and linker to create DOS programs. For the compiler, it explains how to choose a memory model. For the linker, it describes switches and definition (.def) file syntax for linking DOS objects.

For information about:

What's in This Chapter

Choosing a Memory Model for DOS Programs

This section provides an overview of how to choose a memory model when compiling DOS programs.

For a detailed description of all the memory models and guidelines for when to use each one, see Chapter 7, "Choosing a Memory Model."

Real mode memory models

You can use these memory models for real mode DOS programs: Note: The Virtual memory model (-mv) and the Virtual Code Management system are no longer supported.

32-bit protected mode memory models

If your compilation will run in 32-bit protected mode, you can use either of these two models: The DOSX memory model (-mx) is compatible with the DOSX 386 DOS extender, available from Flashtek. The Phar Lap memory model (-mp) is compatible with the Phar Lap 32-bit DOS extender, available from Phar Lap.

Always try to compile all the files in a DOS application with the same memory model or explicitly declare a type for each pointer in a function prototype. If you are mixing near and far data references, make sure that all declarations match their corresponding definitions, or hard-to-find bugs can result.

For more information, see Chapter 7, "Choosing a Memory Model."

Definition File Directives

You use the following directives in definition files for DOS and DOSX programs. For information on these directives see Chapter 11, "Definition File Directives."
	STACKSIZE number 

Building and Using Windows DLLs

Dynamic linked libraries (DLLs) are a powerful part of Windows. They let multiple programs use a single copy of the same library at run-time, thus saving both memory and storage space. DLLs also improve performance since the program is no longer linking on the fly.

This chapter describes how to write and compile DLLs under Windows 3.1, and call a DLL function from another program. For information on compiling Win32 DLLs, see Chapter 16, "Win32 Programming Guidelines."

What's in This Chapter

Writing a DLL

This section describes the three steps for creating a Windows dynamic linked library (DLL): It also discusses how to avoid problems if you use standard library functions in your DLL.

Initializing the DLL

Your DLL must include an initialization function named LibMain(), which is called when the DLL is first used. You can perform any initialization the DLL needs in this routine.

Note: When you compile a DLL, the compiler automatically includes some start-up code in it. When you use the DLL, Windows calls that start-up code, which, in turn, calls your LibMain() function.

The following example shows a typical skeleton for LibMain():

	int FAR PASCAL LibMain(HANDLE hInstance,
		WORD wDataSeg, WORD wHeapSize, 
		LPSTR lpsxCmdLine )
	     * Perform any initialization here. 
	     * ...

	     * Unlock the DLL data segment if it's 
	     * moveable.
	    if (wHeapSize > 0)

	     * Return 1 if LibMain() is successful. 
	    return 1; 
LibMain() must be declared FAR PASCAL. It has four arguments, described in Table 19-1.
	Table 19-1 Arguments to LibMain() 

	Argument Description

	hinst The DLL's instance handle. 
	wDataSeg The value of the Data Segment (DS) register. 
	cbHeapSize The DLL's heap size, as set in its definition file. 
	lpszCmdLine Command line information. It's rarely used. 
The start-up code that the compiler adds to your DLL locks the data segment into memory. If you want the DLL's data segment to be re-locatable, you must unlock it with UnlockData().

If LibMain() successfully initializes your DLL, return 1. Otherwise, return zero, and Windows unloads the DLL from memory.

Using WEP when the DLL is terminated

The run-time library provides a default Windows Exit Procedure (WEP()), which is called when a DLL is terminated or Windows exits. This function calls static destructors, calls atexit(), and performs other typical cleanup services.

Before calling static destructors, WEP calls the function _WEP. The run-time library provides a _WEP that does nothing. If your DLL needs to perform any special cleanup (such things as setting values upon exit, or displaying messages) before Windows unloads it, include a _WEP termination function.

To allow your DLL to use the default WEP, write EXPORT WEP in the DLL's module definition file. For related information, see the section "Writing the definition file" later in this chapter.

Note: In C++ Version 6, users were required to provide their own WEP. A default WEP is now provided for compatibility with Microsoft C++. If you export your own WEP function, it automatically overrides the default WEP. However, your code is then responsible for all cleanup.

Declaring functions to export

Functions that other applications or libraries call must be declared as FAR, because they will not be in the same segment as the code that calls them. You may also want to declare them as PASCAL, because Pascal linkage is more efficient than C linkage. The following function definition is declared implicitly as residing in a far code segment, via the commonly used CALLBACK macro; just as functions shown in all previous examples, it can be called by other applications or libraries:
	int CALLBACK AddTwo(int x)
	    return (x + 2);
Read more about declaring functions to export in the section "Writing the definition file" later in this chapter.

The DS and SS registers

Unlike an application, a DLL does not have its own stack. Instead, the DLL uses the stack of the application that called it, so the data segment DS and stack segment SS registers are not the same. As in Large and Compact memory models, DS does not equal SS; this is the default situation for DLLs. (For more information, see Chapter 2, "Compiling Code.") The difference can cause problems if you use code that expects the DS and SS registers to be the same, and you compile your DLL in a memory model that uses near pointers (the Small and Medium memory models). For example, the standard libraries for the Small and Medium memory models expect the DS and SS registers to be the same.

For more information on memory models, see Chapter 7, "Choosing a Memory Model."

Suppose this function is in a DLL:

	void myDLLFunc()
	    char *from="hello there", to[25]; 
	    strcpy(to, from);
If you compile your program with the Small or Medium memory model, the compiler will raise an error. strcpy() expects its arguments to be near pointers, so it expects the pointers to be relative to the data segment. However, the pointers from and to are relative to the calling application's SS.

With most functions, you can avoid this problem two ways. One way is to compile the DLL with the Large memory model, which uses far pointers. The other way is to declare the variable you pass to be static or global; this stores it in the DLL's data segment. For example, the previous function would look like this:

	void myDLLFunc()
	    static char *from="hello there", to[25]; 
	    strcpy(to, from);
Some functions in the standard library will work correctly only in DLLs that are compiled with the Large memory model. For example, file I/ O functions such as fprintf() call several internal functions with their own local variables. When you compile your DLL in the Medium or Small memory model, the internal functions that the file I/ O functions call expect their pointer arguments to be relative to the data segment. But the arguments are relative to the calling application's stack segment. The application will cause a general protection fault because it looks for the data in the data segment instead of in the stack segment.

When you write a function that DLL functions may call, be sure your function's pointer arguments are far pointers.

Compiling a DLL

Once you've written a DLL, the next step is to compile it. Several considerations must be kept in mind, beginning with writing the module definition file.

Writing the definition file

You must write a module definition file for your DLL that lists all the functions that other applications or libraries may use. The following shows an example of a module definition file for a DLL that exports AddTwo() and uses the termination routine WEP():
		_AddTwo @1 
		_TimesTen @2
For more information on writing definition files for DLLs, see Chapter 16, "Win32 Programming Guidelines."

The LIBRARY statement is mandatory and specifies that this is a dynamic-linked library, not an application. The name of the library, MyDLL, follows the keyword. It is a good idea to use the same name in this statement and in the name of the .dll file.

The DESCRIPTION statement is optional and can contain a maximum of 128 characters for describing the DLL.

The EXETYPE statement is mandatory and specifies what kind of executable this is. For a Windows DLL, you must specify WINDOWS.

The DATA statement is mandatory and specifies the attributes for the DLL's data segment. You must include SINGLE. It specifies that there can be only one instance of a library in memory no matter how many applications access it. You can include other keywords to specify more attributes. For example, including MOVEABLE allows the memory manager to move the segment if necessary.

The HEAPSIZE statement is optional and specifies the minimum amount of space for the DLL's local heap. Use LocalAlloc() to allocate local memory. Since this DLL doesn't use the local heap, it specifies zero as the minimum heap size. Don't use a STACKSIZE statement, since a DLL doesn't have a stack. It uses the calling application's stack.

Functions that your DLL provides must be exported explicitly. There are two ways: the EXPORTS statement and the _exports keyword.

The EXPORTS statement lists the functions that other applications and libraries can call. If your DLL uses WEP(), you must list it here. All exported functions must follow the name mangling scheme as specified in the section "Name Mangling in Digital Mars C++" in Chapter 4, "Mixing Languages." Following the exported mangled name is an optional cardinal entry value. This cardinal is an index that allows for faster lookup times than searching by name allows. A cardinal is an integer proceeded by an @ and is separated from the function name by a space. Optionally, the exported name can be wrapped in double quotes, eliminating the need for intermediate spaces. The following shows the EXPORTS used both ways:

	EXPORTS ?TimesTwo@@ZCHH@Z @2 
	EXPORTS "?TimesTwo@@ZCHH@Z" @2 

Alternative export method

The other way to export functions is to use the _export keyword in your C or C++ function declaration statement. This keyword causes the compiler to notify the linker that this is an exported function and is typically used within Windows programs in place of the definition file's EXPORTS statement. With the EXPORTS statement, you had to contend with name mangling; the advantage of using the _export keyword is that you do not.

See "__export and __loadds" in Chapter 3, "Digital Mars C++ Language Implementation" for more information.

Consider the following example:

	int _export TimesTwo(int x)
	    return (x * 2);

Exporting C++ functions

If the DLL is written in C++, you must use the mangled name of its functions in the EXPORTS statement of the DLL's module definition file. To find the mangled name, you can do one of two things: link to create a map file for your DLL, and look for your functions in the section "Publics by Name"; or run the utility LIBUNRES with the -p switch on the .obj files that comprise your DLL. Both methods give you a listing of mangled names. For more information, see the section "Name Mangling in Digital Mars C++" in Chapter 4, "Mixing Languages." For information on LIBUNRES see Chapter 14, "Using Digital Mars C++ Utilities."

For example, suppose you have a C++ DLL named CPPDLL that has one module called cppdll.cpp, and a function called TimesTwo(). Compile the source module as follows:

	sc -c -WD cppdll.cpp 
Now run the LIBUNRES utility as follows:
	libunres -p cppdll.obj 
This will list all public symbols in cppdll.obj. One of them will be the mangled version of TimesTwo(), and might look something like this:
You can usually tell at first glance if this is the symbol you are looking for, but if in doubt, run the UNMANGLE utility on the mangled name.

Finally, edit your module definition file, cppdll. def, so that the EXPORTS statement contains the mangled name:

When you use the DLL with an application, be sure to use the function's mangled name in the IMPORTS statement of the application's module definition file. However, when you call the DLL function in the application, use the unmangled name and call it just as you would any other function. For example, to call TimesTwo() from an application, use this:
	a = TimesTwo(b); 

Building the DLL

When you compile a DLL in the IDDE, be sure to do the following: Assuming you have already provided source and a definition file. mydll. dll is the name of the DLL, two ways to build and link a DLL:
	sc -c -W -msw mydll.c link mydll,mydll.dll,,,mydll.def 
	sc -WD mydll.c
This last method launches the linker for you and assumes the .def file has the same name as the source file.

Prolog and epilog

Different types of prologs and epilogs are generated depending on the setting of the -W compiler option.

For information on choosing prolog and epilog code, see Chapter 16, "Win32 Programming Guidelines."

Using DLLs in an Application

Three ways that an executable can link and use the functions in a DLL are: This section describes how to use each of these methods.

Explicitly with an IMPORTS statement

To import a function explicitly when you link your application, use an IMPORTS statement in the application's module definition file to list the names of the DLL functions that it uses. This method is useful if you know what DLL functions the application needs when you link the application.

For example, if you have a DLL written in C called mydll. dll that contains AddTwo(), the IMPORTS section might look like this:

Notice the underscore before AddTwo; this results from C-style mangling for functions with C linkage. When specifying the DLL function to import, you must follow the name mangling conventions as specified in "Name Mangling in Digital Mars C++" in Chapter 4, "Mixing Languages."

If the DLL assigned a cardinal entry value to one of its exported functions, you can use that cardinal in the IMPORTS statement to help Windows find the function more quickly. If MyDLL assigned AddTwo() a cardinal entry value of 1, the IMPORTS statement would look like this:


Implicitly with an import library

If you want to import a function implicitly when you link an application, you can create an import library, then link it in with your application. An import library contains no code; it is a symbol table that the linker uses to define the locations of the DLL functions. This method is useful if when you link the application, you know what DLL functions it needs.

Create an import library with the IMPLIB command line utility. It takes two arguments: the name for the import library and the name of the DLL's module definition file. For example, this command line creates an import library for mydll.dll:

	implib mydll.lib mydll.def
IMPLIB finds a .DLL file that has the same name as its first argument and creates an import library for it. When you link your application, list the import library along with the other libraries that it uses.

Here is an example of a function that is linked implicitly:

	extern... FAR AddTwo();

		int x, y = 3; 
		x = AddTwo(y); 

Dynamically with LoadLibrary()

If you want to dynamically import a function when you run your application, load its DLL with LoadLibrary(). This method is useful if you won't know what functions the application needs until it's running. For example, each option in the Windows Control Panel is a DLL that the Windows Control Panel loads dynamically each time you run it. The Windows Control Panel doesn't know until it's running what options are available.

For example, to load mydll.dll and call the function AddTwo(), use this:

	FARPROC func; 
	int x, y = 3;
	hLib = LoadLibrary("MYDLL.DLL");
	if (hLib >= 32) { 
	    func = GetProcAddress(hLib, "_AddTwo");
	    if (func != (FARPROC) NULL) 
		x = (* func)(y);
In the previous example, when you call GetProcAddress(), Windows creates a Thunk layer and Thunk calls the function. In this case, the DLL function must have Pascal linkage in order to use the load library. See Programming Windows by Charles Petzold for more information on Thunk layers.

LoadLibrary() loads the library and returns a pointer to it. GetProcAddress() looks up the function by name in the library and returns a pointer to the function. The expression (* func)(y) dereferences the function pointer and calls AddTwo() with y as its argument. FreeLibrary() lets Windows know that you are no longer using the DLL and Windows can unload the library if nothing else is using it.

To look up the function by its ordinal entry value instead of by name, you need to change the call to GetProcAddress(). Suppose MyDLL assigned AddTwo() the number 1. Get the function's address with this statement:

	func = GetProcAddress(hLib, MAKEINTRESOURCE(1)); 

Using a C++ DLL with a C program

Avoid using a C++ DLL with a C application. Since C and C++ differ in how they pass parameters and handle function names, it is not easy to call a C++ function from a C program.

However, if you must, you can use a C++ DLL with a C program. The easiest way is to recompile the C application as a C++ source file, and to use the extern "C" construct to give the application's functions C linkage and use the extern "C++" construct to give the DLL's functions C++ linkage. For example, this code gives three application functions C linkage and one DLL function C++ linkage:

	extern "C" {
	    void old_app_function1(); 
	    void old_app_function2();
	    extern "C++" { 
		int cplusplus_DLL_func();
	    void old_ app_ function3();

DOSX Programming Guidelines

This chapter describes how to create 32-bit protected-mode DOS programs with a linear address space of 4 gigabytes (4GB) by compiling with either of two memory models. Programs compiled with these memory models can run on any IBM PS/2 or PC/AT computers or compatibles with an 80386, 80486, or Pentium CPU and can take advantage of all the features of the 80386 architecture.

For more information on the 32-bit protected mode memory models, see Choosing a Memory Model.

What's in This Chapter

DOS Extender Compatibility and Requirements

You can create 32-bit protected mode DOS programs with a linear address space of 4 gigabytes (GB). You can use either of these memory models: Note: Digital Mars C++ no longer supports the -z memory model for the ZPM 32-bit DOS extender.

This chapter discusses how to use the DOSX 386 DOS Extender. Information about the Phar Lap 386| DOS Extender is included where possible. For more information, contact Flashtek or Phar Lap.

Hardware requirements

Programs compiled with the DOSX 386 or Phar Lap 386 memory models run only on computers with an 80386, 80486, or Pentium CPU.

Hardware-sensitive items that can affect DOSX programs include the enabling hardware for the A20 line and 8259 interrupt controllers. If these are compatible with either their PC/ AT or IBM PS/ 2 counterparts, they should not affect your DOSX applications.

If the A20 enabling hardware is not PC/ AT compatible or IBM PS/ 2 compatible, an XMS extended memory manager (XMM) compatible with that computer may be required. (An extended memory manager is a program installed at boot time by inserting the appropriate line in the config. sys file.) If this is the case, the program will exit with the error message:

	Cannot enable the A20 line, XMS memory manager required 
The vast majority of 80386 equipped personal computers can run DOSX programs without the need for extended memory manager software. If you find a computer that displays this message, contact us so we can investigate the compatibility problem.

Extended memory is not required to run DOSX 386 programs. If extended memory is available, however, it will be added to the heap/ stack space. This means that DOSX 386 programs will run on 80386 computers that have no extended memory if there is sufficient conventional memory available.

The DOSX 386 memory model will not operate on computers equipped with an 80286 or 8088. If the startup code detects any processor other than an 80386 or 80486, it will exit with the error message:

	Fatal error, 80386 in real mode is required 

Software requirements

DOSX programs are compatible with IBM's vdisk.sys, Microsoft's ramdrive.sys, smartdrv.sys, himem.sys and other drivers that may or may not use extended memory. The DOSX extender is known to be compatible with these drivers: Note: Some extended memory managers, such as himem.sys, are not compatible with vdisk.sys. Installing both of those programs simultaneously results in no extended memory being available for other application programs, regardless of the amount of extended memory that is not actually being used.

When it starts up, a DOSX program allocates all the extended and conventional memory it needs. If there is an XMM available, the program uses it to allocate the extended memory and enable the A20 line. If there is no XMM, the program allocates the memory and enables the A20 line itself.

DOSX programs contain code to protect driver buffers in extended memory so that invalid pointers cannot write over them. You can access memory allocated to an extended memory RAM disk only by calling the RAM disk code.

The DOSX memory model is not compatible with drivers that leave the processor in V86 mode unless the driver is VCPI compatible, like Qualitas 386^ MAX version 5. 0 and higher, or DPMI compatible, like Microsoft Windows 3.0 and higher. If a DOSX program determines that the processor is in V86 mode and the software that switched it to that state is neither VCPI nor DPMI compatible, the DOSX program exits with the message:

	Fatal error, 80386 in real mode is required 

DOSX memory limitations

DOSX has the following memory limits: Note: If less than 1Mb of extended memory is available, Windows 3.0 in 386 enhanced mode does not reliably run protected mode programs except from the DOS prompt. In this instance you need to add memory or upgrade to Windows 3.1.

Building a DOSX Executable

To compile and link a DOSX program, perform the following steps:

Running a DOSX Program

DOSX lets you write code that uses all the features of the 80386. In particular: DOSX programs have a simple, built-in interface to DOS. All I/ O, such as printf(), write(), and getchar() calls, use the built-in DOS interface code transparently.

To the user, a DOSX program looks like a typical executable. It requires no special loaders or device drivers because everything it needs to run is contained within it. A DOSX program is only about 10KB larger than if it had used the Large memory model.

When it runs, a DOSX program places the processor in protected mode, allocates all available extended and conventional memory, and executes all instructions in protected mode. It returns to real mode only when it needs to interface with real mode code such as DOS, or hardware interrupt handlers.

Managing memory

When you run a DOSX program, it checks for extended memory management software in this order: 1. DPMI hosts, such as Windows 3.0 and higher 2. VCPI hosts, such as Qualitas 386^MAX version 5.0 and higher 3. XMS compatible devices, such as himem. sys 4. The BIOS function call INT 15 function 0x88 A DOSX program uses the first device it finds to allocate all the extended memory and enable the A20 line. If it cannot find any of the above devices, it looks for any driver that uses extended memory, such as ramdrive.sys, smartdrv.sys, or vdisk.sys. It allocates all unused extended memory.

If you are using extended memory software that isn't a DPMI host, the program also allocates all the unused memory and combines it with the extended memory in an area called the X memory space. Your program treats this block of memory as a large, single, continuous block. The extended memory that drivers have allocated is not part of this block, so errant pointers cannot overwrite it. The first megabyte of memory, including the video display buffer, is also not part of this block, and you can access it as you would in any other memory model.

Your program loads its code and static data into the bottom of the X memory space. The heap grows up from the top of static data towards the stack, and stack grows down from the top of the X memory space towards the heap.

Figure 20-1 X memory space 

	Static data
The stack can grow until it reaches the first 4KB boundary above the top of the heap. If the stack tries to grow beyond that, your program is aborted.

The default minimum size for a DOSX program's stack is 4096 bytes. You can change the minimum stack size with the =nnnn compiler option, or with the _stack global variable:

	unsigned int _stack = nnnn; 
This number is rounded up to the nearest 4KB boundary. Your program uses this number to prevent the heap from growing into the stack. Memory allocation functions such as malloc() and calloc() fail when the heap has grown to the largest size possible given the current minimum stack size.

Running on DPMI hosts in 386 enhanced mode

If you intend to handle interrupts such as 0x08 to 0x0F, 0x70 to 0x77, 0x1B, 0x1C, 0x23, or 0x24, or use the msm_signal function, all code, data, and stack that the interrupt handler might access must be locked. This prevents the interrupt handler from being swapped out to disk. You can lock the necessary memory with the function __x386_memlock. Use __x386_memunlock to unlock memory. These functions can be called even if the program is not running under DPMI, in which case they will always return success. You also need to lock the code, data, and stack segments when using the functions cerror_ open and cerror_ close.

Memory Protection

The X memory model uses the 80386 protection mechanisms to check for invalid pointers, detect stack overflow, and protect code from overwrites.

When you link a program for the DOSX memory model, OPTLINK adds code to your program to handle all interrupts except hardware interrupts. When the processor detects a fault, it issues an interrupt. The DOSX code then prints out diagnostic information and terminates the program. For example, if you use a null pointer, the output might be:

	possible illegal address 

	error code = 0000 

	cs = 002B	eip = 00000E6A
	ss = 003B	esp = 000B6FF0 
	ds = 0033	ebp = 000B6FFC
	es = 0033	eax = 00000000 
	fs = 0000	ebx = 0000FF00
	gs = 0000	ecx = 00000000 
	eflags = 00013246 edx = 00002B74
			esi = 000000D4 
			edi = 00002E55 
	absolute start address of DGROUP 10000000
	available conventional memory 00025000 to 0009D000
	available extended memory 001C0000 to 00200000 
	X memory space located at 10000000 to 100B7000 
If you want to execute an interrupt, return to real mode by calling int86() or int86x() before executing the interrupt.

Differences in the 32-Bit Memory Models

When you use a 32-bit memory model, there are no far calls or far pointers. Pointers contain only a 32-bit offset. Arrays are limited by the amount of available memory, up to 4GB. The amount of data that functions like read() and write() can transfer is limited by the amount of available disk space, up to 4GB.

Differences affecting library functions

These standard library functions behave differently in 32-bit memory models. For more information see the function's description in the Run-Time Library Reference.
	bdos() bdosx() _chkstack() 
	getDS() int86() int86x() 
	intdos() intdosx() peek() 
	poke() segread() write() 

Standard library functions not supported with DOSX

You cannot use the following functions in programs compiled with the DOSX memory model:
	bios_disk() dos_abs_disk_read() 
	dos_abs_disk_write() farmalloc() 
	farcalloc() farcoreleft() 
	farfree() farrealloc 

Special functions for the DOSX memory model

There are a number of special functions provided for use with the DOSX memory model. For more information see the function descriptions in the Run-Time Library Reference.

Special Topics

This section describes the following 32-bit DOS programming issues:

Accessing the video buffer with DOSX

Access video memory with a 32-bit protected mode pointer, as illustrated in the examples below: The sample code below allocates a new selector with a base address that points to 0x0B8000, which is in the video buffer for a color monitor. This calls a function that returns a far pointer in DX: EAX. If the selector is allocated successfully, the offset in EAX will be zero. If not, EAX will hold the requested address and DX will hold a base address of absolute zero (equivalent to _x386_ zero_ base_ selector in the next example). Note that all the code fragments in this section are part of the same program. The following example defines the variables used in the other examples as well:
	include macros.asm
	extrn _x386_zero_base_selector: word, _disp_base:word 

	extrn _x386_mk_protected_ptr:dword, 
	extrn _disp_open:near 

	public _main
	_main proc near 
	    push 0b8000h
	     call _x386_mk_protected_ptr 
		; returns pointer in DX: EAX 
	    mov es,dx	; should reference a video selector
	     mov byte ptr es:[0],'X' 
		; places an X on the screen 
The next block of sample code writes to the screen using a selector with an address of absolute zero. This selector is stored in a global variable and is available for applications to use as required:
	mov es,_x386_zero_base_selector	; load the selector 
	mov byte ptr es:[0b8002h],'Y'	; places a Y on the screen 
The next block of sample code writes to the screen using near pointers based on the default selector normally found in DS. Your code must first call the function _x386_get_abs_address to determine the base address of DS (DGROUP). Since this value is always greater than 1MB, you need to use a negative offset to access the video buffer. The segment limits DOSX sets on DS allow the use of negative offsets, as illustrated by the following:
	push ds		; push far pointer ds:0
	push 0		; returns address in EAX 
	call _x386_get_abs_address ;returns address in EAX 

	neg eax		; DS:EAX now points to zero
	mov byte ptr ds:[eax+0b8004H],'Z' 
			; places a Z on the screen 
This last code fragment calls the _disp_open run-time library function (from the Display package) to identify the video card and create a selector that points to the video buffer. When you use this method to access video memory, you should inspect the value in _disp_open to make sure it is non-zero. If _disp_open fails to identify the video card and uses the BIOS to access the video buffer, _disp_base will equal zero.
	call _disp_open	; initialize _disp_base
	mov  es,_disp_base 
	mov byte ptr es:[6],'T'	; places a 'T' on the screen 

	mov ah,4ch
	int 21h		; terminates sample program 
	_main endp

Interfacing to real mode functions

To enclose real mode code, use the begcode_16 and endcode_16 macros found in x386mac. asm. Real mode code must return with a far return value. To call the real mode procedure:
	mov AX,250eh
	mov EBX,	; seg:offset of real mode procedure 
	mov ECX,	; number of words to copy from
			; protected stack to real mode stack 
	int 21h 
ECX must not be greater than 63 (two-byte) words; therefore there is a maximum of 126 bytes that can be transferred. Making ECX zero will make the call slightly faster and preserve real mode stack space. The real mode procedure receives control with a stack of about 300 bytes in size. The dword return address is immediately placed on the stack and any copied parameters are placed above that. All general registers are preserved when a protected mode function calls a real mode function, as well as when the real mode function returns control to the protected mode function. The real mode function receives ds = cs; all other segment registers are undefined. Upon return to protected mode, all general registers are as they were left by the real mode code. Segment registers are as they were prior to the int 21h call. The stack will be unchanged even if the real mode function changed the values of the parameters on the real mode stack; the real mode function cannot return values on the stack.

Note that it is difficult to get the real mode segment value, since there are no segment fixups in the protected mode code. To get the segment value of the real mode code:

	mov ax,2509h	;get system segments and selectors
	int 21h 
All general registers will be destroyed and filled with various real and protected mode segments and selectors. The value of interest is the real mode code segment returned in BX. The following program demonstrates the use of a real mode procedure:
	include macros.asm
	include x386mac.asm 

	begcode_16	; define start of real mode
			; code segment 
	real_proc proc far 
	mov EAX,[ESP+4]	; mov first parameter into EAX
	real_proc endp 

	endcode_16	;end of real mode code segment 

	public _main 
	_main proc near
	push 12345678h	; parameter to send to real 
			; mode code
	mov AX,2509h	; get system segments and 
			; selectors
	int 21h		; get real mode segment in BX 
	rol EBX,16	; put segment in high word of EBX 
	mov BX, offset real_proc	; EBX now = cs:ip
	mov ECX,2	;copy two words or one dword 
	mov AX,250eh
	int 21h		;call real mode procedure 

	; make a GP fault to examine registers:
	push CS 
	pop SS
	; illegal value in SS will terminate program 
	; and dump registers to screen. EAX should
	; equal 12345678h 

	_main endp
While function call 250eh is similar to Pharlap's function 250eh, it is not identical; function 2509h as used above is totally different from Pharlap's version, and Pharlap function 2510h which allows the caller to specify all registers is not supported in DOSX. Other relevant Pharlap-like function calls which DOSX supports are as follows:
Function Description

2502-2507 Deals with real and protected mode interrupt 
2508 Gets base address of selector 
250c Gets hardware interrupt vectors 
250d Gets real mode data buffer address and real mode 
	call back device address 
2511 Executes interrupt in real mode 
252b Subfunctions 5 and 6; locks and unlocks virtual 
	memory (useful under DPMI) 
The real mode data buffer (function 250d) is shared with the DOS extender and is overwritten during disk i/ o or screen i/ o, so it should only be used for temporary storage. The normal size of this buffer is 16K bytes, but it may be smaller if there is insufficient conventional memory. Keep in mind that real mode libraries will have some difficulty accessing protected mode data.

Accessing real mode memory from protected mode

The example program which shows how to access real mode memory from protected mode. In this example, 45K bytes of static data are allocated in the real mode code segment; this is roughly the maximum that can be used.
	include macros.asm
	include x386mac.asm 
	This program demonstrates how to allocate static 
	real mode memory, and access it both from real
	mode code and 32-bit protected mode code. The 
	function main places the number 12345678h in the
	real mode array "real_data". main then calls the 
	real mode function "real_proc", which retrieves
	that number and returns to protected mode with 
	that number in EAX. main then causes a GP fault
	so that the registers can be examined. 
	begcode_16	; define start of real mode code 
			; segment 

	real_data db 45000 dup (0) ; real mode data 

	real_proc proc far	; procedure to call from
				; protected mode 
	assume DS:__X386_CODESEG_16 ; name of real mode code segment 
	mov EAX, dword ptr DS:real_data[10000] ; mov the dummy data into EAX 
	real_proc endp 

	endcode_16	; end of real mode code segment 
	begdata		; protected mode data segment
	extrn __x386_zero_base_selector:word 
			; data selector enddata 

	begcode		; protected mode code segment 
	public _main
	_main proc near 
	mov AX,2509h	; get system segments and
			; selectors 
	int 21h		; get real mode segment in BX 

	There are no fixups done on 32-bit code, so you 
	cannot use segment names as immediate values;
	you must use function 2509h. This function 
	destroys all registers, filling each 32-bit
	register with two segments or two selectors. 

	push EBX	; save real mode segment which is in BX 

	mov ES,__x386_zero_base_selector
	; note that the above selector is not hardwired, 
	; but is available through the public variable
	; as shown above. It has a base of 0, 4GB limit 

	movzx EBX,BX	; zero upper word
	shl EBX,4	; convert segment to absolute 
			; address
	assume ES: nothing 
	mov dword ptr ES: real_data[EBX+10000],12345678h
	; above instruction puts dummy data in 
	; real_data[ 10000], the selector in ES
	; points to zero. EBX gives the offset of the 
	; start of the real mode segment. Ensure that ES
	; does not have any "assumes" on it 

	pop EBX	; restore real mode segment in BX
	rol EBX,16 
	mov BX, offset real_proc ; EBX now = cs:ip of
				 ; real_proc 
	xor ECX,ECX		 ; copy zero parameters on stack
	mov AX, 250eh 
	int 21h			; call real_proc 

	; make a general protection fault to examine
	; registers 

	push CS
	pop SS 
	; illegal value in ss will terminate program and
	; dump registers to screen. 
	;eax should have the value 12345678h 

	_main endp

Accessing the first megabyte of memory

To access any part of the first 1Mb of memory in a DOSX program, you need to perform one of these operations: Both these methods are illustrated in the sample code that follows. Which method you choose depends on the situation: for one-time access, far pointers would typically be easiest; for multiple accesses, near pointers might be best.
	include macros.asm 

	begdata extrn __x386_zero_base_selector:word 
		; data selector for using far pointers
	extrn __x386_get_abs_address:dword 
		; function pointer for using near pointers 


	public _main 
	_main proc near 

	The far pointer method: A selector is stored in 
	a global variable as shown in the extrn
	definition above. This selector has a base 
	address of zero and a 4 Gb address limit. It is
	primarily useful for addressing the first 1Mb 
	since everything above the first 1Mb is remapped
	with the paging mechanism and thus is not 
	readily usable. This selector can be used to
	access the video buffer as follows: 

	; place an X in the upper left corner
	; of a color monitor 

	mov ES,__x386_zero_base_selector
	mov byte ptr ES:[ 0b8000h], 'X' 

	The segment wrap method: This method is a bit 
	more complex initially, but the use of near
	pointers is a great advantage in some cases. It 
	relies on the fact that the 80386 "wraps" around
	the 4 Gb address just like an 8088 wraps around 
	the 1 mbyte address. The segment limits have been
	set up such that the default DS selector cannot access
	addresses low enough to corrupt the code; however,
	there is no limit on upper 
	addresses other than generating page faults if you
	try to read or write to empty space. To make 
	the segment wrap work, you first have to find the base
	address of DGROUP. This varies 
	depending on whether the system is DPMI or non DPMI.
	The example below determines this at run 
	time so it will work in either situation.

	push DS
	push 0		; put far pointer to start of DGROUP 
			; on stack
	call dword ptr __x386_get_abs_address 
			; returns address in EAX
	add ESP, 8	; pop far pointer from stack 
			; then subtract address in EAX from
			; address in DS to get to zero 

	neg EAX 

	; now DS:[ EAX] points to absolute zero. Add the
	; value required to EAX to access an address in 
	; the first 1Mb. 

	; To place X in upper right corner of a
	; color monitor: 

	mov byte ptr DS:[0b809Eh + EAX],'X' 


	_main endp

Miscellaneous Tips

The following are useful tips on 32-bit DOS applications that address some commonly asked questions:

Using protected mode pointers

You can use a protected mode pointer to access video memory (or other memory in the first 1Mb not assigned to your program. Memory above the 1Mb boundary not assigned to your program is protected, except under DMIP where protection is less strict. All linear addresses above the 1Mb boundary point to memory that is owned by the application. Extended memory that is not owned by the application is simply not mapped, and does not exist from the application's point of view.

Accessing memory mapped i/o devices

DOSX code cannot access memory mapped i/ o devices above the 1Mb boundary.

DOSX compatibility with Phar Lap

DOSX does emulate some Phar Lap function calls. However, in some cases the emulation is not exact, and is only compatible in the context in which the DOSX library uses the function calls. There are also many Phar Lap functions that are not implemented. If you use the library functions, most compatibility issues should not be a concern. Assembly language programmers might have problems with function calls like 0x 2516 (not implemented in DOSX) or 0x2509 (implemented but incompatible with the corresponding Phar Lap function).

Functions that are not implemented will return EAX = 0xA5A5A5A5, with the carry flag set; this is the Phar Lap convention for handling unimplemented functions.

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 Chapter 4, "Mixing Languages" and Chapter 2, "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 

	Conventionl 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
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:
Copyright © 1995-2001 Digital Mars. All Rights Reserved.