www.digitalmars.com [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:

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 |
		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: 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."
	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:

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."
	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

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)
		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: 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: 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

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;
	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:

Optimizing handle code

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

Tips for Using Handle Pointers Efficiently

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

Porting code with handles

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

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

	#define NO_HANDLE 1
	#include  
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.