Note: Win32s applications built with C++ Version 7 will only run on version 1.15 and above of Win32s.
Note: To developers using NT: OPTLINK (the linker) does not accept COFF resource files, but does accept standard Microsoft resources files.
-mn -WDTo 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().
To compile console applications, use the -mn compiler option, and specify the SUBSYSTEM CONSOLE directive in the .def file.
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.
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]}
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.
For information about:
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."
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.
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:
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.
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.
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:
For more information on the using the MFC 2.5 libraries, see the "read me" file SRC\MFC16\mfcsrc.txt in the distribution.
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] }
For information about:
For a detailed description of all the memory models and guidelines for when to use each one, see Chapter 7, "Choosing a Memory Model."
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."
EXETYPE [DOSX | DOS] REALMODE STACKSIZE number
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."
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.
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.
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.
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.
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 RESIDENTNAMEFor 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 @2or
EXPORTS "?TimesTwo@@ZCHH@Z" @2
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); }
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.cppNow run the LIBUNRES utility as follows:
libunres -p cppdll.objThis 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@ZYou 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@ZWhen 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);
sc -c -W -msw mydll.c link mydll,mydll.dll,,,mydll.defand
sc -WD mydll.cThis last method launches the linker for you and assumes the .def file has the same name as the source file.
For information on choosing prolog and epilog code, see Chapter 16, "Win32 Programming Guidelines."
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._AddTwoNotice 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
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.defIMPLIB 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);
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));
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(); };
For more information on the 32-bit protected mode memory models, see Choosing a Memory Model.
This chapter discusses how to use the DOSX 386 DOS Extender. Information about the Phar Lap 386| DOS Extender is included where possible. For more information, contact Flashtek or Phar Lap.
Hardware-sensitive items that can affect DOSX programs include the enabling hardware for the A20 line and 8259 interrupt controllers. If these are compatible with either their PC/ AT or IBM PS/ 2 counterparts, they should not affect your DOSX applications.
If the A20 enabling hardware is not PC/ AT compatible or IBM PS/ 2 compatible, an XMS extended memory manager (XMM) compatible with that computer may be required. (An extended memory manager is a program installed at boot time by inserting the appropriate line in the config. sys file.) If this is the case, the program will exit with the error message:
Cannot enable the A20 line, XMS memory manager requiredThe vast majority of 80386 equipped personal computers can run DOSX programs without the need for extended memory manager software. If you find a computer that displays this message, contact us so we can investigate the compatibility problem.
Extended memory is not required to run DOSX 386 programs. If extended memory is available, however, it will be added to the heap/ stack space. This means that DOSX 386 programs will run on 80386 computers that have no extended memory if there is sufficient conventional memory available.
The DOSX 386 memory model will not operate on computers equipped with an 80286 or 8088. If the startup code detects any processor other than an 80386 or 80486, it will exit with the error message:
Fatal error, 80386 in real mode is required
When it starts up, a DOSX program allocates all the extended and conventional memory it needs. If there is an XMM available, the program uses it to allocate the extended memory and enable the A20 line. If there is no XMM, the program allocates the memory and enables the A20 line itself.
DOSX programs contain code to protect driver buffers in extended memory so that invalid pointers cannot write over them. You can access memory allocated to an extended memory RAM disk only by calling the RAM disk code.
The DOSX memory model is not compatible with drivers that leave the processor in V86 mode unless the driver is VCPI compatible, like Qualitas 386^ MAX version 5. 0 and higher, or DPMI compatible, like Microsoft Windows 3.0 and higher. If a DOSX program determines that the processor is in V86 mode and the software that switched it to that state is neither VCPI nor DPMI compatible, the DOSX program exits with the message:
Fatal error, 80386 in real mode is required
sc -mx test1.obj test2.obj -otest.exeTo perform the link step manually, you need to link the DOSX startup code in cx.obj. You must link with OPTLINK. For example:
optlink \sc\lib\cx+test1+test2,tmp;
To the user, a DOSX program looks like a typical executable. It requires no special loaders or device drivers because everything it needs to run is contained within it. A DOSX program is only about 10KB larger than if it had used the Large memory model.
When it runs, a DOSX program places the processor in protected mode, allocates all available extended and conventional memory, and executes all instructions in protected mode. It returns to real mode only when it needs to interface with real mode code such as DOS, or hardware interrupt handlers.
If you are using extended memory software that isn't a DPMI host, the program also allocates all the unused memory and combines it with the extended memory in an area called the X memory space. Your program treats this block of memory as a large, single, continuous block. The extended memory that drivers have allocated is not part of this block, so errant pointers cannot overwrite it. The first megabyte of memory, including the video display buffer, is also not part of this block, and you can access it as you would in any other memory model.
Your program loads its code and static data into the bottom of the X memory space. The heap grows up from the top of static data towards the stack, and stack grows down from the top of the X memory space towards the heap.
Figure 20-1 X memory space Stack I V Unused ^ I Heap Static data CodeThe stack can grow until it reaches the first 4KB boundary above the top of the heap. If the stack tries to grow beyond that, your program is aborted.
The default minimum size for a DOSX program's stack is 4096 bytes. You can change the minimum stack size with the =nnnn compiler option, or with the _stack global variable:
unsigned int _stack = nnnn;This number is rounded up to the nearest 4KB boundary. Your program uses this number to prevent the heap from growing into the stack. Memory allocation functions such as malloc() and calloc() fail when the heap has grown to the largest size possible given the current minimum stack size.
When you link a program for the DOSX memory model, OPTLINK adds code to your program to handle all interrupts except hardware interrupts. When the processor detects a fault, it issues an interrupt. The DOSX code then prints out diagnostic information and terminates the program. For example, if you use a null pointer, the output might be:
INTERRUPT 0DH, GENERAL PROTECTION FAULT possible illegal address error code = 0000 cs = 002B eip = 00000E6A ss = 003B esp = 000B6FF0 ds = 0033 ebp = 000B6FFC es = 0033 eax = 00000000 fs = 0000 ebx = 0000FF00 gs = 0000 ecx = 00000000 eflags = 00013246 edx = 00002B74 esi = 000000D4 edi = 00002E55 absolute start address of DGROUP 10000000 available conventional memory 00025000 to 0009D000 available extended memory 001C0000 to 00200000 X memory space located at 10000000 to 100B7000If you want to execute an interrupt, return to real mode by calling int86() or int86x() before executing the interrupt.
bdos() bdosx() _chkstack() getDS() int86() int86x() intdos() intdosx() peek() poke() segread() write()
bios_disk() dos_abs_disk_read() dos_abs_disk_write() farmalloc() farcalloc() farcoreleft() farfree() farrealloc
include macros.asm begdata extrn _x386_zero_base_selector: word, _disp_base:word enddata begcode extrn _x386_mk_protected_ptr:dword, _x386_get_abs_address,dword extrn _disp_open:near public _main _main proc near push 0b8000h call _x386_mk_protected_ptr ; returns pointer in DX: EAX mov es,dx ; should reference a video selector mov byte ptr es:[0],'X' ; places an X on the screenThe next block of sample code writes to the screen using a selector with an address of absolute zero. This selector is stored in a global variable and is available for applications to use as required:
mov es,_x386_zero_base_selector ; load the selector mov byte ptr es:[0b8002h],'Y' ; places a Y on the screenThe next block of sample code writes to the screen using near pointers based on the default selector normally found in DS. Your code must first call the function _x386_get_abs_address to determine the base address of DS (DGROUP). Since this value is always greater than 1MB, you need to use a negative offset to access the video buffer. The segment limits DOSX sets on DS allow the use of negative offsets, as illustrated by the following:
push ds ; push far pointer ds:0 push 0 ; returns address in EAX call _x386_get_abs_address ;returns address in EAX neg eax ; DS:EAX now points to zero mov byte ptr ds:[eax+0b8004H],'Z' ; places a Z on the screenThis last code fragment calls the _disp_open run-time library function (from the Display package) to identify the video card and create a selector that points to the video buffer. When you use this method to access video memory, you should inspect the value in _disp_open to make sure it is non-zero. If _disp_open fails to identify the video card and uses the BIOS to access the video buffer, _disp_base will equal zero.
call _disp_open ; initialize _disp_base mov es,_disp_base mov byte ptr es:[6],'T' ; places a 'T' on the screen mov ah,4ch int 21h ; terminates sample program _main endp endcode end
mov AX,250eh mov EBX, ; seg:offset of real mode procedure mov ECX, ; number of words to copy from ; protected stack to real mode stack int 21hECX must not be greater than 63 (two-byte) words; therefore there is a maximum of 126 bytes that can be transferred. Making ECX zero will make the call slightly faster and preserve real mode stack space. The real mode procedure receives control with a stack of about 300 bytes in size. The dword return address is immediately placed on the stack and any copied parameters are placed above that. All general registers are preserved when a protected mode function calls a real mode function, as well as when the real mode function returns control to the protected mode function. The real mode function receives ds = cs; all other segment registers are undefined. Upon return to protected mode, all general registers are as they were left by the real mode code. Segment registers are as they were prior to the int 21h call. The stack will be unchanged even if the real mode function changed the values of the parameters on the real mode stack; the real mode function cannot return values on the stack.
Note that it is difficult to get the real mode segment value, since there are no segment fixups in the protected mode code. To get the segment value of the real mode code:
mov ax,2509h ;get system segments and selectors int 21hAll general registers will be destroyed and filled with various real and protected mode segments and selectors. The value of interest is the real mode code segment returned in BX. The following program demonstrates the use of a real mode procedure:
include macros.asm include x386mac.asm begcode_16 ; define start of real mode ; code segment real_proc proc far mov EAX,[ESP+4] ; mov first parameter into EAX retf real_proc endp endcode_16 ;end of real mode code segment begcode public _main _main proc near push 12345678h ; parameter to send to real ; mode code mov AX,2509h ; get system segments and ; selectors int 21h ; get real mode segment in BX rol EBX,16 ; put segment in high word of EBX mov BX, offset real_proc ; EBX now = cs:ip mov ECX,2 ;copy two words or one dword mov AX,250eh int 21h ;call real mode procedure ; make a GP fault to examine registers: push CS pop SS ; illegal value in SS will terminate program ; and dump registers to screen. EAX should ; equal 12345678h _main endp endcode endWhile function call 250eh is similar to Pharlap's function 250eh, it is not identical; function 2509h as used above is totally different from Pharlap's version, and Pharlap function 2510h which allows the caller to specify all registers is not supported in DOSX. Other relevant Pharlap-like function calls which DOSX supports are as follows:
Function Description 2502-2507 Deals with real and protected mode interrupt vectors 2508 Gets base address of selector 250c Gets hardware interrupt vectors 250d Gets real mode data buffer address and real mode call back device address 2511 Executes interrupt in real mode 252b Subfunctions 5 and 6; locks and unlocks virtual memory (useful under DPMI)The real mode data buffer (function 250d) is shared with the DOS extender and is overwritten during disk i/ o or screen i/ o, so it should only be used for temporary storage. The normal size of this buffer is 16K bytes, but it may be smaller if there is insufficient conventional memory. Keep in mind that real mode libraries will have some difficulty accessing protected mode data.
include macros.asm include x386mac.asm comment& This program demonstrates how to allocate static real mode memory, and access it both from real mode code and 32-bit protected mode code. The function main places the number 12345678h in the real mode array "real_data". main then calls the real mode function "real_proc", which retrieves that number and returns to protected mode with that number in EAX. main then causes a GP fault so that the registers can be examined. & begcode_16 ; define start of real mode code ; segment real_data db 45000 dup (0) ; real mode data real_proc proc far ; procedure to call from ; protected mode assume DS:__X386_CODESEG_16 ; name of real mode code segment mov EAX, dword ptr DS:real_data[10000] ; mov the dummy data into EAX retf real_proc endp endcode_16 ; end of real mode code segment begdata ; protected mode data segment extrn __x386_zero_base_selector:word ; data selector enddata begcode ; protected mode code segment public _main _main proc near mov AX,2509h ; get system segments and ; selectors int 21h ; get real mode segment in BX comment& There are no fixups done on 32-bit code, so you cannot use segment names as immediate values; you must use function 2509h. This function destroys all registers, filling each 32-bit register with two segments or two selectors. & push EBX ; save real mode segment which is in BX mov ES,__x386_zero_base_selector ; note that the above selector is not hardwired, ; but is available through the public variable ; as shown above. It has a base of 0, 4GB limit movzx EBX,BX ; zero upper word shl EBX,4 ; convert segment to absolute ; address assume ES: nothing mov dword ptr ES: real_data[EBX+10000],12345678h ; above instruction puts dummy data in ; real_data[ 10000], the selector in ES ; points to zero. EBX gives the offset of the ; start of the real mode segment. Ensure that ES ; does not have any "assumes" on it pop EBX ; restore real mode segment in BX rol EBX,16 mov BX, offset real_proc ; EBX now = cs:ip of ; real_proc xor ECX,ECX ; copy zero parameters on stack mov AX, 250eh int 21h ; call real_proc ; make a general protection fault to examine ; registers push CS pop SS ; illegal value in ss will terminate program and ; dump registers to screen. ;eax should have the value 12345678h _main endp endcode end
include macros.asm begdata extrn __x386_zero_base_selector:word ; data selector for using far pointers extrn __x386_get_abs_address:dword ; function pointer for using near pointers enddata begcode public _main _main proc near comment& The far pointer method: A selector is stored in a global variable as shown in the extrn definition above. This selector has a base address of zero and a 4 Gb address limit. It is primarily useful for addressing the first 1Mb since everything above the first 1Mb is remapped with the paging mechanism and thus is not readily usable. This selector can be used to access the video buffer as follows: & ; place an X in the upper left corner ; of a color monitor mov ES,__x386_zero_base_selector mov byte ptr ES:[ 0b8000h], 'X' comment& The segment wrap method: This method is a bit more complex initially, but the use of near pointers is a great advantage in some cases. It relies on the fact that the 80386 "wraps" around the 4 Gb address just like an 8088 wraps around the 1 mbyte address. The segment limits have been set up such that the default DS selector cannot access addresses low enough to corrupt the code; however, there is no limit on upper addresses other than generating page faults if you try to read or write to empty space. To make the segment wrap work, you first have to find the base address of DGROUP. This varies depending on whether the system is DPMI or non DPMI. The example below determines this at run time so it will work in either situation. & push DS push 0 ; put far pointer to start of DGROUP ; on stack call dword ptr __x386_get_abs_address ; returns address in EAX add ESP, 8 ; pop far pointer from stack ; then subtract address in EAX from ; address in DS to get to zero neg EAX ; now DS:[ EAX] points to absolute zero. Add the ; value required to EAX to access an address in ; the first 1Mb. ; To place X in upper right corner of a ; color monitor: mov byte ptr DS:[0b809Eh + EAX],'X' ret _main endp endcode end
Functions that are not implemented will return EAX = 0xA5A5A5A5, with the carry flag set; this is the Phar Lap convention for handling unimplemented functions.
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."
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:
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"
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.
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.
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:
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.
#define __handleAll 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 #includeThe functions in handle.h will call their equivalents in the standard library, such as malloc(), realloc(), and free().