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 *} { } {* 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)