[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]