[Home]
[Search]
[CTG]
[RTL]
[IDDE]
Last update Dec 8, 2002
Win32 Programming Guidelines
This chapter describes how to use the compiler to create
Win32 and Win32s applications. For information about:
- Creating 16-bit Windows programs, see Chapter 17,
"Win16 Programming Guidelines."
- Using the IDDE to create applications, see
The User's Guide and Reference.
- The Win32s API, see the included Microsoft documentation.
What's in This Chapter
- How creating 32-bit Windows applications differs from
creating 16-bit Windows applications.
- The distinctions between 32-and 16-bit graphical
environments and Digital Mars' support for Win32, Win32s
and development tools.
- How to compile and link various Win32 applications,
including applications that use MFC.
- How to compile and link Win32s applications.
- How to recompile and relink 16-bit applications to be 32-bit
Windows applications.
Some Basic Information
The following will help you understand the information in this
chapter.
- Windows 3.1 is a 16-bit graphical environment that runs
on DOS, which is a 16-bit operating system. For
simplicity, Windows 3.1 (and 3.0) will be referred to as
Win16.
- Windows 95 and Windows NT (Windows New
Technology) are 32-bit operating systems. For simplicity,
Windows 95 and Windows NT will be referred to as
Win32.
- Win32s is an NT emulator, or operating system extension,
that allows you to run Win32 applications on a Win16
host.
- 32-bit applications for Windows are referred to as Win32
applications. 16-bit applications for Windows are referred
to as Win16 applications.
- The Win32 Applications Programming Interface (API), the
graphical environment for Windows 95 and NT, is very
similar to the Windows 3 graphical environment.
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:
- The ability to run 32-bit programs under Windows 3.1.
- The ability to manipulate data and perform calculations in
32-bit mode, which improves the performance of
applications that are data-, memory-or calculation
intensive, such as spreadsheets, simulation packages,
desktop publishing and CAD packages.
- Features previously unavailable in Windows 3.1, such as
sparse memory.
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:
- Complete window interface
- All graphics functions (GDI)
- Object linking and embedding (OLE), version 1.0
- Memory-mapped files (backed by disk image)
- Network support, such as Netbios and named pipes
- Common dialogs
- Named shared memory
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 compiler generates
Microsoft OMF object files. OPTLINK
does not use Microsoft COFF files.
- In order to compile with the correct startup code, Win32
and Win32s executables need to contain a WinMain()
entry point. This causes the compiler to generate a
reference to the _acrtused startup function.
- Use a module definition file to define explicitly as many
criteria as possible. To establish the proper executable for
NT, for example, place EXETYPE NT in your module
definition file, and use the SUBSYSTEM[ WINDOWS]
keyword. For further information on Win32 .def files,
see Chapter 11, "Definition File Directives."
- The -mn switch is the memory model switch for Win32
applications available from the command line interface.
This is the 32-bit flat model, essentially a "small model"
with 4GB addressing.
- Creating a resource file for a Win32 application is no
different from creating one for a Win16 application. The
resource compiler takes a resource definition file and
produces a 32-bit .res file. Having 16-bit compatibility
with the .res files helps you build Win32 applications
from Win16 applications.
- When creating Win32 applications, OPTLINK provides
transparent linking and binding of .res files to an .exe
file. 16-bit .res files are converted to 32-bit during the
link process.
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:
- Remove any references to #pragma startaddress.
- Provide a reference to DllMain().
- If the entry point routine was not DllMain(), write a
new DllMain() stub that calls the entry point routine
upon process_attach().
- If you need to write code that is called prior to static
construction, check the source for the run-time library to
determine what calls are necessary to initialize the library.
Then use #pragma startaddress to specify the entry
point, and call the initialization routines as needed.
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 |
PROCESSTERM | THREADINIT |
THREADTERM ]
DESCRIPTION 'descriptive line'
EXETYPE NT
HEAPSIZE [number, commit]
STACKSIZE [number, commit]
STUB 'filespec'
SUBSYSTEM [ NATIVE | WINDOWS | CONSOLE | POSIX ]
VERSION number[. number]
CODE [READ] [WRITE] [EXECUTE ] [SHARED]
DATA [READ] [WRITE] [EXECUTE] [SHARED]
SEGMENTS
{ name [CLASS 'class']
[READ] [WRITE]
[EXECUTE] [SHARED]
}
IMPORTS { [internal=] externalfile. func }
EXPORTS { parms [=intname] [@number [NONAME]]
[CONSTANT]}
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:
- In order for Win32 to interpret handles properly, declare
all handles as HANDLE, HINSTANCE, or similarly. Win32
will not properly interpret handles that use int or WORD.
The sample program ZOOM illustrates how to declare
handles correctly.
- When you compile with the -mn or -mf memory models,
the following keywords are ignored: __far, __huge,
__interrupt, __loadds, and __handle.
- In all Windows NT code, SS==DS==ES at all times;
otherwise the NT API functions themselves can fail. When
using the -mn or -mf memory models, SS==DS==ES
automatically for all functions, including run-time
library functions.
- Some Win16 API functions are not supported by Win32.
For example, replace calls to MoveTo() with
MoveToEx() calls.
- Replace all machine-specific code with non-machine
specific C or C++ functions to make your code as portable
as possible. For example, Use time() and
localtime() instead of calling int86().
- For message passing and other functions that use
wParam, declare parameters with WPARAM type. This is
especially true for message passing between windows,
such as with SendMessage(). Using the WPARAM type
allows the parameter to be interpreted as a 16-bit value
for Win16 and 32-bit value for Win32. Functions declared
as lParam are dwords in both implementations.
- Win32s executables that call 16-bit DLLs need to use the
Universal Thunk mechanism to communicate.
- For mixed model programs, perform these steps:
- Select the Large memory model.
- Rebuild the project as a Win16 application.
- If the project builds and runs correctly, convert it to
Win32, but first make any of the adjustments previously
mentioned.
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:
- Creating Windows DLLs, see Chapter 19, "Building and
Using Windows DLLs."
- Using the IDDE to create Windows applications and DLLs,
see The User's Guide and Reference.
- Win32s and Win32 programming, see Chapter 16, "Win32
Programming Guidelines."
- Using the WINIO library, see the Run-Time Library
Reference.
What's in This Chapter
- Choosing a memory model and prolog/ epilog code for a
Windows program.
- Recompiling MFC 2.5 code for Digital Mars C++.
- SC and CL options for Windows programming.
- Definition file directives for Win16 programs.
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:
- Small
- Medium
- Compact
- Large
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:
- To get a program working, just use -W.
- For maximum program speed and compactness, use -WA
or -WD.
- When you are using -WA or -WD and want to debug the
resulting executable, use the m (generate INC BP / DEC
BP to mark far stack frames) modifier also. This enables
many debuggers (including the Digital Mars debuggers) to
distinguish between near and far call frames.
- Use the -2 (optimize for 80286 CPU) compiler option in
combination with -W1 when compiling applications or
DLLs that will run in protected mode only. You can also
consider using -3 or -4, provided you specify that the
application will only run in enhanced mode.
You should avoid these combinations of options:
- Don't use -W3 or -WA for code that will be part of a DLL.
- Don't use -Ws (smart callbacks) for code that will be part
of a DLL.
- When using -W2, -WA, or -WD, make sure you mark all
callback functions and exported functions as __export.
- Don't use -WA or -WD on code to be run in Windows real
mode.
If your program defines a virtual member function that you need to
call from a DLL:
- If _loadds (or -mu) is not used for the function, DS will
probably not have the correct value in it when the
function is called.
- If _loadds (or -mu) is used, DS will have the correct
value in it as long as there is only one instance of the
program running. A second instance will have DS set to
the value for the first instance.
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:
- Special purpose code, where the data segment needs to
be relocatable.
- Embedded systems, where the data segment will be
initialized from a ROM-based image.
- 16-bit Windows DLLs, where the DLL was made to be
multi-instanced by creating multiple copies of DGROUP at
run time.
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:
- Use the -gf compiler option if you are linking with these
libraries: smfc25d. lib and mfc250d. lib. In general,
use -gf when using the DLL form of MFC.
- Use the -g compiler option if you are linking with these
libraries: lafxcwd. lib and lafcdwd. lib.
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."
NAME name[ WINDOWAPI| WINDOWCOMPAT| NOTWINDOWCOMPAT ] [ NEWFILES ]
LIBRARY name [ INITGLOBAL | INITINSTANCE ]
DESCRIPTION 'descriptive line'
EXETYPE [WINDOWS 3.00 | WINDOWS 3.10 | DOS4 | DOS | UNKNOWN]
HEAPSIZE [number | MAXVAL]
NEWFILES
PROTMODE
REALMODE
STACKSIZE number
STUB 'filespec'
CODE [PRELOAD | LOADONCALL] [EXECUTEONLY | EXECUTEREAD]
[MOVEABLE | FIXED ] [IOPL | NOIOPL]
[CONFORMING | NONCONFORMING] [DISCARDABLE | NONDISCARDABLE]
[SHARED | NONSHARED]
DATA [NONE | SINGLE | MULTIPLE] [PRELOAD | LOADONCALL]
[MOVEABLE | FIXED ] [READONLY | READWRITE]
[IOPL | NOIOPL] [SHARED | NONSHARED]
SEGMENTS
{ name [CLASS 'class']
[PRELOAD | LOADONCALL] [EXECUTEONLY | EXECUTEREAD]
[MOVEABLE | FIXED ] [READONLY | READWRITE]
[DISCARDABLE | NONDISCARDABLE] [IOPL | NOIOPL]
[NONCONFORMING | CONFORMING] [SHARED | NONSHARED] }
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:
- Using the IDDE to create DOS applications, see the User's
Guide and Reference.
- Creating WINIO applications, see the Run-Time Library
Reference.
- Creating Windows 3. 1 applications and DLLs, see Chapter
17, "Win16 Programming Guidelines."
- Win32 and Win32s programming, see Chapter 16, "Win32
Programming Guidelines."
What's in This Chapter
- An overview of how to choose a memory model for a
DOS program.
- Definition file directives for DOS programs.
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:
- Tiny (-mt compiler option)
- Small (-ms compiler option)
- Medium (-mm compiler option)
- Compact (-mc compiler option)
- Large (-ml compiler option)
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:
- DOSX (-mx compiler option)
- Phar Lap (-mp compiler option)
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."
EXETYPE [DOSX | DOS]
REALMODE
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
- How to initialize a DLL.
- How to use WEP.
- How to export functions.
- Compiling a DLL.
- Using a DLL in an application.
- Using a C++ DLL in a C application.
Writing a DLL
This section describes the three steps for creating a Windows
dynamic linked library (DLL):
- Writing the source code
- Writing the definition file
- Compiling the 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)
UnlockData(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():
LIBRARY MyDLL
DESCRIPTION 'MyDLL --The DLL I Wrote'
EXETYPE WINDOWS
CODE MOVEABLE DISCARDABLE
DATA SINGLE MOVEABLE
HEAPSIZE 0
EXPORTS
_AddTwo @1
_TimesTen @2
WEP @3 RESIDENTNAME
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
or
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:
?TimesTwo@@ZCHH@Z
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:
EXPORTS ?TimesTwo@@ZCHH@Z
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:
- Select Windows DLL in the Projects Options dialog box.
- Turn off the SS Equals DS option in the Memory Model
Options dialog box, to specify that the data segment and
stack segment are different.
When you compile a DLL at the command line, be sure to do the
following:
- Append w to your -m memory model option to specify
that the data segment and stack segment are different.
- Use the -W switch to specify that this is a Windows
executable.
- Use the -c switch to compile the DLL, but not link it.
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
and
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:
- Explicitly, by using the IMPORTS statement in the .def
file
- Implicitly, by linking an import library
- Explicitly, by dynamically loading the DLL with
LoadLibrary() and linking to the function with
GetProcAddress()
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:
IMPORTS MYDLL._AddTwo
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:
IMPORTS _AddTwo= MYDLL.1
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:
HINSTANCE hLib;
FARPROC func;
int x, y = 3;
hLib = LoadLibrary("MYDLL.DLL");
if (hLib >= 32) {
func = GetProcAddress(hLib, "_AddTwo");
if (func != (FARPROC) NULL)
x = (* func)(y);
}
FreeLibrary(hLib);
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();
};
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
- How handle pointers work.
- The handle format.
- Declaring handle pointers.
- Creating handles to dynamically allocated memory.
- Dereferencing handles.
- Tips on porting and debugging code that contains
handles.
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:
- It is built into the compilers, so you
don't need to call functions to access handles, lock or
unlock pages, or initialize the virtual memory system.
- It uses expanded memory if available and is compatible
with LIMS EMS versions 3.2 and 4.0.
- It lets you easily port code to operating systems with
virtual memory (such as UNIX and OS/2). The program
runs as efficiently as if it used conventional memory.
- It works with both C and C++ and is a compatible
extension to ANSI C.
- It uses the same code in the DOSX and Phar Lap memory
models. The compiler treats handle pointers as far
pointers in these models.
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;
func(h);
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:
- The handle's value might have changed.
- Your code dereferenced another handle in the interim.
Referencing a handle may cause another handle's page to
be swapped out.
- You called a function. Since a function might dereference
another handle, the converted handle's page might be
swapped out.
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:
- Use handles for data structures that your program
accesses infrequently. Your program will read and write
memory less often and will contain less code for
dereferencing.
- Choose handle pointers for data structures that emphasize
fast access over those that emphasize compactness.
- Keep related data structures on the same page by
allocating or declaring them together.
- Use expanded memory conventionally (for example, via
the EMM library routines) in the same program where you
use handles. But if you don't keep the two uses
independent, the results could be unpredictable.
- Perform a search over a large database by trying to put
the access structures in conventional memory and the
data in handle space. Look-up is fast and nothing is read
from disk until the search data is found.
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
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:
- Beware of having a stray pointer into a page that has
been swapped out of memory. This problem rarely
exhibits symptoms.
- Don't use more than four dereferenced handles at a time.
This problem shows up only if they fall on different
logical pages.
- Beware of function calls that could dereference handles.
- In converting code from using malloc() to
handle_malloc, be sure to convert
(char *) malloc(x) to
(char __handle *) handle_malloc(x). Using
(char *) handle_malloc(x) dereferences the
handle and stores a far pointer in h.
Here are some techniques to deal with the problems outlined above:
- If your program uses the dynamic allocation functions in
handle. h, define NO_HANDLE to be 1 so the functions
don't use handles. If you no longer have problems after
redefining NO_HANDLE, you can be sure that handles
caused your bugs.
- Encapsulate data structures that use handles in C++
classes. This confines the code that dereferences handles
to a few places and isolates handle problems to a class
definition.
- Look carefully for simultaneous accesses to logical pages.
Make sure you don't dereference more than four handles
at a time.
- Program defensively. Assume that all function calls
invalidate previously dereferenced handles.
Copyright © 1995-2001 Digital Mars. All Rights Reserved.