JUNE 1993 VOLUME 5 NUMBER 6
INDEX
Hello and welcome to the June 1993 issue of Bullets!
As I was thinking of a column theme for this month's issue of Bullets, my thoughts kept coming
back to implementation of developer support. I wondered if it was too close to my personal
issues.
Discussions of the search for the elusive "best," "most encompassing,"
"all-in-one-you'll-ever-need" API sets for developers race across magazine headlines. In much
smaller print appears news on the search for the means to support them. API diversity sets the
tone for the wide range of support methods that pervade the PC world.
Thankfully, not all development efforts die in three to five years. Good legacy applications, and
hence accompanying compilers, tools, TSRs and runtimes, will survive! How should these
products be supported?
You, as an internal or external application developer, must face this question too. The quest to
support these legacy products in conjunction with current flagship products as we round the
corner with beta offerings on new products is real.
The task of defining and offering the appropriate support for all levels of developers and
developer products is a challenge. Not a new task, certainly, but an evolving one. At Novell,
"Two years ahead and gaining," applies to services as well as to engineering.
Watch out, Jean-Luc, as we "make it so."
Mad Poarch
Director, Developer Support/Service
Novell's DOS Protected Mode Services (DPMS)
In today's DOS environments resident software is used to provide extensions to DOS. It
enhances DOS performance and capabilities, and provides the interface to external environments
and peripherals, such as networks and optical drives. But while these memory resident extensions
add functionality to DOS, where conventional memory is limited to 640K, they can also use up
considerable amounts of memory needed to run DOS applications.
The DOS Protected Mode Services (DPMS) API from Novell is specifically designed to allow
DOS device drivers and TSRs to execute in protected mode and reside in extended memory.
DPMS frees conventional memory for applications and allows memory resident software to
access up to 16 megabytes of extended memory on 286, 386 and 486 computers. This article
provides a look at the philosophy behind DPMS, as well as a functional overview and specific
coding examples of a DPMS client program provided with the DPMS Software Developer's Kit
(SDK).
Memory Resident Software & Limited Space
As mentioned above, DOS programs have traditionally been limited to using 640K of
memory. Additionally, they have had to share this limited space with a variety of resident
software programs, such as TSRs and device drivers. And while DOS applications have
continued to grow in size, so have the size, complexity and number of TSRs and drivers
employed in a typical DOS user's configuration, leaving even less memory available for those
applications. This is especially true in networked environments, where resident device drivers,
transport protocols, shells and redirectors use up even more of that precious 640k.
Numerous technologies have emerged to alleviate the 640K restriction. DOS memory managers
help to optimize memory by allowing device drivers and TSRs to load in "upper memory"
between 640K and one megabyte on 386 computers. Examples of these memory managers are
Qualitas 386MAX, Quarterdeck's QEMM and those provided in recent versions of MS-DOS and
DR DOS. This approach works well for many situations, but does not fully solve the problem.
For TSRs to "load high", they must now compete with the many BIOS routines, buffers and
other system components that use upper memory. Because upper memory is fragmented and
limited in size and availability, many TSRs still are forced to load in conventional memory.
Memory paging technologies, such as LIM-EMS, allow programs to access expanded memory
through a small "window" in the addressable first megabyte of memory. However, this approach
requires 64K for the paging window, and can only perform operations on data after it has been
mapped into that window. Memory paging is an effective technique for applications like database
and spreadsheet programs that need to manipulate large amounts of data, but it is not often well
suited to the needs of resident system software designed to perform specific functions.
More recently DOS extender specifications, such as VCPI and DPMI, have emerged allowing
DOS programs to use protected mode and access extended memory directly. While these
technologies benefit large applications, they are generally too cumbersome for utility programs
and TSRs.
The DPMS Approach
The DPMS API allows resident programs to access the protected mode capabilities of Intel
80286, 386 and 486 computers to store and execute code and data. Its primary functions are to
allocate extended memory, manage protected mode descriptor tables and handle the transitions
between real mode and protected mode execution. DPMS defines a "client-server" software
architecture, where a client program calls the DPMS server to perform the necessary memory
allocations and mode transitions.
In contrast to the technologies mentioned previously, the DPMS API is specifically tailored to
the needs of TSRs and device drivers. Its smaller size and reduced complexity make it relatively
easy and straightforward for developers to implement protected mode functionality into their
programs. Vendors can easily port existing real mode drivers and TSRs to use DPMS, and allow
them to run in real mode when a DPMS server is not resident.
DPMS client programs use very little upper or conventional memory, so more memory remains
available for existing programs. In addition, DPMS supports current versions of DOS and
coexists with existing memory managers, DOS extenders, and protected mode environments
such as Microsoft Windows.
By using extended memory to execute and store data, DPMS client programs provide two
notable benefits.
- Because they can virtually eliminate their use of memory within the first
megabyte, they do not constrain the amount of available conventional or upper memory. Users
continue to add system enhancements without limiting their ability to run large applications.
- By removing their own memory restrictions, DPMS clients can incorporate additional
needed functionality and improve their performance. This ability will benefit all developers who
write DOS TSRs. With the growing number of resident programs being used in today's DOS
environments, the competition for the limited amount of DOS memory will continue to intensify.
With DPMS, developers can use protected mode and extended memory to add extra
functionality, and at the same time reduce their footprint in conventional memory. This gives
them a significant advantage over competitors who must limit functionality to conserve memory.
The DPMS Software Developer's Kit (SDK) includes the Novell DPMS server.
Developers may distribute the server component royalty-free with their DPMS client programs.
DPMS is not tied to any specific DOS version, so clients can run on current versions of DOS.
(Note: the currently shipping SDK includes a prerelease version of the DPMS server for
developing prototypes. Once the DPMS server is finalized, developers will be updated and
allowed to ship that server.)
Novell will be including the DPMS server and several DPMS clients in Novell DOS 7,
scheduled to ship late this summer. It will include DPMS versions of its disk cache and disk
compression driver, the peer-to-peer desktop server and CD- ROM file extensions.
DPMS Functional Overview
DPMS services are provided by the DPMS server, a device driver that must be loaded
before any client can use its services. These services consist of about 25 functions that:
- Handle mode transitions between real and protected mode
- Allocate and manage protected mode descriptors
- Map descriptors to real mode memory segments
- Allocate and map linear memory
Once the DPMS server is installed, it hooks onto the INT 2F chain. DPMS clients should
first call INT 2F to check for the presence of DPMS and find the entry point addresses for
subsequent calls. Depending on how a client is written, it may fail to install if DPMS is not
installed, or it may operate entirely in real mode.
DPMS clients are created as real mode, 16-bit DOS .EXE or .COM programs, using standard
assemblers and linkers. A typical structure might be:
- Initialization code to be discarded after it is finished
- Real mode code and data "footprint"
- Relocatable code and data, which would be executed in protected mode and moved to
extended memory.
DPMS clients always require a small amount of memory within the first megabyte. At a
minimum, the client must maintain pointers to DOS and code to perform the DPMS call up to
protected mode. Some developers may find it convenient to have specific functions execute in
real mode, while the remainder of the program uses protected mode.
DPMS Code Examples
The following code examples are taken from VDISK.ASM, a sample client included in the
DPMS SDK. In the examples, INIT_TEXT contains the initialization code, PROT_TEXT
contains code to be relocated in protected mode and the call down to real mode code, and
LOW_TEXT contains the code to remain in real mode and the call up to the protected mode
code. To simplify the code excerpts in this article, the global variables declared in Figure 1 apply
for all other code excerpts.
FIGURE 1: Global variable declarations
real_func dd ?
;real_func will be set to point to DPMS's real mode entry
; point if a DPMS server is found. The code in this segment
; will then use this vector to call DPMS to request a call up
; to the protected mode code in PROT_TEXT.
prot_func dd ? ;DPMS protected mode entry point.
prot_sel dw ?
;Protected mode data selector for a data segment descriptor
; that will be aliased to PROT_TEXT's (code) descriptor,
; allowing us to write into PROT_TEXT.
DPMS_Reg_Struc <0,0,0,,0,0,0,0,0,,,EFL,0, 0,, ,, ,, ,, ,>
;This is the DPMS register structure used to make DPMS calls.
;Order of the fields is:
; Bits Register Bits Register
; 00h EDI 20h EIP
; 04h ESI 24h CS/reserved
; 08h EBP 28h EFLAGS
; 0Ch reserved 30h SS/reserved
; 10h EBX 34h ES/reserved
; 14h EDX 38h DS/reserved
; 18h ECX 3Ch FS/reserved
; 1Ch EAX 40h GS/reserved
END of FIGURE 1
The example in Figure 2 allocates the data descriptors to be used while in protected mode. The
code allocates:
- One data descriptor for the device driver request packet -the device driver request
packet is a data structure passed to VDISK when ever DOS attempts to perform operations on the
virtual disk.
- One data descriptor for the (extended memory) data area in PROT_TEXT
- One data descriptor for an alias to the LOW_TEXT real mode segment
FIGURE 2: Allocating protected mode descriptors
.
.
.
mov cx,3 ;Number of descriptors required
mov ax,DPMS_D_ALLOC ;DPMS function number 0200h
;Allocate device request packet
; descriptor
call [real_func] ;Call DPMS through real mode
; entry point
jc dpms_err
mov request_sel,ax ;This gives PROT_TEXT its ES.
;It cannot be initialized yet.
add ax,8 ;Get next selector
mov prot_sel,ax ;Protected mode data selector,
; which will be initialized
; further down by DPMS_D_SET_BASE
; to an alias of PROT_TEXT's code
; segment descriptor.
add ax,8 ;Get next selector
mov data_sel,ax ;Local data descriptor which gives
; PROT_TEXT its DS, and
mov bx,ax ; aliases the descriptor to.
mov cx,ds ; LOW_TEXT.
mov ax,DPMS_D_ALIAS_REAL ;DPMS function number 0203h
call [real_func]
jnc dpms_fixup ;If carry = 1, free descriptors
; in reverse order using DPMS
; function 0201h
.
.
.
END of FIGURE 2
The example code in Figure 3 moves the protected mode segment into extended memory through
the following sequence of operations:
- Relocate PROT_TEXT to extended memory.
- Set the base of the descriptor to a linear address returned from DPMS_M_RELOC using
the selector (prot_sel) for that descriptor.
- Change the limit of the descriptor to make sure that data is only written via "prot_sel."
FIGURE 3: Relocating code into protected mode
rsCallup DPMS_Reg_Struc
.
.
.
push es
mov ax,PROT_TEXT
mov es,ax
xor si,si ;ES:SI is the base of the
; PROT_TEXT segment
mov cx,offset PROT_TEXT:prot_end ;size
dec cx ;limit=size-1.
mov bx,009Ah ;Executable segment
xor dx,dx ;DPMS to allocate a descriptor
mov ax,DPMS_M_RELOC ;DPMS function number 0400h
call [real_func] ; returns with selector in AX
mov rsCallup.DPMS_CS,ax ;Selector for executable
; descriptor
;DPMS_M_RELOC returns the linear
; address in BX,CX
;Next, call DPMS_D_SET_BASE to
; make prot_sel into data alias
mov dx,cx ; to same segment.
mov cx,bx ;CX,DX is linear base address.
mov bx,prot_sel
mov ax,DPMS_D_SET_BASE ;DPMS function number 0204h
call [real_func]
;Then, call DPMS_D_SET_LIMIT to
; make sure you only write to the
; data via prot_sel.
mov dx,offset PROT_TEXT:prot_data_size
dec dx ;limit=size-1
xor cx,cx ;Limit in CX,DX. Since limit<64K,
; then CX=0
mov bx,prot_sel
mov ax,DPMS_D_SET_LIMIT ;DPMS function number 0205h
call [real_func]
.
.
.
END of FIGURE 3
Figure 4 demonstrates how to call up from real mode segment to protected mode segment by
performing the following operations:
- Set up the descriptor that was reserved previously so that it points to the request
packet by mapping a real mode segment (value is in ES) via a protected mode descriptor stored
in request_sel
- Pass control to the protected mode dispatch routine, which calls a procedure in protected
mode. The DPMS_CALL_PROT function sets the CPU registers on entry to the values set up in
the DPMS_Reg_Struc.
FIGURE 4: Calling up to protected mode
request_sel equ DPMS_ES ;ES:BX points to request
; structure
request_off equ DPMS_BX
data_sel equ DPMS_DS ;Alias to the LOW_TEXT segment.
callup PROC FAR
IRP reg,
push reg
ENDM
mov request_off,bx ;Save the caller's BX
mov bx,request_sel ;Alias selector
mov cx,es ;Request packet's segment
mov ax,DPMS_D_ALIAS_REAL ;DPMS function 0203h
call [real_func]
mov ax,cs
mov es,ax ;ES:DI must point
; to rsCallup
mov di,offset LOW_TEXT:rsCallup ;VDISK code in
; protected mode.
xor cx,cx ;Copy 0 words from
; stack to stack
mov ax,DPMS_CALL_PROT ;DPMS function
; number 0100h
call [real_func]
mov ax,rsCallup.DPMS_AX ;Restore error code.
IRP reg,
pop reg
ENDM
ret
callup ENDP
END of FIGURE 4
The example code in Figure 5 demonstrates how to make a call down from a protected mode
segment to a real mode segment. Calling up requires a call to the DPMS_CALL_PROT function
(the call to a protected mode segment). DPMS_CALL_PROT can only terminate with a RETF,
so the only function needed to make a call down to a real mode segment is
DPMS_CALL_REAL_RETF. This function must be called from protected mode.
FIGURE 5: Making a call down to real mode
rsCalldown DPMS_Reg_Struc
xms_func_IP equ DPMS_IP
xms_func_CS equ DPMS_CS
prot_data_size label byte ;used in INIT_TEXT to set
; limit on prot_sel
dpms_callxms PROC NEAR
push es
mov es,prot_sel ;alias to PROT_TEXT
; descriptor
mov es:rsCalldown.DPMS_AH,XMSMOVE
mov di,offset PROT_TEXT:rsCalldown
;DPMS reg. offset
xor cx,cx
mov ax,DPMS_CALL_REAL_RETF ;DPMS func. 0101h
call [prot_func] ;call DPMS via
; protected mode
pop es ; entry point.
ret
dpms_callxms ENDP
END of FIGURE 5
In VDISK, DPMS_CALL_REAL_RETF is only used to call XMS (Extended Memory
Specification), so some of the registers can be initialized with the correct values. In general, you
might call down to more than one real mode entry point, so you would need to initialize the
rsCalldown function each time you used it.
At the beginning of the code segment in Figure 5, the DS register points to LOW_TEXT, where
the "prot_sel" variable holds a selector which is used for a data segment alias to the
PROT_TEXT (code) descriptor. This is loaded into ES to allow you to write into PROT_TEXT
since, in protected mode, you cannot write into a code segment.
For More Information
Novell's DOS Protected Mode Services (DPMS) SDK, is available to members of Novell
Developer Programs. To purchase the DPMS SDK, contact Novell Developer Relations at
1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.
Saving & Restoring Drive Mappings & the NetWare Client SDK
Many programming situations require you to save drive mappings and later restore the
mappings to their original state. You may need to save and restore drive mappings when
recovering critical applications after a connection is lost. This procedure also may be necessary
for special processing requiring an application to login to a server that already has an active
connection, which will cause the respective drives for that server to be cleared as well.
The February 1992 issue of Bullets featured an article on saving and restoring drive mappings
using the NetWare C Interface for DOS v1.2. This article presents an update demonstrating a
mechanism for obtaining and restoring drive mappings using the NetWare Client SDK. With
either kit, the methods used are fundamentally the same, but certain underlying differences in the
required APIs make the process easier with the NetWare Client SDK.
The article demonstrates methods for saving and restoring drive mappings through excerpts from
an application called MAPIT2.C that can be downloaded from Novell's CompuServe forum,
NDEVREL (Library, filename: MAPIT2.ZIP). MAPIT2.C uses environmental control functions
to obtain and set the root environment variables. These functions are used to modify the PATH
environment variable to obtain and set the search drives for both local and NetWare drive
mappings. Although MAPIT2.C uses these routines, this article does not explicitly discuss the
environmental control functions.
Saving Mappings
Required APIs:
- NWGetDriveStatus( )
- NWGetFileServerName( )
Required Functions:
- Environmental control functions to GET and SET the PATH variable at the "root"
environment for search drive manipulation.
The simplest approach for saving mappings is to use NWGetDriveStatus( ) to obtain the
drive status (information) for all 26 valid drives (drives "A"[1] "Z"[26]). If you are using NETX,
this API actually runs through 32 drives which includes currently mapped temporary drives as
well (temporary drives are only mapped during the execution of the application that mapped
them). NETX.VLM allows 26 valid drives ("A" through "Z").
The NWGetDriveStatus API accepts a drive number as input and allows a range of path formats
to be returned depending on a "pathFormat" flag. The drive mappings are stored in "mapDrive,"
(see Figure 6 for the "mapDrive" structure definition).
FIGURE 6: "mapDrive" control structure
#define MAXMAPPINGS 32
/* Mapping Control Structure:
** Client SDK - use full paths with server name
** NOTE: the search drive order is not respect to the mapped
** drive indexes. They are essentially independent. */
struct {
WORD status;
// status type:used,network...
NWCONN_HANDLE connHandle
// server connection Handle
char serverName[48
// server name
BYTE isSearch
// 0 if non-search drive,
// non-0 otherwise
char srchpath[256];
// search paths
char rootPath[256];
// root part or rootPath (base)
char relativePath[256];
// path off of the base
} mapDrive[MAXMAPPINGS];
END of FIGURE 6
As shown in Figure 7, "mapDrive" is indexed by NetWare drive number.
FIGURE 7: Saving drive mappings
for (drv = 0; drv < MAXMAPPINGS; drv++) {
Code = NWGetDriveStatus(
drv + 1,
NW_FORMAT_NETWARE,
&mapDrive[drv].status,
&mapDrive[drv].connHandle,
(char NWFAR *)&mapDrive[drv].rootPath[0],
(char NWFAR *)&mapDrive[drv].relativePath[0],
tmpPath);
NWGetFileServerName(
mapDrive[drv].connHandle,
mapDrive[drv].serverName);
/*
printf(
"\n[%d] base=%s path=%s status %x connHandle %x\n\ttmppath=",
drv,
mapDrive[drv].rootPath,
mapDrive[drv].relativePath,
(int)mapDrive[drv].status,
mapDrive[drv].connHandle,
tmpPath);
*/
}
END of FIGURE 7
For the purposes of this article, the NW_FORMAT_NETWARE constant is the best
choice. The information returned by the API (status, connection handle, root path, relative path,
and full path) fully describe each mapped drive. The "status" mask dictates what type of drive it
is: NW_UNMAPPED_DRIVE, NW_LOCAL_FREE_DRIVE, NW_LOCAL_DRIVE, or
NW_NETWORK_DRIVE. This drive type is defined in NWDPATH.H. The connection handle
returned indicates the server to which the drive is mapped. If a server connection is dropped
before you have a chance to reset the drive mapping, you should obtain the server name for each
drive using NWGetFileServerName( ). The server name is required, since the connection handle
may change when connections are reestablished.
Search drives are handled strictly by the PATH environment variable, and in all cases the ROOT
environment is manipulated. Each drive/path specified in the PATH environment variable is
parsed and the path order is recorded, taking into account the full paths specified for local drive
mappings. The sample code in Figure 8 uses the "search" field in the structure to store full path
names for all local mappings and only the drive letters for network mappings. The path order is
dictated strictly by the index into "mapDrive" as shown in Figure 8.
FIGURE 8: Storing path names for local mappings & drive letters for network
mapings
GetEnvironmentVar("PATH",thePath);
if (thePath[0] != NULL) {
pntr = thePath;
for (drv = 0; (drv < MAXMAPPINGS) &&
(pntr = _fstrchr(pntr,':')); drv++ ) {
pntr2 = _fstrchr(pntr,';');
if (pntr2)
_fmemcpy(mapDrive[drv].srchpath, pntr - 1,
(int)(pntr2 - pntr + 1));
else
_fstrcpy(mapDrive[drv].srchpath, pntr - 1);
// printf("\nSearch: %d: - %s",drv, mapDrive[drv].srchpath);
*pntr = ' ';
// mark respective drive as search drive if netware drive
if (mapDrive[*(BYTE *)(pntr - 1) - 'A'].connHandle) {
mapDrive[*(BYTE *)(pntr - 1) - 'A'].isSearch = 1;
}
} /* while */
}
END of FIGURE 8
Restoring Mappings
Required APIs:
- NWSetDriveBase
- NWGetConnectionHandle
Required Functions:
The sample code in Figure 9 assumes that you still have connections to the servers to
which your drives were originally mapped. If not, you can use the "mapDrive" structure to
determine what server connections are required to restore the mappings. Since the connection
handle may change for new server connections, you should call NWGetConnectionHandle( )
before mapping a drive using NWSetDriveBase( ).
FIGURE 9: Restoring mapped NetWare Drives
// re-map all netware drives
for (drv = 0; drv < MAXMAPPINGS; drv++) {
// If connection ID specified for drive, then reset it,
// otherwise it's a local mapping only.
switch (mapDrive[drv].status) {
case NW_UNMAPPED_DRIVE: // no need to handle this one
break;
case NW_LOCAL_DRIVE: // map local drive
break;
case NW_NETWORK_DRIVE: // redirected drive
// printf("\nDrive: %c := Redirected...not handled",
'A' + drv);
case NW_NETWARE_DRIVE: // netware drive
// printf("\nDrive: %c: = %s\\%s \\ %s",'A' + drv,
// serverName, mapDrive[drv].rootPath,
// mapDrive[drv].relativePath);
cCode = NWGetConnectionHandle(
mapDrive[drv].serverName,
0,
&mapDrive[drv].connHandle,
NULL);
if (cCode) {
printf("\n\tNWGetConnectionHandle: %c: - error
%x",'A'+drv,cCode);
break;
}
cCode = NWSetDriveBase(drv + 1,
mapDrive[drv].connHandle,
NULL,
mapDrive[drv].rootPath,
0);
if (cCode) {
printf("\n\tNWSetDriveBase: %c: - error %x",
'A'+drv,cCode);
break;
}
// if there's a path off of base, then 'cd' to it
if (mapDrive[drv].relativePath[0]) {
sprintf(tmpPath,"%c:%s",'A' + drv,
mapDrive[drv].relativePath);
if (cCode = chdir(tmpPath)) {
printf("\n\tchdir: failed - %s (%d)",
tmpPath,
errno);
}
}
break;
case NW_LITE_DRIVE:
// printf("\nDrive: %c := NWLite...not handled",
'A' + drv);
break;
} // switch
} // for
END of FIGURE 9
The first required task is to map all network drives. The search drives are handled strictly
by the PATH environment variable, which will be discussed later.
In the sample code, all drives are initially mapped to their rooted portion (base) using
NWSetDriveBase(), then a chdir() (change directory) is used to move to the appropriate relative
path. Both the "rootPath" and "relativePath" portions of each drive mapping were stored earlier
for this operation. NWSetDriveBase sets the drive to a base (root) only, and the relative portion
of the path must be set using the change directory function. In most cases, the "server\volume:" is
the rooted portion and relative portion consists of the remainder of the path from the volume to a
particular directory.
The "status" field is also required to map each drive, since it determines what kind of drive is
being remapped. This field dictates whether the drive was local, free, a redirected drive, a
NetWare drive, or some other kind. For this article, only NetWare drives are considered.
Search Drives
Search mappings are handled strictly by the root PATH environment variable. Both local
drive mappings and NetWare search drives are restored by building a new PATH environment
variable and writing/putting it back to the root environment. It is built quite easily from the data
stored in the "mapDrive" structure using the "search" field and a sequencial "index" into
"mapDrive." The drives and paths stored in the "search" field are concatenated repeatedly for
search drives to build the new PATH environment variable. Note that the "search" field contains
either full paths for all local drives, and only drive letters followed by a ":." for all NetWare
drives. Figure 10 contains a code segment depicting creation of the PATH enviroment variable.
FIGURE 10: Creation of the PATH environment variable
// now reset the search drives & create the PATH variable
thePath[0] = NULL;
for (drv = 0; drv < MAXMAPPINGS && mapDrive[drv].srchpath[0];
drv++)
{ strcat(thePath,mapDrive[drv].srchpath);
strcat(thePath,";");
} /* for */
// update the root environments
strcpy(tmpPath,"PATH=");
strcat(tmpPath,thePath);
PutEnvironmentVar(tmpPath, masterEnvPntr, maxEnvSize);
END of FIGURE 10
Note: that the structure used for saving the drives is used for more than one purpose.
The search order is strictly adhered to in the
|