[Back to MEMORY SWAG index]  [Back to Main SWAG index]  [Original]

Hello All,

Again, interrupts from protected mode. This is an updated version of my
previous article, which, by the way, generated much less respons (none)
than I expected. Where are the BTrieve Programmers, the DesqView API
Writers, the fossil Writers, the .... Maybe they know everything
already. Well then, what has been changed?

* little bugs fixed (memory not freed, SEG does not work, etc.)
* I stated that if you want to pass parameters on the stack you had to
  do low level stuff. This is not necessary. I do everything in high
  level(?) pascal now.
* Point 5 of the first Type of unsupported interrupts was inComplete.
  There's sometimes much more work involved :-(
* A simple Unit is presented, which helps to cut down code size. See
  Appendix A

Compiling Real to protected mode has been very simple For most of us.
Just Compile and go ahead. 99.5% of your code works fine. But the other
0.5% is going to give you some hard, hard work.
   In this article I describe first how I first stuck on the protected
stone. Than I try to give a general overview of problems one might
encounter when using interrupts. Next I describe the solutions or give
at least some hints, and I give a solution to the original Program which
made me aware of protected mode conversion problems. Appendix A lists
the code For a Unit I found usefull when porting my DesqView API to
protected mode.
    References can be found at the end of this article. of course, all
disclaimers you can come up With apply!


When Compiling a big Program, which supported DesqView, a GP fault
occurred. It was simple to trace the bug down: TDX would show me the
offending code. You can get the same error if you try to run the
following Program in protected mode:

========cut here==========================
Program Test;

Function  dv_win_me : LongInt;  Assembler;
Asm
  mov    bx,0001h
  mov    ah,12h
  int    15h       {* push dWord handle on stack *}
  pop    ax        {* pop it *}
  pop    dx        {* and return it *}
end;

begin
  Writeln(dv_win_me);
end.
========cut here==========================

This little Program must be run under DesqView. When run under DesqView
it returns the current Window handle on the stack. BUT: when Compiled
under protected mode NO dWord handle is returned on the stack. So a
stack fault occurs.

What happened? I stuck on one of those unsupported interrupts. Only
supported interupts guarantee to return correct results. You can find a
list of all supported interrupts in the Borland Open Architecture
Handboek For Pascal, Chapter 2 (seperate sold by Borland, not included
in your BP7 package). Supported are the general Dos and Bios interrupts.

BeFore eleborating on supported and unsupported interrupts, I have to
explain a few issues which are probably new to us Pascal Programmers.
Whenever a user interrupt occurs in protected mode (you issue a int xx
call) Borlands DPMI Extender switches to Real mode, issues the
interrupt, and switches back to protected mode.

This works find For most Cases: interrupts which only pass register
parameters work fine. But what happens if you, For example, called the
Print String Function? (int 21h, ah=09h). You pass as parameters ds:dx
pointing to the String to be printed. But, be aware: in protected mode
ds contains not a segment but a selector! and the selector in ds
probably points to an area above the 1MB boundary. These two things are
going to give Real mode Dos big, big problems. Don't even try it!
    So Borland's DPMI Extender does more than just switching from
protected to Real mode when an interrupt occurs: it translates selectors
to segments when appropriate. But, it can only do so For interrupts it
KNOWS that they need a translation. Such interrupts are called
supported. Interrupts about which Borland's DPMI Extender does not know
about are unsupported. and they are going to give you Real problems!

So you see, when only data is passed in Registers, everything works
fine. But if you need to pass Pointers, there is a problem. But why did
the above Program not work? It didn't use selectors you might ask. Well,
there is another set of interrupts that are unsupported: those that
expect or return values on the stack. This is the Case With the above
Program.

So, to conclude:
* supported interrupts
  - simple parameter passing using Registers, no segments/selectors
    or stacks included
  - interrupts which Borland's DPMI Extender knows about (too few For
    most of us)
* unsupported interrupts
  - using segments/selectors
  - involving stacks

In the next two sections I will fix both Types of problems. I make use
of the DPMI Unit, which comes With the Open Architecture Handbook. You
do not need this Unit. As this DPMI Unit is just a wrapper around the
DPMI interrupt 31h, simply looking the interrupts up in Ralph Brown's
interrupts list and writing Functions/Procedures For them, works fine.


Unsupported interrupts which need segments
------------------------------------------

Because the data segment and stack segment reside in protected mode, you
need to allocate memory in Real mode, copy your data (which resides
above 1MB) and issue the interrupt by calling the DPMI Simulate Real
Interrupt. So our to-do list is:
1) allocate Real mode memory
2) copy data from protected mode to Real mode
3) set up the Real mode Registers
4) issue interrupt
5) examine results

1) You can allocate Real mode memory by issueing a GlobalDosAlloc (not
   referenced in the online help, but you can look it up in the
   Programmer's refercence manual) request. The GlobalDosAlloc is in the
   WinApi Unit. For example:

     Uses WinAPI;
     Var
       Return : LongInt;
       MemSize : LongInt;
     begin
       MemSize := 1024;
       Return := GlobalDosAlloc(MemSize);
     end;

   This call allocates a block of memory, 1K in size, below the 1MB
   boundary. The value in Return should be split in LongRec(Return).Lo
   and LongRec(Return).Hi. The Hi-order Word contains the segment base
   address of the block. The low-order Word contains the selector For
   the block.

2) You use the selector to acces the block from protected mode and you
   use the segment of the block to acces the block within Real mode (your
   interrupt).
       For example: we want to exchange messages With some interrupt. The
   code For this would be:
     Uses WinAPI;
     Var
       Return : LongInt;
       MemSize : LongInt;
       RealModeSel : Pointer;
       RealModeSeg : Pointer;
       Message : String;
     begin
       MemSize := 256;
       Return := GlobalDosAlloc(MemSize);
       PtrRec(RealModeSel).seg := LongRec(Return).Lo;
       PtrRec(RealModeSel).ofs := 0;
       PtrRec(RealModeSeg).seg := LongRec(Return).Hi;
       PtrRec(RealModeSeg).ofs := 0;

     {* Both RealModeSel(ector) and RealModeSeg(ment) point to the same

        physical address now. *}

     {* move message from protected mode memory to the allocated selector *}
       Message := 'How are your?';
       Move(Message, RealModeSel^, Sizeof(Message));

     {* issue interupt, explained below *}
     { <..code..> }
     {* the interrupt returns a message *}

     {* move interrupt's message below 1MB to protected mode *}
       Move(RealModeSel^, Message, Sizeof(Message));
       Writeln(Message);     {* "yes, I'm fine. Thank you!" *}
     end;

3) We will now examine how to setup an interrupt For Real mode. Most of
   the time this is transparantly done by Borland's DPMI Extender, but
   we are on our own now. to interrupt Dos, we use the DPMI Function
   31h, 0300h. This interrupt simulates an interrupt in Real mode.

   The Simulate Real Mode Interrupt Function needs a Real mode register
   data structure. We pass the interrupt and the Real mode register data
   structure to this Function, which will than start to simulate the
   interrupt.
       This Function switches to Real mode, copies the contents of the
   data structure into the Registers, makes the interrupt, copies the
   Registers back into the supplied data structure, switches the
   processor back to protected mode and returns. Voila: you are in
   control again.
       Maybe you ask: why need I to setup such a data structure? Why can
   I not simply pass Registers? Several reasons exist, but take For
   example the RealModeSeg of the previous example. You cannot simply
   load a RealModeSeg in a register. Most likely a segment violation
   would occur (referring to a non existing segment or you do not have
   enough rights etc.). ThereFore only in Real mode can Real mode
   segments be loaded.

       The data structure to pass Registers between protected and Real
   mode can be found in the DPMI Unit which I Repeat here:

     Type
       TRealModeRegs = Record
         Case Integer of
           0: (
               EDI, ESI, EBP, EXX, EBX, EDX, ECX, EAX: LongInt;
               Flags, ES, DS, FS, GS, IP, CS, SP, SS: Word);
           1: (
               DI,DIH, SI, SIH, BP, BPH, XX, XXH: Word;
               Case Integer of
                 0: (
                     BX, BXH, DX, DXH, CX, CXH, AX, AXH: Word);

                 1: (
                     BL, BH, BLH, BHH, DL, DH, DLH, DHH,
                     CL, CH, CLH, CHH, AL, AH, ALH, AHH: Byte));
         end;

   This looks reasonably Complex, doesn't it! More simply is the
   following structure (found in, For example, "Extending Dos" by Ray
   Duncan e.a.)
   offset  Lenght Contents
   00h     4      DI or EDI
   04h     4      SI or ESI
   08h     4      BP or EBP
   0Ch     4      reserved, should be zero
   10h     4      BX or EBX
   14h     4      DX or EDX
   18h     4      CX or ECX
   1Ch     4      AX or EAX
   20h     2      CPU status flags
   22h     2      ES
   24h     2      DS
   26h     2      FS
   28h     2      GS
   2Ah     2      IP (reserved, ignored)
   2Ch     2      CS (reserved, ignored)
   2Eh     2      SP (ignored when zero)
   30h     2      SS (ignored when zero)

   In the following example, I set the Registers For the above message
   exchanging Function. It's best to clear all Registers (or at least
   the SS:SP Registers) beFore calling the Simulate Real Mode Interrupt.

     Uses DPMI;
     Var
       Regs : TRealModeRegs;
     begin
       FillChar(Regs, Sizeof(TRealModeRegs), #0); {* clear all Registers *}

       With Regs do  begin
         ah := $xx;
         es := PtrRec(RealModeSeg).Seg;
         di := PtrRec(RealModeSeg).ofs
       end;  { of With }
     end;

   All this is fairly standard. Just set up the Registers you interrupts
   expect, very much like the Intr Procedure.

4) We can now issue the interrupt in Real mode using the RealModeInt
   Procedure (in the DPMI Unit). Its definition is

     Procedure RealModeInt(Int: Byte; Var Regs: TRealModeRegs);

   or you can call int 31h, Function 0300h, see Ralph Brown's interrupt
   list.
   For our message exchanging Program it would simply be:
     RealModeInt(xx, Regs);

5) Examine the results. Modified Registers are passed in the Regs data
   structure so you can check the results.
       It is necessary to discriminate between to Types of returned
   segments. In the example above, I assumed that the Interrupt returned
   data in the allocated memory block. I already have a selector For
   that block, so I can examine the results.
   Another Type of interrupt returns Pointers to segments it has
   allocated itself. As we don't have a selector For that memory block
   we have to create one. We need the following Functions:
   - AllocSelectors, to allocate a selector
   - SetSelectorBase, to let it point to a physical address
   - SetSelectorLimit, to set the size
   An example For this situation: Assume that a certain interrupt
   returns a Pointer to a memory area. This Pointer is in es:di.
   Register cs contains the size of that memorya rea. I show you how to
   acces that segment.

     Uses DPMI;
     Var
       Regs : TRealModeRegs;
       p : Pointer;
     begin
     {* setup  Regs *}
     {* issue interrupt, returning es:di *}

     {* as we don't have a selector, create one *}
       PtrRec(p).Seg := AllocSelectors(1);
       PtrRec(p).ofs := 0;

     {* this selector points to no physical address and has size 0 *}
     {* so let the selector point to es:di *}
       SetSelectorBase(PtrRec(p).Seg, Regs.es*16+Regs.di);

     {* Forgive me! This was a joke. The last statement does not work   *}
     {* of course. Regs.es*16+Regs.di will in the best Cases ({$R+,Q+}) *}
     {* result in an overflow error. You have to Write:                 *}
       SetSelectorBase(PtrRec(p).Seg, Regs.es*LongInt(16)+Regs.di);

     {* the selector now points to a memory area of size 0 *}
       SetSelectorLimit(PtrRec(p).Seg, Regs.cx);

     {* we don't have to set the accesrights (code/data, read/Write, etc. *}
     {* as they are almost ok *}

     {* we can now acces this memory using selector p *}
     { <acces block> }

     {* after using it, free selector *}
       FreeSelector(PtrRec(p).Seg);
     end;


Are there any questions? No? Let's go ahead than to the next Type of
interrupts.

Unsupported interrupts which use the stack
------------------------------------------
The second Type of unsupported interrupts are the ones which make use of
the stack. We can distinguish between:
1. interrupts which need parameters on the stack
2. interrupts which return parameters on the stack

1) For the first Type we need to setup a stack. There is an extra
   Compilication, which I had not told yet. As the stack in protected
   mode resides in a protected mode segment it is unusable For the Real
   mode interrups. So Borland's DPMI Extender switches from the
   protected to a Real mode stack (and back). We can supply a default
   Real mode stack if we set the stack Registers (ss and sp) in the Real
   mode register data structure to zero. else it is assumed that ss:sp
   points to a Real mode stack. Failure to set them up properly could
   have disastrous results!

   We will have to do:
   1) create a Real mode stack using GlobalDosAlloc
   2) fill this stack With values
   3) set ss and sp properly
   4) issue interrupt

   All in one example Program. The following Program sets DesqView's
   mouse on a given location on the screen. The supplied handle is the
   handle of the mouse. As DesqView needs dWord values on the stack I
   allocated a LongIntArray stack which is defined as:

     Const
       MaxLongIntArray = 1000;
     Type
       PLongIntArray = ^TLongIntArray;
       TLongIntArray = Array [0..MaxLongIntArray] of LongInt;

   The example Program:

     Procedure SetMouse(Handle, x, y : LongInt);
     Const
       StackSize = 3*Sizeof(LongInt);
     Var
       Regs : TRealModeRegs;
       Stack : PLongIntArray;
       l : LongInt;
     begin
     {* clear all Registers *}
       FillChar(Regs, Sizeof(TRealModeRegs), 0);

     {* setup the Registers *}
       Regs.ax := $1200;
       Regs.bx := $0500;

     {* allocate the stack *}
       l := GlobalDosAlloc(StackSize);

     {* set stacksegment register sp. ss should be set to the bottom of *}
     {* the stack = 0 *}
       Regs.sp := LongRec(l).Hi;
       Stack := Ptr(LongRec(l).Lo, 0);

     {* fill the stack *}
       Stack^[0] := Handle;
       Stack^[1] := y;
       Stack^[2] := x;

     {* issue the interrupt *}
       RealModeInt($15, Regs);

     {* free the stack *}
       GlobalDosFree(PtrRec(Stack).Seg);
     end;

2) Looks much like solution above. if only values are returned on the
   stack. Don't Forget to set sp to the top of the stack. In the above
   example settings Regs.sp := StackSize;
       An example is given below, where a solution to my original
   problem is given.


Solution For the dv_win_me Procedure:

  Uses DVAPI, Objects, WinApi, WinTypes, DPMI;

  Function dv_win_me : LongInt;
  Const
    StackSize = Sizeof(LongInt);
  Var
    Regs : TRealModeRegs;
    RealStackSeg : Word;
    RealStackSel : Word;
    l : LongInt;
  begin
  {* clear all Registers *}
    FillChar(Regs, Sizeof(TRealModeRegs), #0);

  {* allocate a 1 dWord stack *}
    l := GlobalDosAlloc(StackSize);
    RealStackSeg := LongRec(l).Hi;
    RealStackSel := LongRec(l).Lo;

  {* clear the stack (not necessary) *}
    FillChar(Ptr(RealStackSel, 0)^, StackSize, #0);

  {* set Registers *}
    With Regs do  begin
      bx := $0001;
      ah := $12;
      ss := RealStackSeg;
      sp := StackSize;
    end;  { of With }

  {* perForm Real mode interrupt *}
    RealModeInt($15, Regs);
    dv_win_me := PLongInt(Ptr(RealStackSel, 0))^;

  {* free the stack *}
    GlobalDosFree(PtrRec(RealStackSel).Seg);
  end;

  begin
    Writeln(dv_win_me);
  end.


You see, code size bloats in protected mode! (ThereFore Borland gave us
16MB....)


Appendix A.
-----------

As promised, some routines I found usefull when working With Real mode
segments.

====================cut here====================
Unit DPMIUtil;

Interface

Uses Objects, DPMI;

Const
  MaxLongIntArray = 1000;
Type
{* this Type is usefull For DesqView stacks *}
  PLongIntArray = ^TLongIntArray;
  TLongIntArray = Array [0..MaxLongIntArray] of LongInt;


{* clear all Registers to zero *}

Procedure ClearRegs(Var Regs : TRealModeRegs);

{* allocate memory using GlobalDosAlloc and split the returned *}
{* LongInt into a protected mode Pointer and a Real mode segment *}

Function  XGlobalDosAlloc(Size : LongInt; Var RealSeg : Word) : Pointer;

{* free memory *}

Procedure XGlobalDosFree(p : Pointer);


Implementation

Uses WinAPI;

Procedure ClearRegs(Var Regs : TRealModeRegs);
begin
  FillChar(Regs, Sizeof(TRealModeRegs), 0);
end;

Function  XGlobalDosAlloc(Size : LongInt; Var RealSeg : Word) : Pointer;
Var
  l : LongInt;
begin
  l := GlobalDosAlloc(Size);
  RealSeg := LongRec(l).Hi;
  XGlobalDosAlloc := Ptr(LongRec(l).Lo, 0);
end;

Procedure XGlobalDosFree(p : Pointer);
begin
  GlobalDosFree(PtrRec(p).Seg);
end;

end.  { of Unit DPMIUtil }
====================cut here====================


Example code how to use it. The above dv_win_me routine would look like:

  Uses DVAPI, Objects, WinApi, WinTypes, DPMI;

  Function dv_win_me : LongInt;
  Const
    StackSize = Sizeof(LongInt);
  Var
    Regs : TRealModeRegs;
    Stack : PLongIntArray;
  begin
  {* clear all Registers *}
    ClearReges(Regs);

  {* allocate a 1 dWord stack *}
    Stack := XGlobalDosAlloc(StackSize, Regs.ss);

  {* set Registers *}
    Regs.bx := $0001;
    Regs.ah := $12;
    Regs.sp := StackSize;

  {* perForm Real mode interrupt *}
    RealModeInt($15, Regs);
    dv_win_me := Stack^[0];

  {* free the stack *}
    XGlobalDosFree(Stack);
  end;

  begin
    Writeln(dv_win_me);
  end.

Compare this to the previous code. It just looks a bit prettier
according to my honest opininion.


Conclusion
----------

As you saw, the switch from Real to protected mode may be rather
painfull. I hope With the above examples and explanations you can make
it a bit more enjoyable. One question remains: why did Borland not
clearly told us so? Why not present a few examples, warnings, etc.?
Maybe RiChard Nelson can answer this questions For us. Everything he
says is his private opinion of course, but a look in the kitchen could
be worthWhile.

if you still have questions, I'm willing to answer them in either
usenet's Comp.LANG.PASCAL or fidonet's PASCAL.028 or PASCAL. I can't
port your library of course but if the inFormation presented here is not
enough, just ask.



References
----------
- The usual Borland set of handbooks

- "Borland Open Architecture Handbook For Pascal", sold separately by
Borland,
  184 pages.

- "Extending Dos, a Programmer's Guide to protected-mode Dos", Ray
  Duncan, Charles Petzold, andrew Schulman, M. Steven Baker, Ross P.
  Nelson, Stephen R. Davis and Robert Moote. Addison-Wesly, 1992.
  ISBN: 0-201-56798-9

- "PC Magazine Programmer's Technical Reference: The Processor and
   Coprocessor", Robert L. Hummel. Ziff-Davis Press, 1992.
  ISBN: 1-56276-016-5


  { Dunno if this came before or after this message :) }

Hello Protectors,

of course, a few hours after my message has been released to the net,
bugfixes seem necessary )-:

Some minor bugfixes:

* In the example about allocating memory below the 1MB, memory is
  allocated but not released. As we have only 1MB down their, this can
  become a problem ;-)
  Fix:  adding the statement
    GlobalDosFree(RealModeSel);
  will clean things up

 * The solution to interrupts which requires parameters passed on the
  stack has a bug. The
    les  di,Regs
  statement does not work of course. Replace by
    mov  di,ofFSET Regs
    mov  dx,SEG Regs
    mov  es,dx
  This does not work when Regs is declared in the stack segment (well
  done Borland....), you encounter bug number 16, just as I did.... (see
  next message)



[Back to MEMORY SWAG index]  [Back to Main SWAG index]  [Original]