[comp.windows.ms.programmer] Windows3/TSR interface HELP

dave@mgc.uucp (Dave Lockwood) (05/15/91)

This is the second posting - I didn't see any replies the first time :-(

I'm trying to write a Windows 3 application which needs to interface to a
DOS TSR which communicates via an interrupt. Everything works fine in real
mode, but I get UAEs and reboots in both standard and 386 enhanced mode.

The code segment that's causing the trouble:

	union	REGS	InRegs;
	union	REGS	OutRegs;
	struct	SREGS	SegRegs;
	HANDLE		hBuffer;
	LPSTR		lpBuffer;

	InRegs.h.ah = 11;
	InRegs.h.al = 1;
	InRegs.x.dx = FP_OFF(lpBuffer);

	hBuffer = GlobalAlloc(GHND,512L);
	lpBuffer = GlobalLock(hBuffer);
	InRegs.x.di = FP_OFF(lpBuffer);
	SegRegs.es = FP_SEG(lpBuffer);

	int86x(0x7f,&InRegs,&OutRegs,&SegRegs);

I've done a lot of RTFM-ing, but to no avail (at least, the problem isn't
solved). I didn't write the TSR, so it's difficult to get changes done
there (I don't have the source).

My suspicions are that lpBuffer above is a 'selector' (whatever that is)
rather than a true, _far address, and when the TSR (assembler) uses the
ES:DI register pair, some sort of protect trap takes place.

Any ideas on how to solve this? Do I need to spend $500 on the DDK and write
a special device driver? Please help - any comments would be appreciated;
if you did respond via a posting last time, please respond again. My upstream
site lost some news just about the "right" time :-(.

Thanks in advance!

-- 
Dave Lockwood          dave@mgc.uucp                 G4CLI@GB7DCC._199.GBR.EU
Head of Technology           ...!uunet!mcsun!ukc!mgc!dave
----MG Computer Systems Ltd, PO Box 50, Doncaster, DN4 5AW +44-302-738770----

roger@bjcong.bj.co.uk (ROGER JAMES) (05/17/91)

In article <1991May15.133642.40@mgc.uucp> dave@mgc.UUCP (Dave Lockwood) writes:
>
>I'm trying to write a Windows 3 application which needs to interface to a
>DOS TSR which communicates via an interrupt. Everything works fine in real
>mode, but I get UAEs and reboots in both standard and 386 enhanced mode.
>
>My suspicions are that lpBuffer above is a 'selector' (whatever that is)
>rather than a true, _far address, and when the TSR (assembler) uses the
>ES:DI register pair, some sort of protect trap takes place.
>
Your suspiscions are correct. The TSR needs to be a passed an address that
is valid in real mode (i.e it is a segment/offset rather than a selector/offset
). You can allocate a buffer that has such a valid address by using the
GlobalDOSAlloc function. However you will not be able to pass this address
to your TSR in a segment register. This will cause a UAE. You can pass
this address in a non segment register, which is the easiest way to do it
if you have access to the source of the TSR. If you dont have access to
the source then _here comes the bit you dont want to hear_. You need
to call your TSR via DPMI, there are various examples of how to do this
knocking around on Online and Compuserve, it is not straightforward.
Here comes the _next bit you dont want to hear_. If the buffer you passed
to the TSR is going to be used at interrupt time (i.e. from within a
hardware interrupt service routine). Then your system will crash
whenever you run DOS shells from windows. There are a number of ways round
this, all of which are very messy.


-- 
roger@bj.co.uk 				(UK only)
roger@boldon-james-limited.co.uk	(Internet)
..!mcsun!ukc!pyrltd!bjcong!roger

Norbert_Unterberg@p4.f36.n245.z2.fidonet.org (Norbert Unterberg) (05/17/91)

 > I'm trying to write a Windows 3 application which needs to interface to a
 > DOS TSR which communicates via an interrupt. Everything works fine in real
 > mode, but I get UAEs and reboots in both standard and 386 enhanced mode.
 >
 > The code segment that's causing the trouble:
 >
 [...]
 >         hBuffer = GlobalAlloc(GHND,512L);
 >         lpBuffer = GlobalLock(hBuffer);
 >         InRegs.x.di = FP_OFF(lpBuffer);
 >         SegRegs.es = FP_SEG(lpBuffer);
 >
 >         int86x(0x7f,&InRegs,&OutRegs,&SegRegs);
 >
 > I've done a lot of RTFM-ing, but to no avail (at least, the problem
 > isn't
 > solved). I didn't write the TSR, so it's difficult to get changes done
 > there (I don't have the source).

You obviously have read the wrong manual. Ever heard of the DPMI (Dos Protected 
Mode Interface)? That should solve your problems.

The protected mode you must watch out for a lot of things you can ignore in 
real mode. Here are some of the differences:

- In protected mode the processor can access more than 1MB of memory. In real 
mode only the 1st MByte can be reached by the processor. So if you GlobalAlloc 
some memory in protected mode, that memory can be anywhere, very likely 
somewhere above 1 MB. Hence your TSR (in real mode) can _not_ reach that 
memory.
- In proteted mode all segment `addresses' are no longer `adresses'. All values 
stored in segment registers are selectors. A selector is an index in a (local 
or global) descriptor table (LDT/GDT), which contains the linear addresses for 
the segments. So a selector only identifies a segment. That si similar to the 
Windows memory handle. The segment can float in memory, but the selector 
remains valid, as long as the segment address is updated in the descriptor 
table.

- The protected mode has its own interrupt table, which is not identical with 
the real mode interrupt table. So calling a (real mode) TSR from protected mode 
must fail, because the protected mode interrupt vector does not point to the 
TSR.

Because these restrictions make it (nearly) impossible for a protected mode 
application to communicate with a real mode programm, some people from Borland, 
Microsoft, Intel, Lotus, Phar Lap etc. came together and developed the DPMI 
specification. DPMI stands for Dos Protected Mode Interface and is an API for 
protected mode programs that allows it to do several (dirty) things and tricks 
otherwise not possible in PM. A copy of that specification can freely(!) be 
orderd by Intel, Santa Clara.

This DPMI has all the functions you need to call yout TSR. Fist you must 
allocate the memory you need in the real mode addressable 1 MB region. That is 
done with function `Allocate DOS Memory Block'. The result is a selector AND 
the segment address of the memory block. Then you fill in the required register 
values for the TSR into a data area called `real mode call structur'. When 
calling the real mode program, all registers will be loaded from this 
structure. Then you can call yout TSR via the DPMI fuction `Simulate Real Mode 
Interrupt'. The TSR will (hopefully) do its job and fill the memory with the 
expected values. And don't forget to free the DOS memory block, if it is no 
longer needed.

Hope I could give you some clues...

Norbert,
Dortmund, FRG

glenn@imagen.com (glenn boozer) (05/18/91)

>This is the second posting - I didn't see any replies the first time :-(
>
>I'm trying to write a Windows 3 application which needs to interface to a
>DOS TSR which communicates via an interrupt. Everything works fine in real
>mode, but I get UAEs and reboots in both standard and 386 enhanced mode.
>
>The code segment that's causing the trouble:
>
>        union   REGS    InRegs;
>        union   REGS    OutRegs;
>        struct  SREGS   SegRegs;
>        HANDLE          hBuffer;
>        LPSTR           lpBuffer;
>
>        InRegs.h.ah = 11;
>        InRegs.h.al = 1;
>        InRegs.x.dx = FP_OFF(lpBuffer);
>
>        hBuffer = GlobalAlloc(GHND,512L);

Does not work in either Protected mode

>        lpBuffer = GlobalLock(hBuffer);
>        InRegs.x.di = FP_OFF(lpBuffer);
>        SegRegs.es = FP_SEG(lpBuffer);
>
>        int86x(0x7f,&InRegs,&OutRegs,&SegRegs);
>
>I've done a lot of RTFM-ing, but to no avail (at least, the problem isn't
>solved). I didn't write the TSR, so it's difficult to get changes done
>there (I don't have the source).

You shouldn't need to change the TSR.

>
>My suspicions are that lpBuffer above is a 'selector' (whatever that is)
>rather than a true, _far address, and when the TSR (assembler) uses the
>ES:DI register pair, some sort of protect trap takes place.

You guessed it!

>
>Any ideas on how to solve this? Do I need to spend $500 on the DDK and write
>a special device driver? Please help - any comments would be appreciated;
>if you did respond via a posting last time, please respond again. My upstream
>site lost some news just about the "right" time :-(.

The DDK is not needed for this.


The problem is that the TSR runs in REAL mode and the windows applications
running in either protected mode use selectors[LDT] and not pointers[SEG:OFF]
to access buffers.

What follows I hastily pieced together from code I have working.

In a nutshell:
       To Send a buffer address to the TSR [Send it a REAL address of
        Segment:Offset, not a protected mode selector]
        1) DosAllocate a memory buffer  [This will be a buffer in the first
           640K of address space.  This buffer is LOCKED and will not move.
           [Not recomended by Microsoft]
        2) Copy the data from the buffer that is in "Windows Application space"
           into the DOS Buffer
        3) Get the Segment and Offset of the DOS buffer and pass that to the
           TSR.
        4) Release the DOS Buffer

       To use the buffer address [Segment:offset] that the TSR send you:
        1) Allocate a Selector [Not recomended by Microsoft]
        2) Set the selector base and length with the data returned from the
           TSR.  [Not recomended by Microsoft]
        3) Use the data
        4) Return the selector.  [Not recomended by Microsoft]


Code fragments follow.  If I left anything out, give me a note.

#define FarPtr far *
/* Data structure for calls to the TSR */
typedef union
    {
    char FarPtr p; /* treat as pointer */
    void FarPtr pv;
    struct /* treat as selector/offset */
        {
            unsigned off;
            unsigned sel;
   } w;
   long l;  /*treat as arithmetic item */
    } FPTR;

/* Data structure for value returned from GlobalDosAlloc*/
typedef union
    {
    struct /* treat as selector and a paragraph */
        {
            WORD sel;
            WORD par;
   } w;
   DWORD d; /*treat as DWORD */
    } DOSPS;

/* Windows kernel calls not in WINDOWS.H */

WORD  FAR PASCAL SetSelectorBase(HANDLE hSelector, DWORD dwBase);
WORD  FAR PASCAL SetSelectorLimit(HANDLE hSelector, DWORD dwLimit);
DWORD FAR PASCAL GlobalDosAlloc(DWORD);
WORD  FAR PASCAL GlobalDosFree(WORD);

#define WIND386ENHANCEDMODE (dwWinMemFlags & WF_ENHANCED)
#define WINDPROTECTEDMODE     (dwWinMemFlags & WF_PMODE)
#define WINDREALMODE        (!(dwWinMemFlags & WF_PMODE))

static DWORD  near dwWinMemFlags;        /* Window Memory configuration Flags */
static DOSPS  near lpsDosParagraphSelector;
static WORD   near hPhysMemHandle;           /* Handle to DOS memory */
static LPSTR  near lpPhysPtr;               /* pointer to DOS memory */

/***************************************************************************
*Name: GetWindowsMemoryConfig                                              *
*                                                                          *
*    Get windows memory configuration word so we can execute Real Mode and *
*    Protected Mode specific code.                                         *
*                                                                          *
*Returns: Nothing                                                          *
***************************************************************************/

void GetWindowsMemoryConfig ()
{
   dwWinMemFlags = GetWinFlags();
}


/***********************************************************************

Module:  GetPhysicalMemoryHandle

   Sets up a physical memory handle which can be used to access memory in
protected mode.
  If the selector cannot be created, the call fails and returns NULL.

  returns: Memory access handle.

**********************************************************************/

WORD FAR PASCAL GetPhysicalMemoryHandle()
{

    HANDLE hSel;
    HANDLE hSel2;


    /*. create a selector for use by MakePhysicalMemoryPtr() */
    /* The how of this is taken from an SR response. */
    if ((hSel2 = GlobalAlloc(GMEM_FIXED,(long) 64)) != NULL) {
        hSel = AllocSelector(hSel2);
        GlobalFree(hSel2);
    } else {
        return (WORD)NULL;
    }
    /* successful:  return a handle */
    return hSel;
}

/***********************************************************************

Module:  MakePhysicalMemoryPtr

   This routine sets up a protected mode pointer to a specified physical
address.  This pointer is good for 4K of memory at the specified physical
segment + offset address.  If NULL, an error occurred.

  returns: Memory access handle.

**********************************************************************/

LPSTR NEAR PASCAL MakePhysicalMemoryPtr(

    WORD wMemHandle,             /* Physical memory handle created by
                                    GetPhysicalMemoryHandle().
                                    a valid selector is passed.            */

    WORD wSegment,               /* Segment address to be referenced.  This
                                    is a real-mode paragraph number.       */


    WORD wOffset                 /* Offset from Segment address to be referenced. */

)
{
    /*. set selector base from wSegment parameter */
    SetSelectorBase(wMemHandle, (((LONG)wSegment)<<4) + wOffset );
    /*. set limit for 4K bytes accessable */
    SetSelectorLimit(wMemHandle,(long) 0x0FFF);
    /*. make and return a long pointer using passed wMemHandle */
    return (LPSTR)MAKELONG(0, wMemHandle);
}

/***************************************************************************

Routine: FreePhysicalMemoryPtr

   Deallocates the specified physical memory pointer.  This call is used to
free a selector in protect mode.  Once this function is run on a pointer,
the physical memory handle which was used to create that pointer
CANNOT BE USED AGAIN.

Returns:  nothing

***************************************************************************/

void FAR PASCAL FreePhysicalMemoryPtr(

    LPSTR lpMemPtr               /* Pointer used for access to physical
                                    memory.  The pointer must have been
                                    constructed by MakePhysicalMemoryPtr().*/

)
{
    /*. free the selector */
    FreeSelector(HIWORD(lpMemPtr));

}




[Main code fragment]

This code handles Real, Standard, and Enhanced memory models all at the
same time.


/* Get Memory Model  [Real/Standard/Enhanced] */
       GetWindowsMemoryConfig();


/* If in protected mode, assign LOW memory for use with TSR */
       if (WINDPROTECTEDMODE) {
           /* Assign a memory handle for physical address usage with TSR */
           if(!( hPhysMemHandle = GetPhysicalMemoryHandle())) {
               MessageBoxOKHand ((LPSTR) "Error ??: Could not get Physical Memory for TSR");
               return;
           }
       }

[call tsr-calling routine n times]

/* if in protected mode, return the handle we allocated */
       if ( WINDPROTECTEDMODE) {
           FreePhysicalMemoryPtr(lpPhysPtr);
       }
       return;




[TSR-Calling routine]
static FPTR   near fp;

        if( WINDPROTECTEDMODE) {  /* allocate DOS memory to put data into */
           lpsDosParagraphSelector.d = GlobalDosAlloc((DWORD)max( APImsg.len, 4));
           if(APImsg.buffer) {
                lmemcpy( (LPSTR)MAKELONG(0, lpsDosParagraphSelector.w.sel), APImsg.buffer,
                         max( APImsg.len, 4));
               fp.w.sel = lpsDosParagraphSelector.w.par;
               fp.w.off = 0;
           } else {    /* Null Pointer */
               fp.p = 0L;
           }
        } else {        /* If in real mode */
            fp.p = APImsg.buffer;
        }
        dx = fp.w.sel;
        bx = fp.w.off;
/* Call the tsr */
    rc = int2f(ax, cx, si, di, dx, bx, (unsigned int far *)&di,
        (unsigned int far *)&si, (unsigned int far *)&cx,
        (unsigned int far *)&dx, (unsigned int far *)&bx);

    if(WINDPROTECTEDMODE {
        (void) GlobalDosFree(lpsDosParagraphSelector.w.sel);
    }

    if(WINDREALMODE) {
        fp.w.sel = dx;
        fp.w.off = bx;
    } else {
        lpPhysPtr = MakePhysicalMemoryPtr(hPhysMemHandle, dx /* segment addr */,
                                        bx /* offset */);
        fp.p = lpPhysPtr;       /* use pointer lpPhysPtr */
    }


Glenn Boozer  glenn@imagen.com
QMS  [PostScript is what we do best]

glenn@imagen.com (glenn boozer) (05/18/91)

dave@mgc.uucp (Dave Lockwood) writes:


>This is the second posting - I didn't see any replies the first time :-(
>
>I'm trying to write a Windows 3 application which needs to interface to a
>DOS TSR which communicates via an interrupt. Everything works fine in real
>mode, but I get UAEs and reboots in both standard and 386 enhanced mode.
>
>The code segment that's causing the trouble:
>
>        union   REGS    InRegs;
>        union   REGS    OutRegs;
>        struct  SREGS   SegRegs;
>        HANDLE          hBuffer;
>        LPSTR           lpBuffer;
>
>        InRegs.h.ah = 11;
>        InRegs.h.al = 1;
>        InRegs.x.dx = FP_OFF(lpBuffer);
>
>        hBuffer = GlobalAlloc(GHND,512L);

Does not work in either Protected mode

>        lpBuffer = GlobalLock(hBuffer);
>        InRegs.x.di = FP_OFF(lpBuffer);
>        SegRegs.es = FP_SEG(lpBuffer);
>
>        int86x(0x7f,&InRegs,&OutRegs,&SegRegs);
>
>I've done a lot of RTFM-ing, but to no avail (at least, the problem isn't
>solved). I didn't write the TSR, so it's difficult to get changes done
>there (I don't have the source).

You shouldn't need to change the TSR.

>
>My suspicions are that lpBuffer above is a 'selector' (whatever that is)
>rather than a true, _far address, and when the TSR (assembler) uses the
>ES:DI register pair, some sort of protect trap takes place.

You guessed it!

>
>Any ideas on how to solve this? Do I need to spend $500 on the DDK and write
>a special device driver? Please help - any comments would be appreciated;
>if you did respond via a posting last time, please respond again. My upstream
>site lost some news just about the "right" time :-(.

The DDK is not needed for this.


The problem is that the TSR runs in REAL mode and the windows applications
running in either protected mode use selectors[LDT] and not pointers[SEG:OFF]
to access buffers.

What follows I hastily pieced together from code I have working.

In a nutshell:
       To Send a buffer address to the TSR [Send it a REAL address of
        Segment:Offset, not a protected mode selector]
        1) DosAllocate a memory buffer  [This will be a buffer in the first
           640K of address space.  This buffer is LOCKED and will not move.
           [Not recomended by Microsoft]
        2) Copy the data from the buffer that is in "Windows Application space"
           into the DOS Buffer
        3) Get the Segment and Offset of the DOS buffer and pass that to the
           TSR.
        4) Release the DOS Buffer

       To use the buffer address [Segment:offset] that the TSR send you:
        1) Allocate a Selector [Not recomended by Microsoft]
        2) Set the selector base and length with the data returned from the
           TSR.  [Not recomended by Microsoft]
        3) Use the data
        4) Return the selector.  [Not recomended by Microsoft]


Code fragments follow.  If I left anything out, give me a note.

#define FarPtr far *
/* Data structure for calls to the TSR */
typedef union
    {
    char FarPtr p; /* treat as pointer */
    void FarPtr pv;
    struct /* treat as selector/offset */
        {
            unsigned off;
            unsigned sel;
   } w;
   long l;  /*treat as arithmetic item */
    } FPTR;

/* Data structure for value returned from GlobalDosAlloc*/
typedef union
    {
    struct /* treat as selector and a paragraph */
        {
            WORD sel;
            WORD par;
   } w;
   DWORD d; /*treat as DWORD */
    } DOSPS;

/* Windows kernel calls not in WINDOWS.H */

WORD  FAR PASCAL SetSelectorBase(HANDLE hSelector, DWORD dwBase);
WORD  FAR PASCAL SetSelectorLimit(HANDLE hSelector, DWORD dwLimit);
DWORD FAR PASCAL GlobalDosAlloc(DWORD);
WORD  FAR PASCAL GlobalDosFree(WORD);

#define WIND386ENHANCEDMODE (dwWinMemFlags & WF_ENHANCED)
#define WINDPROTECTEDMODE     (dwWinMemFlags & WF_PMODE)
#define WINDREALMODE        (!(dwWinMemFlags & WF_PMODE))

static DWORD  near dwWinMemFlags;        /* Window Memory configuration Flags */
static DOSPS  near lpsDosParagraphSelector;
static WORD   near hPhysMemHandle;           /* Handle to DOS memory */
static LPSTR  near lpPhysPtr;               /* pointer to DOS memory */

/***************************************************************************
*Name: GetWindowsMemoryConfig                                              *
*                                                                          *
*    Get windows memory configuration word so we can execute Real Mode and *
*    Protected Mode specific code.                                         *
*                                                                          *
*Returns: Nothing                                                          *
***************************************************************************/

void GetWindowsMemoryConfig ()
{
   dwWinMemFlags = GetWinFlags();
}


/***********************************************************************

Module:  GetPhysicalMemoryHandle

   Sets up a physical memory handle which can be used to access memory in
protected mode.
  If the selector cannot be created, the call fails and returns NULL.

  returns: Memory access handle.

**********************************************************************/

WORD FAR PASCAL GetPhysicalMemoryHandle()
{

    HANDLE hSel;
    HANDLE hSel2;


    /*. create a selector for use by MakePhysicalMemoryPtr() */
    /* The how of this is taken from an SR response. */
    if ((hSel2 = GlobalAlloc(GMEM_FIXED,(long) 64)) != NULL) {
        hSel = AllocSelector(hSel2);
        GlobalFree(hSel2);
    } else {
        return (WORD)NULL;
    }
    /* successful:  return a handle */
    return hSel;
}

/***********************************************************************

Module:  MakePhysicalMemoryPtr

   This routine sets up a protected mode pointer to a specified physical
address.  This pointer is good for 4K of memory at the specified physical
segment + offset address.  If NULL, an error occurred.

  returns: Memory access handle.

**********************************************************************/

LPSTR NEAR PASCAL MakePhysicalMemoryPtr(

    WORD wMemHandle,             /* Physical memory handle created by
                                    GetPhysicalMemoryHandle().
                                    a valid selector is passed.            */

    WORD wSegment,               /* Segment address to be referenced.  This
                                    is a real-mode paragraph number.       */


    WORD wOffset                 /* Offset from Segment address to be referenced. */

)
{
    /*. set selector base from wSegment parameter */
    SetSelectorBase(wMemHandle, (((LONG)wSegment)<<4) + wOffset );
    /*. set limit for 4K bytes accessable */
    SetSelectorLimit(wMemHandle,(long) 0x0FFF);
    /*. make and return a long pointer using passed wMemHandle */
    return (LPSTR)MAKELONG(0, wMemHandle);
}

/***************************************************************************

Routine: FreePhysicalMemoryPtr

   Deallocates the specified physical memory pointer.  This call is used to
free a selector in protect mode.  Once this function is run on a pointer,
the physical memory handle which was used to create that pointer
CANNOT BE USED AGAIN.

Returns:  nothing

***************************************************************************/

void FAR PASCAL FreePhysicalMemoryPtr(

    LPSTR lpMemPtr               /* Pointer used for access to physical
                                    memory.  The pointer must have been
                                    constructed by MakePhysicalMemoryPtr().*/

)
{
    /*. free the selector */
    FreeSelector(HIWORD(lpMemPtr));

}




[Main code fragment]

This code handles Real, Standard, and Enhanced memory models all at the
same time.


/* Get Memory Model  [Real/Standard/Enhanced] */
       GetWindowsMemoryConfig();


/* If in protected mode, assign LOW memory for use with TSR */
       if (WINDPROTECTEDMODE) {
           /* Assign a memory handle for physical address usage with TSR */
           if(!( hPhysMemHandle = GetPhysicalMemoryHandle())) {
               MessageBoxOKHand ((LPSTR) "Error ??: Could not get Physical Memory for TSR");
               return;
           }
       }

[call tsr-calling routine n times]

/* if in protected mode, return the handle we allocated */
       if ( WINDPROTECTEDMODE) {
           FreePhysicalMemoryPtr(lpPhysPtr);
       }
       return;




[TSR-Calling routine]
static FPTR   near fp;

        if( WINDPROTECTEDMODE) {  /* allocate DOS memory to put data into */
           lpsDosParagraphSelector.d = GlobalDosAlloc((DWORD)max( APImsg.len, 4));
           if(APImsg.buffer) {
                lmemcpy( (LPSTR)MAKELONG(0, lpsDosParagraphSelector.w.sel), APImsg.buffer,
                         max( APImsg.len, 4));
               fp.w.sel = lpsDosParagraphSelector.w.par;
               fp.w.off = 0;
           } else {    /* Null Pointer */
               fp.p = 0L;
           }
        } else {        /* If in real mode */
            fp.p = APImsg.buffer;
        }
        dx = fp.w.sel;
        bx = fp.w.off;
/* Call the tsr */
    rc = int2f(ax, cx, si, di, dx, bx, (unsigned int far *)&di,
        (unsigned int far *)&si, (unsigned int far *)&cx,
        (unsigned int far *)&dx, (unsigned int far *)&bx);

    if(WINDPROTECTEDMODE {
        (void) GlobalDosFree(lpsDosParagraphSelector.w.sel);
    }

    if(WINDREALMODE) {
        fp.w.sel = dx;
        fp.w.off = bx;
    } else {
        lpPhysPtr = MakePhysicalMemoryPtr(hPhysMemHandle, dx /* segment addr */,
                                        bx /* offset */);
        fp.p = lpPhysPtr;       /* use pointer lpPhysPtr */
    }


Glenn Boozer  glenn@imagen.com
QMS  [PostScript is what we do best]