Due to popular demand, a few hints how to manage 32 bit pmode with
Delphi 2 and DOS.

1. How is the adress space organized?

Hmm... where to start?

Well, the new thing on a 386+ CPU is a thing called "paging".

To understand paging you need to understand that there is a difference
between LINEAR and PHYSICAL addresses. Physical addresses are those used
by the CPU to address physical memory on your main board. If there was
no paging, they would map identically to linear address space, right?

Now, assume, there actually _is_ paging enabled. This would mean, that
ANY address your program generates while reading/writing memory or
executing code is a linear address that would not neccessarily be the
equivalent of the "real", physical address. The CPU uses some sort of
an address translation table (which is maintained by the OS or the DOS
extender) to generate physical addresses.

This, BTW, is how it is possible to run more than one DOS box at a
time in Windows. Every DOS box has a linear address space starting
at linear address 00000000.

It's obvious that they cannot map to the same physical memory, isn't it?

However, this is not a thing you have to worry about unless you decide
to write an operating system or a DOS extender or such...

Now we know how it's possible to have the full 4G address space for
each application in flat memory model as it is used in WIN 95/NT.
This is done by giving each application an unique set of those
address translation tables.

What about the segment registers, you may ask?

Well, in WIN 95/NT they are loaded with descriptor values that
have a linear base of 00000000 also. (this applies to CS:,DS:,SS: and
ES:. FS: usually too, but there are things like TLS I do not want to go
deeper into...)

So every offset address (a pointer in Delphi 2 is NEAR and nothing else
than an offset) is a linear address too.

Things get different while running under a DOS extender. The base
adresses of CS:, DS:... are unique though, but they are not 00000000.

Even worse, the linear start address may change during program
execution. This is, because the DPMI host is allowed to move the
segment around in memory if you decide to resize it. Resizing the
segment may (but need not) happen anytime a heap object is
created/destroyed. The above section about paging may have told you
already that there is no physical memory moved around.
The OS merely modifies the address translation tables for the
current process.

You also don't have to take care of this fact unless you want to do
direct memory read/write (VIDEO...), since the DOS extender already is
managing anything else for you in almost no time.

2. But what if I actually _want_ to do direct mem and stuff...?

Well, first of all, I suggest, you grab the DPMI 0.9 spec from somewhere
so a lot of questions will be self- answering.

Problem here is that I'm instanly thinking in terms of assembler, not
pascal as a majority of you may do. So there are cases where I would
say "no problem at all" and hacked in the ASM straight from mind.

So, my explaination may appear a bit confusing to others. Anyway, keep
in mind that there is almost no problem that cannot be solved with inline
assembly!

However, personally I use to create a descriptor that actually has a base
of 00000000 and a limit of 4G so I can access all memory using offsets
only. If you like to know how this is done, have a look into CRT.PAS.
If you get no idea anyway, just move the "huge_selector" thing from
"implementation" to "interface" and it will be public.

(SECRET HINT: I've done this for you already, have a look at the source code...
 Psssttt, don't tell anybody... :)

Now, as an example, instead of saying:

mem[$A000:0000]:=color;

just say:

asm
 push gs
 mov  gs,huge_selector
 mov  edx,0a0000h
 mov  al,color
 mov  gs:[edx],al
 pop  gs
end;

Here's the well known putpixel in mode $13:

procedure putpixel (X,Y: Integer; Color: Byte);
begin
  asm
    mov	eax,Y
    mov edx,X
    shl	eax,6
    push gs
    add edx,eax
    mov	gs,huge_selector
    mov	cl,Color
    mov	gs:[edx+eax*4+0a0000h],cl
    pop	gs
  end;
end;

I reordered the instructions a bit to take care of the CPU pipeline(s).

LEAN, MEAN, DIRTY HINT: Unless you compile with stack frames enabled,
the compiler will try to pass arguments in CPU registers. The order is
well defined: 1st parameter in EAX, 2nd in EDX, 3rd in ECX.
It'll be left to you as a challenge to speed up the above routine...

It actually may (and, in most cases, will) work to load GS: with
the selector once and use it all the time, so you save the cycles
spent on saving/loading/restoring a segment register in PM.

You can load even ES: and FS: with another selector, yeah, you even
could change DS: if only the local stack is referenced but you _will_
have to save/restore them since DS:, ES: and FS: are used during program
execution while GS: usually isn't.

Anyway, the above putpixel should also work with DS: instead of GS:
and be one cycle faster or so...

LINEAR FRAME BUFFER (VESA)

This one may be interesting for some of you since you couldn't do it
in 16 bit pascal. I've not tried it yet since I have no VESA 2.0 card
here (shame on me), but there's no reason why it shouldn't work:

- Use DPMI "MAP PHYSICAL REGION" with base address and size of the LFB
  as you received it from VESA BIOS, a linear address will be returned.

- Now you have the choice: use either the above huge_selector and the
  linear address as an offset or create a new descriptor to access the LFB.



Hope this helps a bit.


Wuschel
