This is the documentation for the interface for Borland C++ 4.0 32bit

advertisement
This is the documentation for the interface for Borland C++ 4.0 32bit
compiler to PMODE 3.0 protected mode interface kernel, henceforth
referred to
as PMC. All of PMODE and most of this interface were coded by Tran
(a.k.a.
Thomas Pytel). Feel free to use or distribute it in any manner you wish.
All I
ask, if you use PMC in some production, is credits for it.
----------------------------------------------------------------------------Contents:
--------0 - Introduction
0.0 - Disclaimer
0.1 - Description
0.2 - Acknowlegements
1 - Overview
1.0 - Components
1.1 - Structure
1.2 - Protected mode
1.3 - Memory
1.4 - Negative offsets
2 - Assembly
2.0 - Segments
2.1 - C types
2.2 - Calling conventions
2.3 - ASM programs
3 - Stack
3.0 - Main stack
3.1 - DPMI stacks
3.2 - Nested mode stacks
4 - Mode switching
4.0 - DPMI mode switching
4.1 - PMC mode switching
5 - IRQs
5.0 - Speed
5.1 - IRQ/INT C code
5.2 - Code stubs
6 - Variables
6.0 - Startup variables
6.1 - Runtime variables
6.2 - PMC nested stacks
7 - Macros
7.0 - Pointer macros
7.1 - Other macros
8 - Functions
8.0 - DPMI functions
8.1 - Memory block functions
8.2 - Memory functions
8.3 - String functions
8.4 - File functions
8.5 - IRQ and mode switch functions
8.6 - Other functions
8.7 - Low level functions
8.8 - Code stubs
9 - Miscellaneous
9.0 - Notes
9.1 - Final word
----------------------------------------------------------------------------0 - Introduction:
----------------This document will not attempt to explain the workings of protected
mode.
Neither will I teach you ASM or C. I assume you have a working knowlege
of
both. You do not necessarily need to know ASM, but it would help. If,
however,
you do not know C, why are you trying to use PMC? Go out and learn C
first.
You should read the PMODE documentation before this to get an
understanding of
how the whole protected mode system will work. I will not go into details
about the variables and functions which perform many of the system
functions
until their respective sections.
0.0 Disclaimer:
--------------Legal:
I exclude any and all implied warranties, including warranties of
merchantability and fitness for a particular purpose. I make no warranty
or
representation, either express or implied, with respect to this source
code,
its quality, performance, merchantability, or fitness for a particular
purpose. I shall have no liability for special, incidental or
consequential
damages arising out of or resulting from the use or modification of this
source code.
English:
You can probably guess...
0.1 Description:
---------------PMC is basically all that is needed to run code generated by BCC32
(Borland
C++ for Win32) in full 32bit protected mode under DOS. This is possible
because BCC32 is simply a C/C++ compiler. It converts C/C++ statements
into
32bit object code. Its principle task, in Borland's view, is to compile
C/C++
modules for running under Win32. The compiler itself knows nothing of the
environment the code it generates will be running under. This makes it
possible to use its code anywhere 32bit code can be used. There is
nothing
really Win32 specific to it.
The fact that BCC32 was intended for Win32 does have one drawback. The
32bit
libraries provided are designed to be run under Win32. This makes them
useless
to DOS execution because they use Win32 system calls. Those small
functions
from the default 32bit libraries that do not use system calls can
probably be
called. But you have to be very careful and should examine them before
trying
to use them from DOS. I will personally avoid the default libraries
completely. Not being able to use the BC4 libraries has another drawback,
you
will probably not be able to use floating point types.
PMC contains a small library of the most commonly used and needed
system
functions. Some ANSI C compliant functions are provided, but overall, PMC
is
not ANSI compatible. Malloc functions are available. As are many low
level
string and memory block functions. PMC also provides some DPMI functions,
some
non-ANSI file functions, and low level IRQ functions. There are low level
functions for fast mode switching using the raw mode switching services
of
whatever DPMI host it is running under.
I designed the PMC interface and library to serve as a small, tight,
kernel
for other C code to be built around. My primary use for it will be games,
demos, and many other things which do not really use standard C
functions. But
the low level functions provided in PMC.LIB will find much use in almost
any
type of program that is done.
PMC allows flat access to low and extended memory. You need not deal
with
segments or near and far pointers. In fact, BCC32 does not recognize the
near
or far keywords. Memory is split into two pools, a low memory pool and an
extended memory pool. This is transparent to your code though, as the
malloc
routines check both pools for any memory requests. You can deal
explicitly
with the low or high memory heaps if you wish. You may need a low memory
DMA
buffer for example.
The extender used is PMODE 3.0. It is about 9k of code and it is
internal to
the EXE file which is created. For more information on it, you should
read the
PMODE documentation. The programs created with PMC will run on any 386+
system
which is running as a clean system, under XMS, VCPI, or DPMI. This covers
all
memory managers and Windows and OS/2. In addition, the PMODE extender is
very
fast. I should know, I wrote for that purpose.
0.2 - Acknowlegements:
---------------------Most of PMC was coded by myself (Tran). But I must give thanks to Dave
Cooper for the low level malloc routines. His coding of those saved me a
bit
of time. Though he did not have to be quite so insistent about the
debugging
information in the memnode headers.
Also, major thanx to Dave Cooper and Rich Geldreich for help in
squashing
all the nasty little creepy crawly insects in this thing.
Hey Borland... Get some good programmers! You must have gotten some CS
majors fresh out of college, who have never coded the PC in their lives,
to
code many parts of BCC 4.00. Most of the bugs are really stupid and look
like
the programmer's knowlege of PC ASM is trivial at best. Keep up the great
work
guys (a little sarcasm here)... There is good code in BC4, after all, a
C++
compiler is not the easiest program in the world to code. If only all of
the
programmers on this thing were as good as the main coders. Too bad
inexperience has to screw up this nice compiler. Due to that, I can say
there
are almost definately fewer bugs in all of PMC than in BC4.
I mean really... setnle is SIGNED! __memcmp__ is UNSIGNED! The two do
not
belong together. And why don't you try updating your code once in a
while. Its
been quite some time since the bus recognized only 10 bits of a port
address.
I am referring to __inportb__ here.
Why did I use BC4 then? Because I have it, and I use it, and it does
ok. But
if you wish to use PMODE 3.0 as an extender for your own C/C++ compiler,
feel
free. I know of a certain 200k+ extender that might be cut down to size.
----------------------------------------------------------------------------1 - Overview:
------------I created PMC mainly for my own use. Until now, I have been coding
almost
exclusively in 32bit ASM. But now that I have a C compiler capable, and
an
extender I can link to it, I am going back to C. PMC is designed with
execution speed in mind. It is meant to be used for DOS demos, games,
etc...
It can be used for anything though. The loss of full ANSI C compatibility
is
not a big problem for me.
1.0 - Components:
----------------PMC consists of three main parts. The startup header code, the
protected
mode extender, and the main library. These three pieces work together to
allow
32bit C execution. The startup header goes into every C/C++ EXE created
and
is responsible for initializing and terminating the protected mode
system. The
extender provides some DPMI services if a DPMI host is unavailable. The
main
library provides some commonly used C functions for use by your C/C++
code.
The startup header is what first gets control when the program is
executed.
It will attempt to switch the system into protected mode if there is
enough
low memory. If this is unsuccessful, it will immediately terminate with
an
appropriate error message. If successful, the header will go on to set up
the
system for C code execution. Free memory will be checked, as will the DOS
version. Low and high memory pools will be set up. Then a low memory
buffer
will be allocated if one is needed, as well as real mode and protected
mode
nested mode switch call stacks. When the system is set up, the startup
code
calls the function PMmain() which must be defined in your code. This is
the
point where your program gets control. You may terminate execution by
either
returning from PMmain() or calling the exit() function. The byte returned
by
PMmain() or passed to the exit() function is the exit code that will be
passed
to the DOS INT 21h AH=4ch terminate function.
The protected mode extender is my own piece of code. It is very small,
fast,
clean, and stable. Which is more than I can say for many DPMI hosts out
there.
It is not fully DPMI compliant, but it is enough for most purposes (such
as
PMC). The source code is included so that you may put it through your own
scrutiny. In fact, all PMC sources are included. If you suspect a bug,
you
can check it out yourself. PMODE can be used as a standalone extender for
other things besides PMC. For more information, read the PMODE dox.
The main library file is a collection of individually compiled modules
which
do many basic things like string and memory functions and file I/O.
During
linking, any module within a LIB which is not used by some other part of
a
program, is not included in the EXE produced. This means, if you do not
use
the strcat() function in your code, it will not be linked into your EXE.
The
library is provided so that anyone using PMC can assume at least a small
standard set of functions always available. Its nothing like ANSI C/C++,
but
hey, I don't need much more than what is here.
1.1 - Structure:
---------------BCC32 generated C/C++ code runs in full 32bit protected mode assembly
using
PMC. All the code is located in one large segment with a size of 4G. All
functions and labels are 32bit linear addresses. They do not have to be
referenced with a seperate code selector. All data and the stack likewise
reside in a 4G segment and are referenced with 32bit pointers. The code
and
data segments do not have the same base address. This means that a 32bit
data
pointer addresses a different linear memory location if used with the
code
selector. The code and data segments do overlap however, so you can
address
code using the data selector. To do this you just have to adjust the
pointer
by the difference in the base address of the code and data segments. PMC
provides macros for doing this quickly.
In the main thread of execution you can assume that DS = ES = SS, and
this
is the way it must be. If you are familiar with C, you will realize that
this
immediately solves the DS != SS problem while allowing access to large
data
areas. Since this is 32bit protected mode, both 32bit stack and
instruction
pointers are used. This means the runtime stack, as well as all the code,
can
be larger than 64k. In the main thread of execution you can also assume
the
direction flag is clear, and you must keep it that way. If you set the
direction flag for a memory move, you should clear it immediately after.
The
FS and GS segment registers are not used during execution by C/C++ code.
They
are yours for whatever purpose you wish. Be aware that FS and GS are may
not
be preserved by PMC library functions or PMC calls to real mode.
The code and data segments always reside in low memory, though you may
allocate and use extended memory for code and data at runtime. The good
thing
about keeping code in low memory is the fact that under DPMI this memory
is
always locked. DPMI hosts which support virtual memory will never swap
out
your IRQ handlers or stack segment.
1.2 - Protected mode:
--------------------Running in protected mode makes accessing real mode DOS and BIOS
services a
little more complex. You can not simply call the necessary INT functions
in
most cases. You will probably have to use DPMI mode translation services
to
access real mode functions. This will be necessary for real mode services
which require buffer pointers in segment registers. For any service that
does
not require segment registers to be passed, you may use a simple INT
within
your protected mode code. You should read the PMODE documentation to get
a
better understanding of how this works.
In cases where you have to pass a buffer address to a real mode
service, you
must make sure that buffer is in low memory. The real mode system can not
use
extended memory. If you wish to read from a file to extended memory, you
would
have to read that data to a buffer in low memory, then copy it from there
to
extended memory. But do not worry, the file functions included in PMC do
this
for you. Any filename or read/write address you pass will be buffered
through
low memory if it needs to be. The whole thing is transparent to the code
using
the file functions.
There is a specific low memory buffer allocated at startup for use by
code
which accesses real mode services. The alternative to doing this kind of
buffering yourself, is to have a DPMI host which remaps any real mode
calls
which expect buffer addresses itself. This is not a very good method in
my
mind. Its main use is to make real mode code more portable to protected
mode.
If you are writing protected mode code from scratch, it makes more sense
to do
the buffering within your own functions. If the DPMI host must do it, it
has
the difficult task of determining which real mode calls require buffers,
in
which registers, moving memory to or from, etc... The DPMI host may not
cover
all possible real mode functions. This also affects the size of the DPMI
code,
it becomes very large. Needless to say, I prefer to do the buffering
myself.
It also doesn't cover little known or your own real mode functions. In
those
cases, you would have to do the buffering yourself anyway.
1.3 - Memory:
------------The memory space in protected mode is divided into two pools, a low
memory
pool and an extended memory pool. The two areas are seperated by the ROM
BIOS
area of the PC. This is not really a problem for C code. The malloc
routines
check both pools when looking for memory. The only affect this seperation
has
is to disallow the size of the largest block of memory from including
both
low and extended memory. You may never have to worry about high or low
memory,
but just use the PMC library functions.
Having a specific low memory pool has an advantage. Low memory is
needed for
talking to real mode DOS and BIOS. Low memory is also needed for the low
DMA
channels. Unless you specifically request low memory, the malloc routines
will
check the extended memory pool first and low memory pool after. This will
allow low memory to be used only when extended memory has ran out,
preserving
it as long as possible for possible explicit needs.
1.4 - Negative offsets:
----------------------Until recently, I have been using negative 32bit offsets for accessing
data
below the start of a protected mode segment. This has never been a
problem, as
the segment size is 4G, which caused the negative offset to wrap around
to
below the segment. The very large (negative) 32bit values never cause an
exception because the segment is as large as it possibly can be. And they
never reach into unmapped page areas because after combining the 32bit
offset
with the segment base address, the processor winds up with a linear
address
below the start of a segment. In effect, the same general area, not way
up in
memory space.
But now, I have found out this does not work under OS/2. OS/2 seems to
allow
setting a segment to a size of 4G for compatibility reasons. However, it
does
not seem to actually set the descriptor limit that high. This causes
exceptions when attempting to use very large 32bit offsets. This is a
major
setback in my mind. The negative 32bit offsets allowed for usage of 32bit
flat
mode using normal compilers and linkers. What OS/2 does, is force you to
use
another selector for accessing data below the actual data segment.
Without
special runtime linking it is not possible to run truly flat under OS/2.
It is
not possible to use negative offsets under OS/2 successfully, though
under
everything else it will work. So if you don't care for OS/2
compatibility, go
ahead and use them. Note that what I mean by negative offsets, is values
which
will cause the effective calculated address go below zero. It does not
mean
you can't use normal negative offsets such as 'mov eax,[ebp-4]'. Unless
EBP is
less than 4, the previous instruction does not qualify as the kind of
negative
offset I am talking about.
----------------------------------------------------------------------------2 - Assembly:
------------Interfacing and using assembly with BC4 code in protected mode is very
easy,
whether it is through inline assembly or external ASM modules. The main
simplifying factor is that memory is flat. There are not multiple code
and
data segments to deal with. All code/functions reside in one segment, and
all
data and the stack in another. All function calls are near, and all
returns
are via the RET instruction.
2.0 - Segments:
--------------When using external ASM modules, there are three segments you have to
deal
with. The first is the code segment. It is called '_TEXT' and it is a
public
use32 segment of class 'CODE'. All your assembly code goes in this
segment.
Any procedures you make public from this segment will be directly
callable
from C/C++ code. Any C functions can likewise be called directly from
your
ASM code. All addresses in this segment are 32bit.
The other two segments are actually part of a group. This makes them
usable
as one segment. The difference between the two is that one is for static
data
and the other for data space to be allocated at runtime. The static data
segment is '_DATA' and is a public use32 segment of class 'DATA'. Any
data
which you want to have a specied value at runtime goes in here. This
includes
strings, tables, and individual variables. The uninitialized data segment
is
'_BSS' and is a public use32 type of class 'BSS'. Any data which you do
not
need initialized at runtime, or which you want initialized to 0, goes in
here.
You do not need to put uninitialized data in the BSS segment, but it will
save
some space in the final EXE. The entire BSS segment is initialized to 0
at
runtime.
Other than using these segments for grouping of code and data, you
should
not try to access the segment names directly in your code. Instead, you
should
just use the CS, DS, ES, and SS selectors from the C functions. If you
need
the selector values of these segments for something that does not get
control
from C code, you may get them from the data segment. The code and data
selectors are stored in variables at startup. These variables, along with
some
others, are in the DATA segment. They are always within the first 64k of
the
segment. This makes them accessible to any real mode code you may have
thrown
in for any sick reason you might have for using real mode code (just
kidding).
Be aware that any global data defined in C/C++ modules is made public
to all
other modules. Your assembly code may access these variables by name, but
you
must prepend a '_'. So for example, a C global variable 'var', would be
accessed from ASM code as '_var'.
2.1 - C types:
-------------You know the basic C types - char, int, short, long, etc... The size of
the
basic int type is defined as the size of the default machine word of the
system the C code is running under. In 16bit real mode, that size was 16
bits.
Thus, ints were 16bit values. But in 32bit protected mode, that size is
now
32 bits. In 32bit C, signed ints take the range of values from 2147483648 to
2147483647. Unsigned ints obviously range from 0 to 4294967295. The size
of
short ints and long ints has not changed from 16bit real mode though.
Short
ints are still 16 bits, and long ints are now the same as regular ints,
32
bits. Characters are still one byte in length.
The default size of the int is also the default size of a pointer type
in C.
A void * is a 32bit value in 32bit protected mode. This means it can
address
up to 4G of memory space. Most internal calculations are performed in
terms of
32bit values. Also, parameters passed to and returned from functions are
always 32bit values.
To summarize:
Type:
char
unsigned char
short
unsigned short
long
unsigned long
int
unsigned
Bits:
8
8
16
16
32
32
32
32
Range:
-128 to
0 to
-32768 to
0 to
-2147483648 to
0 to
-2147483648 to
0 to
127
255
32767
65535
2147483647
4294967295
2147483647
4294967295
2.2 - Calling conventions:
-------------------------If you wish to interface ASM modules with C, you must understand the
way in
which C calls functions. You must know how the name of the function must
be
formatted. You must also know how parameters are passed to and returned
from
the function. BC4 supports several calling conventions. I will explain
three
of them here. The C calling convention '__cdecl'. The pascal calling
convention '__pascal'. And the fast calling convention '__fastcall'.
In the C calling convention, all parameters are pushed onto the stack
before
the call. They are pushed from right to left. That is, the last parameter
to
the function is pushed first, and the first parameter is pushed last. The
stack is cleaned up by the code that called the function. The function
name is
modified by prepending a '_' to the function name. The C function must
preserve the EBX, ESI, EDI and EBP registers. The EAX register is used to
return a value, be it a char, int, or pointer.
Function:
int __cdecl function (int a, long b, short c, char d, void *e);
ASM call logic:
push *e
order
push d
; push parameters in reverse
push c
push b
push a
call near ptr _function
add esp,4*5
; call the function
; adjust the stack
The stack is cleared right after the function call. EAX contains the
return
value for functions that return a value. Otherwise, EAX is destroyed.
Only the
EBX, ESI, EDI, and EBP registers are preserved. ECX and EDX are
destroyed.
In the pascal calling convention, all parameters are pushed onto the
stack
from left to right. The first parameter is pushed first, and the last is
pushed last. The stack is cleaned up by the function. The function name
is
modified by converting it to all upper case letters. The function must
preserve the EBX, ESI, EDI, and EBP registers. The EAX register is used
to
return a value.
Function:
int __pascal function (int a, long b, short c, char d, void *e);
ASM call logic:
push a
push b
push c
push d
push *e
call near ptr FUNCTION
; push parameters in call order
; call the function
The stack is not adjusted because the function did that. EAX contains the
return value (if any). The EBX, ESI, EDI, and EBP registers have been
preserved. ECX and EDX are destroyed.
The fastcall calling convention allows up to three parameters to be
passed
in registers. The first parameter is placed in EAX, the second in EDX,
and
the third in EBX. If there are more than three parameters, they are
pushed
from left to right starting with the fourth parameter. The stack is
cleaned
up by the function. The function name is modified by prepending a '@' to
the
function name. The function must preserve the ESI, EDI, and EBP
registers. The
EAX register is used to return a value.
Function:
int __fastcall function (int a, long b, short c, char d, void *e);
ASM call logic:
mov eax,a
regs
mov edx,b
mov ebx,c
push d
push *e
call near ptr @function
; first three parameters into
; push the last two parameters
; call the function
The function adjusted the stack. EAX contains the return value (if any).
The
ESI, EDI, and EBP registers have been preserved. EBX, ECX, and EDX are
destroyed.
2.3 - ASM programs:
------------------It is possible to write programs entirely in assembly using PMC. You
may
still use all of the library functions. The only thing you need is a main
ASM
module, instead of a C module, with a function called '_PMmain'.
Ofcourse, the
library functions still take parameters as they would from C, that is on
the
stack.
----------------------------------------------------------------------------3 - Stack:
---------The stack is used for local variables and parameter passing to
functions.
Since DS = SS during execution, any pointers to data on the stack are as
valid
as pointers to data in the data segment. All space allocated from the
stack
is in terms of ints. Space for local variables, arrays, strings, etc...
is
allocated in units of four bytes. This maintains stack alignment on a
dword
boundary. When functions are called, a 32bit return address is pushed
onto
the stack since execution is in 32bit protected mode.
3.0 - Main stack:
----------------The main stack is set up by the PMC header at startup. The default size
of
the main stack is 4k, but you may change that. The size of the stack is
not
limited to 64k, but it is limited by the size of available low memory.
The
stack is kept in low memory because this memory is locked under DPMI
hosts
which support virtual memory. The current stack pointer is the 32bit ESP
register, not the 16bit SP. The stack selector is the regular data
selector,
as it must be in all executing C/C++ code.
3.1 - DPMI stacks:
-----------------A DPMI host will provide its own stack to protected mode IRQ handlers
and
real mode callbacks. This destroys the DS = SS relationship needed for C
code
execution. This is not really a problem though. You may switch off the
stack
provided by the DPMI host during processing of the IRQ or callback. But
you
must switch back to the DPMI stack before returning from the IRQ or
callback.
There are low level functions provided to switch onto a stack within the
data
segment and return to the previous stack. There are also functions
provided by
PMC to set up IRQ handlers which will do this automatically. In addition,
the
low level PMC real to protected mode switch functions always switch to a
valid C stack.
3.2 - Nested mode stacks:
------------------------One of the things the PMC startup header does, is allocate some low
memory
stack space. This space is to be used by low level mode switch functions
to
provide a stack for real mode calls, and to provide DS = SS stack space
for
calls to protected mode from real mode. The size and number of nested
mode
stacks can be specified by your code. The appropriate variables for this
will
be discussed in the section on variables. The number of stacks provided
for
each mode represents the highest level of nested calls that are allowed
to
that mode.
In actuality, when switching modes, the PMC nested mode stacks may be
used
or DPMI stacks may be used. If a DPMI stack is used for a mode switch, a
PMC
stack is not, and it does not count as a nested PMC call.
you to
specify the number of nested DPMI stacks it will support.
different stacks and mode switching, I suggest you do not
number or
size of any default stacks unless you understand how they
which mode switch functions.
PMODE allows
With all the
change the
will be used by
----------------------------------------------------------------------------4 - Mode switching:
------------------There are many different methods of mode switching provided to your
code.
Everything from default DPMI redirection of IRQs to real mode, to custom
fast
PMC mode switching routines. The fastest method of mode switching is the
raw
mode switching routines provided by the DPMI host. The drawback to these
is
that they do not provide a stack for the destination mode. They also
require
special state saving to be done. An easier alternative, and still very
fast,
is the low level mode switch functions provided by PMC. These provide an
appripriate stack for the destination mode. They use the raw mode DPMI
switching services, but they only call the DPMI state saving routines if
it
is necessary.
4.0 - DPMI mode switching:
-------------------------DPMI provides its own services for switching modes. There are basically
five
ways to do it. The first is an IRQ in protected mode that is sent on to a
real
mode handler. This occurs when no handler has been installed for the IRQ
in
protected mode, or when a handler has been installed and it chains to the
default DPMI handler for the IRQ. A software interrupt in protected mode
will,
unless a protected mode handler for that interrupt has been installed, be
sent
on to the real mode handler for processing. The third method is to use
DPMI
translation functions to call a real mode interrupt handler of FAR
procedure.
DPMI may also be used to set up real mode callbacks. These are special
routines in real mode which call a procedure in protected mode. The final
method is the fastest. DPMI allows access to low level raw mode switching
routines. Special measures have to be taken when using these, but they
allow
for the fastest switching across modes. For more information on the
workings
of DPMI mode switching, read the PMODE dox.
The PMC library provides for calling DPMI translation functions. You
may
call real mode FAR or IRET procedures or INTs directly from your C/C++
code
through DPMI. You can also set up normal C functions as real mode DPMI
callbacks. The DPMI services are slower than the PMC low level mode
switching
routines, but they allow for greater control. For example, when calling
real
mode routines, the DPMI functions allow you to set your own real mode
stack
and pass stack parameters. The PMC low level functions don't do this
extra
processing. Also, DPMI callbacks allow you greater control of the
conditions
upon resumption of execution in real mode.
4.1 - PMC mode switching:
------------------------PMC provides an additional array of mode switching options. They are
coded
with the sole purpose of speed in mind, and all use the DPMI raw mode
switching functions. At the lowest level, variables set up by PMC contain
addresses of various flavors of mode switching functions. Calling FAR or
IRET
routines with registers passed or not. There are different versions of
these
functions for state saving/restoring if needed and no state
saving/restoring.
The correct addresses will be set up by the startup header. These
functions,
unlike the DPMI raw mode switch services, provide a stack for the
destination
mode. These lowest level functions are only available to assembly code.
There
is a second level of functions which makes the low level available to
C/C++
code. You may call real mode FAR or IRET procedures or INTs from
protected
mode, passing or not passing registers depending on your needs. You can
also
set up fast real mode callbacks to protected mode. Fast in the sense that
only
what is needed is done. For example, a real mode IRQ does not need to
pass
registers to a protected mode routine it might call to process that IRQ.
For speed, especially within IRQ handlers, you should use the PMC mode
switching functions. But if you need control, stick with the DPMI
functions.
The PMC routines sacrifice control for speed. But the DPMI functions
allow you
to do some things not possible with the PMC switching functions. For
example,
if you wish set up a protected mode INT 21h handler, you should use a
DPMI
callback. This will allow you to jump to the old real mode handler rather
than
having to do a nested call to real mode from within your protected mode
code.
And the consequences of using PMC functions to hook INT 21h, which may
never
directly return from certain interrupt calls (INT 21h AH=4ch), might be
unstable code. This is not a bug with PMC, but rather a predictable
result
given the way the PMC switch functions work.
As for speed, I said the PMC functions were fast, but they are only as
good
as the DPMI raw switch services they use. They add a little bit of extra
processing to a simple raw mode switch. Most notably, providing a stack
for
the target mode. But this is not much. If you really need every ounce of
speed, use the DPMI raw mode switching services yourself. But if you need
the
speed that bad, you will probably have problems with slower DPMI hosts.
In
these cases, you should set up two versions of any critical code that may
be
called from real or protected mode. One version in real mode, and one in
protected mode. The real mode code does not have to do all of the
processing.
It may simply do something which is needed immediately, like sending an
ACK
to a device or modem. It can then call a protected mode routine to do the
rest of the work.
----------------------------------------------------------------------------5 - IRQs:
--------PMC and PMODE were coded with attention to code execution speed. With
IRQs,
this is especially important. I can not make any claims for code running
under
an external DPMI host, but when PMODE provides the protected mode
services
itself, it and PMC allow you to set up IRQ handlers which will get
control as
soon as physically possible. This is critical for sensetive IRQ handlers,
like
a timer routine which has to sync with the vertical retrace of the
monitor.
5.0 - Speed:
-----------Probably the most critical aspect of an IRQ handler is how soon it will
get
control after the hardware interrupt request is generated. PMC allows
this
time to be absolutely minimal if you wish. For the fastest results
possible,
you must set up two seperate IRQ entry points. One in protected mode, and
one
in real mode. If this seems excessive, keep in mind that you do not
necessarily have to use this method. PMC allows for setting up straight
protected mode IRQ handlers without you ever worrying about real mode.
This double entry method gives you the quickest entry because, upon
recieving the interrupt request, the machine could be either in protected
mode or in real mode servicing a DOS or BIOS call or something like that.
There will not need to be a mode switch done to handle the IRQ if a
handler is
available in the mode the machine is currently running in. And if PMODE
is
running in XMS or raw mode, IRQs in both real and protected mode will run
as
fast as physically possible. PMODE runs real mode calls in actual real
mode
rather than V86 mode. This means that upon an IRQ, no overhead code has
to be
executed by the extender, and the IRQ goes DIRECTLY to its real mode
handler.
As for protected mode, under VCPI/XMS/raw, they go directly to their
protected
mode handlers. If there is not a protected mode handler installed, they
will
be redirected to their real mode handler. Again, I can say nothing for
DPMI
IRQs. I speak only for cases where PMODE is in control.
That simple complication of PMODE executing real mode calls in actual
real
mode forces you to install a real mode IRQ handler for any protected mode
handler you install. But this is normally transparent to your code, as
PMC
will automatically set up a real mode handler to redirect to the
installed
protected mode handler. Though PMC does give you that option of setting
up
your own real mode handler, decreasing interrupt latency. Your real mode
IRQ
does not even have to do all of the processing. It may simply do the
speed
critical functions, like sending an ACK to a hardware device. It can then
call
a protected mode function common to both the real mode and protected mode
handlers to do the bulk of the processing.
5.1 - IRQ/INT C code:
--------------------32bit C/C++ code requires DS = SS during execution. However, an IRQ may
wind
up with a stack not quite in this state. Especially in protected mode,
where
DPMI may switch onto a totally different stack. Even if the IRQ came from
code
which was executing on a DS = SS stack. For this reason, you must switch
onto
an appropriate stack before using any C/C++ code within an IRQ handler.
PMC
allows for many methods of doing this.
First of all, you should never point a protected mode IRQ or interrupt
handler directly to a C/C++ function. This is because some things must be
set
up before C/C++ code can be executed as an interrupt handler. A good DS =
SS
stack must be set up. DS and ES must be set to the data selector. And the
direction flag must be cleared. PMC allows you to set up a small code
stub
which will do all of these things, call a function, then restore the
previous
state of the registers and return from the IRQ or interrupt. When calling
protected mode C code from real mode, the PMC mode switch functions do
these
things automatically, so you can call a C function directly from real
mode
without setting up a code stub.
5.2 - Code stubs:
----------------Code stubs are basically small pieces of code set up at run time in low
memory which do some redirection. PMC allows you to set up three basic
types
of code stubs. Their functions range from hooking real mode IRQs for
protected
mode handlers, to interfacing a DPMI callback to a C function.
The first type of stub you can set up is a real mode type. It allows
you to
set up a real mode address which, when called, will transfer control to a
protected mode routine. The call can be either a FAR call from another
real
mode routine, or an IRQ/INT in real mode. The stub will call a protected
mode
C/C++ routine or a protected mode IRETD terminated routine. It may or may
not
pass the real mode registers in a data structure, depending on whether
you
need them or not. This is a nice demonstration of a little speed savings
here.
You do not really need to pass the real mode registers from a real mode
IRQ,
so why do the processing.
The second type of code stub is a protected mode IRQ redirector. It
will do
all the setup necessary for C/C++ execution, call a C function, then
IRETD
from the IRQ. This allows you to set up C functions as IRQ handlers. Your
code must do the EOI for the interrupt controller though. So don't forget
that
nice little:
asm
{
mov al,20h
out 20h,al
}
The third type of code stub is a DPMI real mode callback redirector. It
will
do the setup necessary for C/C++ code, call a C function, then return
from the
callback. The callback must follow the DPMI real mode callback rules.
Your
function recieves and must pass back the address of a register structure.
Normally, you will pass back the address of the same register structure
your
code gets. But if you need to, you can copy the structure, and return a
different address. You MUST do this if you reenable interrupts within
your
callback processing code.
----------------------------------------------------------------------------6 - Variables:
-------------There are two groups of global variables defined by PMC. The first
group is
that data which is set up at runtime. This includes CPL, PIC numbers, and
segment base addresses. The second group has default values which can be
overrided by redefining them within your modules. These variables are
used at
startup to set certain things, like stack size, or memory pool sizes. The
first group of variables always resides within the first 64k of the
'_DATA'
segment. This makes them available to real mode code.
6.0 - Startup variables:
-----------------------There are two groups of startup variables. One which deals directly
with the
PMODE extender. And one which controls the setup of PMC. Those variables
which
start with '_PM' are the PMODE specific variables. They only matter if
the
system is non running under a DPMI host. If there is a DPMI host present,
PMODE is not in control of protected mode, and those variables have no
effect.
Now for the actual variables (shown with their default values):
int _PMpagetables = 1;
This variable sets the number of page tables to be set up under VCPI.
It
directly affects the maximum possible amount of extended memory when
running
in a VCPI system. Each page table requires 4k of low memory and allows
4M of
extended memory. There must always be at least one page table. The
first
page table allows 3M of extended memory to be accessed. Each additional
page
table gives you 4M more of possible extended memory space. This means,
if
the system has 16M of real memory, but you only allow two page tables,
only
7M of extended memory will be seen. If the system had 4M of real memory
and
two page tables, only 4M would be available.
) int _PMselectors = 64;
This sets the maximum number of selectors that are made available to
your
program for allocation. The range is 0 - 8150.
) int _PMrmstacklen = 0x40;
This sets the size of real mode call stacks provided by PMODE for INT
31h
translation services, IRQs, and software INTs redirected to real mode.
The
size is in paragraphs.
) int _PMpmstacklen = 0x80;
This sets the size of protected mode stacks provided by PMODE for real
mode
callbacks to protected mode. The size is in paragraphs. This variable
may be
set to zero if DPMI real mode callbacks are never used within your
code.
) int _PMrmstacks = 2;
This sets the number of nested real mode stacks. There MUST always be
at
least one.
) int _PMpmstacks = 2;
This sets the number of nested protected mode stacks for callbacks.
This
variable may be set to zero if no DPMI real mode callbacks are set up
by
your program.
) int _PMcallbacks = 16;
This variable sets up the number of DPMI real mode callbacks available
for
allocation by your code. It may be set to zero if no DPMI callbacks are
used.
That is the end of the PMODE specific variables. For more information
on
how these affect PMODE, see the PMODE documentation. Now for the PMC
vars:
) int _pmcrmstacklen = 0x40;
This variable sets the size of the stack, in paragraphs, for real mode
calls
done using PMC mode switch functions. It may be set to zero if none are
used.
) int _pmcrmstacks = 4;
This variable sets the number of nested PMC real mode call stacks. It
may be
set to zero if no PMC real mode calls are done.
) int _rmcpmstacklen = 0x80;
This sets the size of the protected mode stack, in paragraphs, supplied
when
using the PMC protected mode call functions from real mode. These
stacks are
also used by IRQ code stubs. If you use neither, this may be set to
zero.
) int _rmcpmstacks = 4;
This sets the number of nested protected mode stacks for PMC real to
protected mode calls and IRQ stubs. It may be set to zero if none are
used.
) int _stklen = 0x1000;
This is the size of the main stack in protected mode. Never set this
below
1k. The size may be larger than 64k. This value is in bytes.
) int _lowheaplen = 0;
This is the minimum size of the largest free block of low memory you
want
available upon getting execution control from the PMC header. If a free
block of low memory of this size can not be provided, the PMC header
will
ith
exit an error message.
) int _extheapmin = 0;
This is the minimum size of the largest free block of extended memory
you
want available from the high memory pool. If sufficient extended memory
is
not present, the PMC startup header will exit with an error message.
) int _extheapmax = 0x7fffffff;
This is the maximum size of the largest extended memory block you want
available from the high memory pool. The default large number assures
you
will get all extended memory available. But if you do not need a lot of
extended memory, you may set this variable lower. If it is zero, no
extended
memory will be touched. Also keep in mind that under VCPI, the maximum
extended memory available is also affected by the _PMpagetables
variable.
) int _minosversion = 0x300;
This is the minimum version of DOS you want your code to run under. If
the
DOS version is too low, the PMC header will exit with an error message.
) PTR _lowbufptr = NULL;
This variable is set by the PMC startup header if _lowbuflen is
anything but
zero. It points to a general low memory area to be used for buffering
of
data for real mode calls.
) int _lowbuflen = 0x1000;
This specifies the size of the low memory buffer you want the PMC
header to
allocate in bytes. It may never be set above 0xfff0. If this variable
is set
to zero, the PMC header will not allocate a low memory area, and the
_lowbufptr variable will not be modified. You can use this to
initialize
_lowbufptr to point to a static area within your data and use that as
the
buffer. You do not necessarily need a low memory buffer, but many of
the PMC
library functions require one. The size of the low memory buffer is
assumed,
by the PMC library functions which use it, to be at least 1k.
6.1 - Runtime variables:
-----------------------These variables are set up by the PMC startup header at runtime. They
contain all sorts of useful information. All of these variables are
located
within the first 64k of the '_DATA' segment. This makes them accessible
to
real mode code. This is very important since the addresses of the PMC
real to
protected mode calls are here.
) DWORD codebase;
This is the 32bit linear base address of the code segment.
) DWORD database;
This is the 32bit linear base address of the data segment (DGROUP).
) DWORD pspbase;
This is the 32bit linear base address of the PSP.
) DWORD envbase;
This is the 32bit linear base address of the environment segment.
) int codesel;
This is the code selector.
) int datasel;
This is the data selector.
) int pspsel;
This is the PSP selector (as returned by DPMI protected mode init).
) int envsel;
This is the environment selector (as returned by DPMI protected mode
init).
) int zerosel;
This is the selector for a descriptor with a base address of zero. Used
to
access data below the beginning of the data segment.
) int _TEXTseg;
This is the real mode value of the '_TEXT' segment.
) int _DATAseg;
This is the real mode value of the '_DATA' segment.
) int _BSSseg;
This is the real mode value of the '_BSS' segment.
) int pspseg;
This is the real mode PSP segment.
) int envseg;
This is the real mode environment segment.
) PTR zeroptr;
This is a relative pointer to the absolute start of memory. It is a
negative value because the data segment starts above the beginning of
memory. But it is a perfectly usable pointer (except under OS/2).
) DWORD data_code;
This value is equivalent to '_codebase - _database'. It is provided for
quick conversion of code to data pointers and vice versa.
) DWORD code_data;
This value is equivalent to '_database - _codebase'. It is provided for
quick conversion of code to data pointers and vice versa.
) int osversion;
This is the DOS version. The major version number is in the high byte
and
the minor version is in the low byte of the first word of the int.
) BYTE osminor;
This is the minor DOS version.
) BYTE osmajor;
This is the major DOS version.
) int DPMIversion;
This is the DPMI version. The major version number is in the high byte
and
the minor version is in the low byte of the first word of the int.
) BYTE DPMIminor;
This is the minor DPMI version.
) BYTE DPMImajor;
This is the major DPMI version.
) int CPL;
This is the CPL your code is running at.
) int selinc;
This is the selector increment value as returned by DPMI INT 31h
function
0003h.
) int PMtype;
This is the protected mode type. The values are as follows, 0 is a
clean
system, 1 is and XMS system, 2 is a VCPI system, and 3 is a DPMI
system.
Note that this is before PMODE sets up DPMI services.
) int processor;
This is the current processor. The values are as follows, 3 is 80386, 4
is
80486, and 5 is 80586.
) BYTE PICtable[2];
This is provided for a quick way of looking up a base interrupt for an
IRQ
number. The first byte is the base interrupt number for the first 8
IRQs.
The second byte is the base interrupt number for the next 8 IRQs.
) BYTE PICmaster;
This is the base interrupt number of the master PIC in the system.
) BYTE PICslave;
This is the base interrupt number of the slave PIC in the system.
) SEGOFF rmstate;
This is the segment:offset of the DPMI raw real to protected mode
switch
routine.
) SEGOFF rmswitch;
This is the segment:offset of the DPMI real mode state save/restore
routine.
) SELOFF pmstate;
This is the selector:offset of the DPMI raw protected to real mode
switch
routine.
) SELOFF pmswitch;
This is the selector:offset of the DPMI protected mode state
save/restore
routine.
) int statesize;
This is the size of the state buffer needed by DPMI. Zero means the
state
does not need to be saved/restored before using the raw mode switch
routines.
) DWORD pmcrmnoregs;
This is the 32bit protected mode offset (in the code segment) of the
PMC low
level real mode FAR call routine that does not pass registers.
) DWORD pmcrmregs;
This is the 32bit protected mode offset (in the code segment) of the
PMC low
level real mode FAR call routine that passes registers.
) DWORD pmcrminoregs;
This is the 32bit protected mode offset (in the code segment) of the
PMC low
level real mode IRET call routine that does not pass registers.
) DWORD pmcrmiregs;
This is the 32bit protected mode offset (in the code segment) of the
PMC low
level real mode IRET call routine that passes registers.
) SEGOFF rmcpmnoregs;
This is the real mode segment:offset of the PMC low level protected
mode
C call routine that does not pass registers.
) SEGOFF rmcpmregs;
This is the real mode segment:offset of the PMC low level protected
mode
C call routine that passes registers.
) SEGOFF rmcpminoregs;
This is the real mode segment:offset of the PMC low level protected
mode
IRET call routine that does not pass registers.
) SEGOFF rmcpmiregs;
This is the real mode segment:offset of the PMC low level protected
mode
IRET call routine that does passes registers.
) DWORD pmstacklen;
This is the size of a PMC nested protected mode stack in bytes rather
than
paragraphs.
) PTR pmstackbase;
This is the base of the entire PMC nested protected mode stack area.
This
can be used to determine if stack space is available before attempting
a PMC
call to protected mode.
) volatile PTR pmstacktop;
This is the current top of the next PMC nested protected mode stack.
) int rmstacklen;
This is the length of a PMC nested real mode stack.
) int rmstackbase;
This is the base of the entire PMC nested real mode stack area. This
can be
used to determine if stack space is available before attempting a PMC
call
to real mode.
) volatile int rmstacktop;
This is the current top (segment) of the next PMC nested real mode
stack.
) MEMBLOCK lowheapblock;
This structure contains the base address and size of the low memory
heap.
There is always a valid base address here because there is always a low
heap.
) MEMBLOCK extheapblock;
This structure contains the base address, size, and DPMI handle of the
extended memory heap. An extended memory heap may not be present. In
this
case, the size field of this structure will contain zero.
) REGSTRUCT rs;
This register structure is used by the PMC low level switch routines to
pass
registers between real and protected mode. The flags field of this
structure
is initialized to a valid real mode value, meaning dangerous stuff like
the
trap flag is cleared. This is so that you can use it directly, without
having to set the FLAGS field all the time, with PMC real mode call
functions. The SS:SP field of this structure is initialized to zero.
This
makes it immediately usable for DPMI real mode call functions.
6.2 - PMC nested stacks:
-----------------------Variables are made available to your code which contain data about the
PMC
nested mode stack areas. This is so that your code can use this memory
for
stack space if you are doing your own mode switching. Here is some logic
on
using and updating the stack variables:
) Protected mode stack:
if (pmstacktop > pmstackbase)
{
temp = pmstacktop;
pmstacktop -= pmstacklen;
SS = datasel;
ESP = temp;
}
else
no_stack_space ();
) Real mode stack:
if (rmstacktop > rmstackbase)
{
temp = rmstacktop;
rmstacktop -= pmcrmstacklen;
SS = temp;
SP = rmstacklen;
}
else
no_stack_space ();
If you do use them, be careful of the order of your operations. Don't
consider the stack space valid until you update the top of stack pointer.
And
don't restore the previous top of stack pointer until you are off the
stack it
references. You do not have to, but you may also disable interrupts while
you
perform stack calculations.
There are two functions made available to ASM code for switching onto a
protected mode nested stack and restoring the old stack. They do not
check
whether a stack is present though. These functions update the stack top
parameters as needed. They are intended for use within your own ASM IRQ
handlers which need to call C functions. You may use them or do the stack
switching yourself.
) DATASTACK
Switch onto the next protected mode nested stack.
Out:
DX:EAX = old SS:ESP to preserve
ECX = ? (destroyed)
pmstacktop = updated to next position
) RESTORESTACK
Restore a previous stack.
In:
DX:EAX = old SS:ESP to restore
Out:
EAX, ECX = ? (destroyed)
pmstacktop = updated to previous position
----------------------------------------------------------------------------7 - Macros:
----------PMC provides some macros to make your life a little easier. These deal
mostly with pointer conversion. Real mode <-> protected mode, code <->
data,
relative <-> linear, etc...
7.0 - Pointer macros:
---------------------
) PTR rlp (DWORD)
This macro converts a linear pointer to a pointer relative to the
protected
mode data segment. For example, rlp (0xa0000) creates a pointer to the
video
buffer area in graphics mode.
) DWORD lnp (PTR)
This macro is the converse of rlp. It converts a data pointer to a
linear
pointer.
) DWORD dcp (PTR)
This converts a pointer relative to the protected mode data segment, to
one
relative to the protected mode code segment.
) PTR cdp (DWORD)
This macro converts a pointer relative to the code segment, to one
relative
to the data segment. Be warned, if the area of memory the code pointer
references does not lie above the beginning of the data segment, a
negative
pointer will be created (unusable under OS/2).
) PTR SEGOFFtoPTR (SEGOFF)
This macro creates a protected mode pointer (relative to the data
segment)
from a real mode SEGOFF structure. Again, be warned about the
possibility of
negative pointers.
) PTR segofftoPTR (off, seg)
This macro creates a protected mode pointer from a segment and offset
immediately provided. Negative pointers are possible.
) WORD PTRtooff (PTR)
This macro returns the real mode offset for a protected mode low memory
pointer.
) WORD PTRtoseg (PTR)
This macro returns the real mode segment for a protected mode low
memory
pointer.
) void PTRtoSEGOFF (SEGOFF, PTR)
This macro converts a low memory pointer directly to a real mode
segment and
offset and stores them in a SEGOFF structure.
) DWORD SEGOFFtoDWORD (SEGOFF)
This converts a SEGOFF structure into a DWORD containing the real mode
segment and offset. The segment is stored in the high word of the DWORD
and
the offset is in the low word.
) DWORD segofftoDWORD (off, seg)
This macro creates a DWORD containing the segment in the high word and
the
offset in the low word.
) WORD DWORDtooff (DWORD)
This macro returns the offset from a DWORD containing a real mode
segment
and offset.
) WORD DWORDtoseg (DWORD)
This macro returns the segment from a DWORD containing a real mode
segment
and offset.
) void DWORDtoSEGOFF (SEGOFF, DWORD)
This macro stores the segment and offset from a DWORD directly in a
SEGOFF
structure.
) DWORD PTRtoDWORD (PTR)
This converts a protected mode pointer to low memory into a DWORD
containing
the real mode segment in the high word and the offset in the low word.
) PTR DWORDtoPTR (DWORD)
This converts a DWORD containing a segment in the high word and an
offset in
the low word into a protected mode pointer. A negative pointer may
result.
7.1 - Other macros:
------------------) BYTE loBYTE (WORD)
This macro returns the low BYTE of a WORD.
) BYTE hiBYTE (WORD)
This macro returns the high BYTE of a WORD.
) WORD loWORD (DWORD)
This macro returns the low WORD of a DWORD.
) WORD hiWORD (DWORD)
This macro returns the high WORD of a DWORD.
) WORD makeWORD (lobyte, hibyte)
This macro creates a WORD from two BYTEs.
) DWORD makeDWORD (loword, hiword)
This macro creates a DWORD from two WORDs.
) max (a, b)
This macro returns the maximum of the two values.
) min (a, b)
This macro returns the minimum of the two values.
) mo (type, variable)
This macro does a type override where a normal type override may not
apply.
For example, a 'mo (DWORD, SEGOFF)' will treat the first 4 BYTEs of the
SEGOFF structure as a DWORD.
----------------------------------------------------------------------------8 - Functions:
-------------Most of the functions provided by PMC are very basic commonly used
functions. They serve well for a kernel, but if you need something more
general you're probably going to have to code it yourself. The library
source
is present so you can use that for reference if you do write your own
functions.
8.0 - DPMI functions:
--------------------For more information about
documentation. Many of these
the
DPMI INT 31h services. There
functions in C, but they are
DPMI protected mode services, see the PMODE
functions are just a direct C interface to
is no real need to use the DPMI descriptor
provided anyway.
) long __pascal dsc_alloc (WORD *sel, int count);
This function attempts to allocate some descriptors through DPMI.
In:
WORD *sel = address where to put first selector allocated
int count = number of descriptors to allocate
Out:
if successful:
long = 0
WORD *sel = first allocated selector
if failed:
long = DPMI error code
Notes:
) If more that one descriptor was requested, the function returns a
base
selector referencing the first of a contiguous array of descriptors.
The
selector values for subsequent descriptors in the array can be
calculated
by adding the selector increment value (selinc).
) The allocated descriptor(s) will be set to expand-up writeable data,
with
the present bit set and a base and limit of zero. The privilege level
of
the descriptor(s) will match the client's code segment privilege
level.
) long __pascal dsc_free (WORD sel);
This functions will attempt to free a single descriptor.
In:
WORD sel = selector to free
Out:
if successful:
long = 0
if failed:
long = DPMI error code
Notes:
) Each descriptor allocated must be freed individually with the
function.
Even if it was previously allocated as part of a contiguous array of
descriptors.
) long __pascal dsc_getbase (WORD sel, DWORD *base);
This function gets the 32bit linear base address of a segment
referenced by
a selector.
In:
WORD sel = selector of segment to get base address of
DWORD *base = address to store 32bit base address
Out:
if successful:
long = 0
DWORD *base = 32bit linear base address
if failed:
long = DPMI error code
) long __pascal dsc_setbase (WORD sel, DWORD base);
This function sets the 32bit linear base address of a segment
referenced by
a selector.
In:
WORD sel = selector to set base of
DWORD base = new 32bit base address
Out:
if successful:
long = 0
if failed:
long = DPMI error code
) long __pascal dsc_getlimit (WORD sel, DWORD *limit);
This function gets the limit of a segment.
In:
WORD sel = selector of segment to get limit of
DWORD *limit = address to store 32bit limit
Out:
if successful:
long = 0
DWORD *limit = 32bit limit
if failed:
long = DPMI error code
) long __pascal dsc_setlimit (WORD sel, DWORD limit);
This function gets the 32bit limit of a segment.
In:
WORD sel = selector of segment to set limit of
DWORD limit = new 32bit limit
Out:
if successful:
long = 0
if failed:
long = DPMI error code
Notes:
) The limit is the size of the segment in bytes minus one.
) Segment limits greater than or equal to 1M must be page aligned. That
is,
they must have the low 12 bits set.
) long __pascal dsc_getaccess (WORD sel, WORD *access);
This function gets the access rights/type word of a segment.
In:
WORD sel = selector of segment to get access word of
WORD *access = address to store access word
Out:
if successful:
long = 0
WORD *access = access rights/type word of segment
if failed:
long = DPMI error code
) long __pascal dsc_setaccess (WORD sel, WORD access);
This function sets the access rights/type word of a segment.
In:
WORD sel = selector of segment to get access word of
WORD *access = address to store access word
Out:
if successful:
long = 0
if failed:
long = DPMI error code
Notes:
) The access rights/type word must abide by DPMI rules. For more
information, see the PMODE documentation.
) long __pascal dsc_alias (WORD src, WORD *dst);
This function creates an alias for a selector given.
In:
WORD src = selector to create alias for
WORD *dst = address of to put alias selector created
Out:
if successful:
long = 0
WORD *dst = alias selector
if failed:
long = DPMI error code
Notes:
) The selector supplied to the function may be either a data descriptor
or
a code descriptor. The alias descriptor created is always an expandup
writeable data segment.
) The descriptor alias returned by this function will not track changes
to
the original descriptor.
) long __pascal dsc_getdsc (WORD sel, DESCRIPTOR *dsc);
This function gets the full 8 byte descriptor of a selector.
In:
WORD sel = selector to get descriptor of
DESCRIPTOR *dsc = address of destination descriptor structure
Out:
if successful:
long = 0
DESCRIPTOR *dsc = descriptor of selector given
if failed:
long = DPMI error code
) long __pascal dsc_setdsc (WORD sel, DESCRIPTOR *dsc);
This function sets the full 8 byte descriptor of a selector.
In:
WORD sel = selector of descriptor to set
DESCRIPTOR *dsc = address of descriptor structure
Out:
if successful:
long = 0
if failed:
long = DPMI error code
Notes:
) The descriptor must abide by DPMI descriptor rules. For more
information,
see the PMODE documentation.
) void __pascal iv_rmget (int intr, SEGOFF *iv);
This function gets a real mode interrupt vector.
In:
int intr = interrupt number
SEGOFF *iv = address of SEGOFF structure to store the interrupt
vector in
) void __pascal iv_rmset (int intr, WORD off, WORD seg);
This function sets a real mode interrupt vector.
In:
int intr = interrupt number
WORD off = offset of real mode interrupt handler
WORD sel = selector of real mode interrupt handler
) void __pascal iv_pmget (int intr, SELOFF *iv);
This function gets a protected mode interrupt vector.
In:
int intr = interrupt number
SELOFF *iv = address of SELOFF structure to store the interrupt
vector in
) long __pascal iv_pmset (int intr, DWORD off, WORD sel);
This function sets a protected mode interrupt vector.
In:
int intr = interrupt number
DWORD off = offset of protected mode interrupt handler
WORD sel = selector of protected mode interrupt handler
Out:
if successful:
long = 0
if failed:
long = DPMI error code
) long __cdecl xlt_simrmint (int intr, REGSTRUCT *rs, int count, ...);
This function calls a real mode interrupt using DPMI translation
functions.
In:
int intr = real mode interrupt number to call
REGSTRUCT *rs = register structure for interrupt
int count = number of words stack parameters that follow
... = optional stack parameters in push order
Out:
if successful:
long = 0
if failed:
long = DPMI error code
Notes:
) See the PMODE documentation for the format and DPMI usage of the
register
structure.
) long __cdecl xlt_callrmfar (REGSTRUCT *rs, int count, ...);
This function calls a real mode FAR routine using DPMI translation
functions.
In:
REGSTRUCT *rs = register structure for call
int count = number of words stack parameters that follow
... = optional stack parameters in push order
Out:
if successful:
long = 0
if failed:
long = DPMI error code
Notes:
) The CS:IP in the register structure contains the FAR routine address
to
call in real mode.
) See the PMODE documentation for the format and DPMI usage of the
register
structure.
) long __cdecl xlt_callrmiret (REGSTRUCT *rs, int count, ...);
This function calls a real mode IRET routine using DPMI translation
functions.
In:
REGSTRUCT *rs = register structure for call
int count = number of words stack parameters that follow
... = optional stack parameters in push order
Out:
if successful:
long = 0
if failed:
long = DPMI error code
Notes:
) See the PMODE documentation for the format and DPMI usage of the
register
structure.
) long __pascal xlt_rmcballoc (SEGOFF *cb, DWORD rsoff, WORD rssel, DWORD
funcoff, WORD funcsel);
This function allocates a DPMI real mode callback.
In:
SEGOFF *cb = address of SEGOFF structure for callback address
DWORD rsoff = offset of register structure to be used by the callback
WORD rssel = selector of register structure to be used by the
callback
DWORD funcoff = offset of callback routine
WORD funcsel = selector of callback routine
Out:
if successful:
long = 0
SEGOFF *cb = real mode address of callback
if failed:
long = DPMI error code
Notes:
) The callback routine may not be a straight C function. It must be a
custom
assembly function or a PMC callback stub.
) long __pascal xlt_rmcbfree (DWORD cbsegoff);
This function frees a DPMI real mode callback.
In:
DWORD cbsegoff = real mode address of callback to free
Out:
if successful:
long = 0
if failed:
long = DPMI error code
) DWORD __pascal mb_info (void);
This function gets the largest available DPMI block of extended memory.
Out:
DWORD = size in bytes of larges available DPMI block of extended
memory
) long __pascal mb_alloc (MEMBLOCK *mb, DWORD size);
This function allocates a DPMI block of extended memory.
In:
MEMBLOCK *mb = address of MEMBLOCK structure for this block
DWORD size = number of bytes to allocate
Out:
if successful:
long = 0
MEMBLOCK *mb = set with linear base address, size, and handle of
block
if failed:
long = DPMI error code
Notes:
) The allocated block is guaranteed to have at least paragraph
alignment.
) long __pascal mb_free (MEMBLOCK *mb);
This function frees a DPMI block of extended memory.
In:
MEMBLOCK *mb = address of MEMBLOCK structure of block to free
Out:
if successful:
long = 0
MEMBLOCK *mb = size field set to zero
if failed:
long = DPMI error code
) long __pascal mb_resize (MEMBLOCK *mb, DWORD size);
This function resizes a DPMI block of extended memory.
In:
MEMBLOCK *mb = address of MEMBLOCK structure of block to resize
DWORD size = new size for block
Out:
if successful:
long = 0
MEMBLOCK *mb = updated with new base address, size, and handle of
block
if failed:
long = DPMI error code
Notes:
) If the block is made smaller, data at the top will obviously be lost.
8.1 - Memory block functions:
----------------------------These functions deal with memory block management. The standard C high
level
memory manipulation functions are available, as well as low level
functions
for dealing with memory pools. The major structure used here is MEMBLOCK.
It
is used by the low level functions to define an area of memory as a pool
for
allocation. The MEMBLOCK structure can be right from an allocation using
DPMI
services. This allows any major part of a program or external driver to
set
up its own memory pool. A MEMBLOCK structure with a size field of zero is
considered a NULL MEMBLOCK structure and is handled correctly by all the
low
level functions.
) DWORD __pascal coreleft (void);
Finds largest allocatable area of memory in the low and extended system
memory heaps.
Out:
if successful:
DWORD = largest allocatable block of memory
if failed:
DWORD = zero, no memory available, or corrupt memory blocks
Notes:
) This is a shell function to the lower level mbcoreleft which is
executed
on the low and extended memory heaps.
) PTR __pascal malloc (DWORD size);
Allocates a memory area from the PMC system heaps.
In:
DWORD size = ammount of memory to get
Out:
if successful:
PTR = address of the requested memory area (relative to DGROUP)
if failed:
PTR = NULL, requested memory not available
Notes:
) This function first tries to allocate from the extended heap, then
the
low heap.
) The returned pointer is dword aligned.
) A size request of zero will always return a NULL pointer.
) PTR __pascal realloc (PTR ptr, DWORD size);
Resizes an existing memory block in the PMC system heaps.
In:
PTR ptr = address of memory area to resize, as given by malloc
DWORD size = new size of the memory area
Out:
if successful:
PTR = new address of the resized memory area
if failed:
PTR = NULL, could not resize memory area, the original memory area
is
still valid
Notes:
) This function can reallocate across the system heaps but will try the
heap
from which the original memory area came first.
) The returned pointer is dword aligned.
) No assumptions should be made if shrinking a memory area.
) void __pascal free (PTR ptr);
Releases a memory area back to the PMC system heaps.
In:
PTR ptr = address of memory area free
Notes:
) If a bad pointer is given to this function, nothing is done.
) BOOL __pascal mbinit (MEMBLOCK *mb);
Initializes a heap for use with the memory block functions.
In:
MEMBLOCK *mb = linear address and size of memory to use for the heap
Out:
if successful:
BOOL = TRUE
MEMBLOCK *mb = linear address and size of heap
if failed:
BOOL = FALSE
MEMBLOCK *mb = undefined
Notes:
) The (possibly) modified MEMBLOCK is what needs to be passed to the
other
low level memory management functions
) DWORD __pascal mbsquish (MEMBLOCK *mb);
Removes as many bytes as possible from the end of the heap.
In:
MEMBLOCK *mb = heap to squish
Out:
DWORD = number bytes removed from the end of the heap (can be zero)
MEMBLOCK *mb = size adjusted to the new size of the heap
Notes:
) Works only with the last memnode in the heap; if that node is not
free,
nothing is removed.
) Store the original size from the MEMBLOCK structure before calling
this
function if planning to use mbunsquish.
) Used by the fileexec function.
) void __pascal mbunsquish (MEMBLOCK *mb, DWORD size);
Restores a heap to its original size.
In:
MEMBLOCK *mb = heap to unsquish
DWORD size = new heap size
Out:
MEMBLOCK *mb = restored heap
Notes:
) A request to make the heap smaller will never take effect.
) This function does NOT allocate memory, but is used to restore to
heap's
size.
) PTR __pascal mbmalloc (MEMBLOCK *mb, DWORD size);
Allocates a memory area within a heap.
In:
MEMBLOCK *mb = heap to allocate from
DWORD size = ammount of memory to allocate, specified in bytes
Out:
if successful:
PTR = DGROUP address of allocated memory
if failed:
PTR = NULL
Notes:
) A size request of zero will always return a NULL pointer.
) PTR __pascal mbrealloc (MEMBLOCK *mb, PTR ptr, DWORD size);
Resizes a memory area within a heap.
In:
MEMBLOCK *mb = heap to resize within
PTR ptr = DGROUP address of memory area to resize
DWORD size = new size for the memory area specified in bytes
Out:
if successful:
PTR = DGROUP address of resized memory
if failed:
PTR = NULL
Notes:
) If mbrealloc fails, the original memory area is still valid.
) Even if shrinking no assumptions should be made about the new memory
area
pointer.
) Calling with a NULL ptr does a mbmalloc.
) Calling with a size of zero does a mbfree.
) All data of the original memory area (or as much as possible if
shrinking)
is preserved and copied to the new memory area.
) void __pascal mbfree (MEMBLOCK *mb, PTR ptr);
Releases a memory area back to a particualr heap.
In:
MEMBLOCK *mb = heap which pointer to free resides
PTR ptr = DGROUP address of mem area to free
Notes:
) With a valid, non-zero sized MEMBLOCK and a valid pointer, this
function
always succeedes, else it always fails.
) DWORD __pascal mbcoreleft (MEMBLOCK *mb);
Find size of largest free memory area within a heap.
In:
MEMBLOCK *mb = heap to scan
Out:
DWORD = largest allocateable memory area in heap
Notes:
) The size of the largest available block is returned, not the total
number
of bytes free.
) BOOL __pascal mbcheck (MEMBLOCK *mb);
Checks the integrity of the heap.
In:
MEMBLOCK *mb = heap to check
Out:
if successful:
BOOL = TRUE, all internal memory sturctures check out ok
if failed:
BOOL = FALSE;
Notes:
) mbcheck could fail if more memory is used than allocated by
overwriting
internal memnodes.
) DWORD __pascal mbgetsize (MEMBLOCK *mb, PTR ptr);
Returns the size of a particular memory area (free or allocated).
In:
MEMBLOCK *mb = heap in which memory area resides
PTR ptr = DGROUP address of memory area to get size of
Out:
if successful:
DWORD = byte size of memory area
if failed:
DWORD = NULL
Notes:
) The requested memarea's size is AT LEAST the requested size by
mbmalloc
or mbrealloc.
) void __pascal mbwalk (MEMBLOCK *mb, WALKMBINF *inf);
Step throught the memnodes and reports information about each, one for
each
call.
In:
MEMBLOCK *mb = heap to traverse
BOOL inf.RESTART = if true, start at beginning of heap and ignore
other
inputs
inf.adx = DGROUP address of last node
inf.size = size of last node
Out:
inf.adx = DGROUP address of new node
inf.size = size of next node
BOOL inf.ALLOCATED = if true, node is allocated
BOOL inf.LASTAREA = if true, this is the last memnode in the heap
BOOL inf.CORRUPT = if true, all other info is void, error detected
within heap
BOOL inf.UNINIT = if true, all other info is void, heap is
uninitialized
8.2 - Memory functions:
----------------------Most of these are standard ANSI C memory functions.
) PTR __pascal memccpy (PTR dst, PTR src, int c, DWORD size);
Copies 'size' bytes from 'src' to 'dst'. The copying stops as soon as
'size'
bytes have been copied or the byte 'c' has been first copied.
In:
PTR dst = destination pointer
PTR src = source pointer
int c = byte to terminate copy on
DWORD size = maximum number of bytes to copy
Out:
PTR = byte immediately following 'c' if 'c' was copied, else NULL
Notes:
) If the two blocks overlap, results are undefined.
) BOOL __pascal memchk (PTR mem, int c, DWORD size);
Checks if a block 'mem' of size 'size' consists entirely of bytes 'c'.
In:
PTR mem = memory block to check
DWORD size = size of block to check
int c = byte to check for
Out:
BOOL = TRUE if block consists entirely of 'c' bytes, else FALSE
) PTR __pascal memchr (PTR mem, int c, DWORD size);
Searches a block 'mem' of size 'size' for a byte 'c'.
In:
PTR mem = memory block to search
DWORD size = size of block to search
int c = byte to search for
Out:
PTR = pointer to first occurance of 'c' if present, else NULL
) int __pascal memcmp (PTR mem1, PTR mem2, DWORD size);
Compares (unsigned) two blocks of memory of size 'size'.
In:
PTR mem1 = pointer to the first block
PTR mem2 = pointer to the second block
DWORD size = number of bytes to compare
Out:
int = < 0 if first block is less than second block
= 0 if the two blocks are the same
> 0 if first block is greater than second block
) PTR __pascal memcpy (PTR dst, PTR src, DWORD size);
Copies 'size' bytes from 'src' to 'dst'.
In:
PTR dst = destination pointer
PTR src = source pointer
DWORD size = number of bytes to copy
Out:
PTR = dst
Notes:
) If the two blocks overlap, results are undefined.
) int __pascal memicmp (PTR mem1, PTR mem2, DWORD size);
Compares (unsigned) two blocks of memory ignoring case on letters.
In:
PTR mem1 = pointer to the first block
PTR mem2 = pointer to the second block
DWORD size = number of bytes to compare
Out:
int = < 0 if first block is less than second block
= 0 if the two blocks are the same
> 0 if first block is greater than second block
) PTR __pascal memmove (PTR dst, PTR src, DWORD size);
Copies 'size' bytes from 'src' to 'dst' compensating for overlapping.
In:
PTR dst = destination pointer
PTR src = source pointer
DWORD size = number of bytes to copy
Out:
PTR = dst
) PTR __pascal memset (PTR mem, int c, DWORD size);
Sets 'size' bytes of block 'mem' to byte 'c'.
In:
PTR mem = memory block to set
DWORD size = size of block to set
int c = byte to set
Out:
PTR = dst
) void movmem (PTR src, PTR dst, DWORD size);
Copies 'size' bytes from 'src' to 'dst' compensating for overlapping.
In:
PTR dst = destination pointer
PTR src = source pointer
DWORD size = number of bytes to copy
Notes:
) This is a macro that remaps to memmove.
) void setmem (PTR dst, DWORD size, int val);
Sets 'size' bytes of block 'mem' to byte 'c'.
In:
PTR mem = memory block to set
DWORD size = size of block to set
int c = byte to set
Notes:
) This is a macro that remaps to memset.
8.3 - String functions:
----------------------Most of these are standard ANSI C string functions. Strings are arrays
of
characters with a NULL terminating character. A type STR is a pointer to
a
string.
) STR __pascal stpcpy (STR dst, STR src);
Copies 'src' string to 'dst'.
In:
STR dst = destination string buffer
STR src = source string
Out:
STR = pointer to terminating NULL character in 'dst'
) STR __pascal strcat (STR dst, STR src);
Appends 'src' string to 'dst' string.
In:
STR dst = destination string
STR src = source string
Out:
STR = dst
) STR __pascal strchr (STR str, int c);
Scans 'str' string for first occurance of character 'c'. The NULL
terminating character is considered to be part of the string.
In:
STR str = string to scan
int c = character to scan for
Out:
STR = pointer to first occurance of 'c' if present, else NULL
) int __pascal strcmp (STR str1, STR str2);
Compares (signed) one string to another.
In:
STR str1 = first string
STR str2 = second string
Out:
int = < 0 if first string is less than second string
= 0 if the two strings are the same
> 0 if first string is greater than second string
) STR __pascal strcpy (STR dst, STR src);
Copies 'src' string to 'dst'.
In:
STR dst = destination string buffer
STR src = source string
Out:
STR = dst
) DWORD __pascal strcspn (STR str1, STR str2);
Scans string 'str1' for the initial segment not containing any
characters
from string 'str2'.
In:
STR str1 = string to scan
STR str2 = string with characters to terminate on
Out:
DWORD = length of the initial segment of 'str1' that consists
entirely of
characters NOT from string 'str2'
) STR __pascal strdup (STR str);
Allocates memory and copies string 'str' to it.
In:
STR str = string to duplicate
Out:
STR = pointer to allocated and copied string, else NULL if not enough
mem
Notes:
) The space allocated is strlen (str) + 1.
) The user is responsible for freeing the memory when it is no longer
needed.
) int __pascal stricmp (STR str1, STR str2);
Compares (signed) one string to another ignoring case.
In:
STR str1 = first string
STR str2 = second string
Out:
int = < 0 if first string is less than second string
= 0 if the two strings are the same
> 0 if first string is greater than second string
) DWORD __pascal strlen (STR str);
Returns the length of string 'str' not including the terminating NULL
character.
In:
STR str = string
Out:
DWORD = length of string not including the terminating NULL character
) STR __pascal strlwr (STR str);
Converts all characters in string 'str' to lower case.
In:
STR str = string to convert to lower case
Out:
STR = str
) STR __pascal strncat (STR dst, STR src, DWORD maxlen);
Appends a up to 'maxlen' characters of string 'src' to the end of
string
'dst' then appends a terminating NULL character.
In:
STR dst = destination string
STR src = source string
DWORD maxlen = maximum number of characters to append
Out:
STR = dst
) int __pascal strncmp (STR str1, STR str2, DWORD maxlen);
Compares (signed) at most 'maxlen' characters of two strings.
In:
STR str1 = first string
STR str2 = second string
DWORD maxlen = maximum number of characters to compare
Out:
int = < 0 if first string is less than second string
= 0 if the two strings are the same
> 0 if first string is greater than second string
) STR __pascal strncpy (STR dst, STR src, DWORD maxlen);
Copies at most 'maxlen' characters from string 'src' to 'dst'
truncating or
NULL padding 'dst' as necessary. The resulting string may not be NULL
terminated if strlen (src) is 'maxlen' or more.
In:
STR dst = destination string buffer
STR src = source string
DWORD maxlen = maximum number of characters to copy
Out:
STR = dst
) int __pascal strnicmp (STR str1, STR str2, DWORD maxlen);
Compares (signed) at most 'maxlen' characters of two strings ignoring
case.
In:
STR str1 = first string
STR str2 = second string
DWORD maxlen = maximum number of characters to compare
Out:
int = < 0 if first string is less than second string
= 0 if the two strings are the same
> 0 if first string is greater than second string
) STR __pascal strnset (STR str, int c, DWORD maxlen);
Sets at most 'maxlen' characters of string 'str' to character 'c'. If
'maxlen' is greater than strlen (str), then strlen (str) characters are
set
instead of 'maxlen'.
In:
STR str = string
int c = character
DWORD maxlen = maximum number of characters to set
Out:
STR = str
) STR __pascal strpbrk (STR str1, STR str2);
Scans string 'str1' for the first occurance of any character from
string
'str2'.
In:
STR str1 = string to scan
STR str2 = string with characters to scan for
Out:
STR = pointer to the first occurance of any of the characters from
string
'str2' in 'str1'. If no characters from 'str2' occur in 'str1',
then
NULL.
) STR __pascal strrchr (STR str, int c);
Scans string 'str' for the last occurance of character 'c'.
In:
STR str = string to scan
int c = character to find
Out:
STR = pointer to the last occurance of 'c' in string 'str'. If 'c'
does
not occur in 'str', then NULL.
) STR __pascal strrev (STR str);
Reverses string 'str' except for the terminating NULL character.
In:
STR str = string to reverse
Out:
STR = str
) STR __pascal strset (STR str, int c);
Sets all characters of string 'str' to character 'c'.
In:
STR str = string
int c = character to set
Out:
STR = str
) DWORD __pascal strspn (STR str1, STR str2);
Scans a string 'str1' for the initial segment consisting entirely of
characters from 'str2'.
In:
STR str1 = string to scan
STR str2 = string with characters to find
Out:
DWORD = length of the initial segment of 'str1' that consists
entirely of
characters from 'str2'.
) STR __pascal strstr (STR str1, STR str2);
Scans string 'str1' for the occurance of substring 'str2'.
In:
STR str1 = string to search
STR str2 = substring to find
Out:
STR = pointer to substring 'str2' withing string 'str1' if exists,
else
NULL.
) STR __pascal strupr (STR str);
Converts all characters in string 'str' to upper case.
In:
STR str = string to convert to upper case
Out:
STR = str
) int strcmpi (STR str1, STR str2);
Compares (signed) one string to another ignoring case.
In:
STR str1 = first string
STR str2 = second string
Out:
int = < 0 if first string is less than second string
= 0 if the two strings are the same
> 0 if first string is greater than second string
Notes:
) This is a macro that remaps to stricmp.
) int strncmpi (STR str1, STR str2, DWORD maxlen);
Compares (signed) at most 'maxlen' characters of two strings ignoring
case.
In:
STR str1 = first string
STR str2 = second string
DWORD maxlen = maximum number of characters to compare
Out:
int = < 0 if first string is less than second string
= 0 if the two strings are the same
> 0 if first string is greater than second string
Notes:
) This is a macro that remaps to strnicmp.
8.4 - File functions:
--------------------These functions use DPMI translation services to call real mode DOS
file
functions. Most of these functions use the low memory buffer area pointed
to
by _lowbufptr of size _lowbuflen. Any necessary buffering is done by
these
functions. For functions that return values, they will be positive. If a
return value is negative, it is a DOS or DPMI error code. I don't expect
you
have any files larger than 2147483647 bytes.
) long __pascal fileopen (STR fnm, int mode);
Open a file for reading, writing, or both.
In:
STR fnm = filename to open
int mode = RDONLY for read only access, WRONLY for write only access,
RDWR for read and write access
Out:
if successful:
long = file handle in low word, high word zero
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if the filename is
in
extended memory.
) long __pascal filecreate (STR fnm);
Create a file.
In:
STR fnm = filename to create
Out:
if successful:
long = file handle in low word, high word zero
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if the filename is
in
extended memory.
) If a file called 'fnm' exists, it is truncated to zero bytes and
opened
for reading and writing.
) long __pascal fileclose (WORD handle);
Close a file.
In:
WORD handle = file handle to close
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
) long __pascal filelseek (WORD handle, long off, int from);
Move a file pointer within a file.
In:
WORD handle = handle of file to seek in
long off = offset from seek start
int from = seek start, SEEK_SET is start of file, SEEK_CUR is current
location within file, SEEK_END is end of file
Out:
if successful:
long = file pointer offset from beginning of file
if failed:
long = DOS or DPMI error code
) long __pascal fileread (WORD handle, PTR buf, long size);
Read 'size' bytes from file 'handle' to 'buf'.
In:
WORD handle = handle of file to read from
PTR buf = buffer to read to
long size = maximum number of bytes to read
Out:
if successful:
long = number of bytes read
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if the source
buffer is
in extended memory.
) long __pascal filewrite (WORD handle, PTR buf, long size);
Write 'size' bytes to file 'handle' from 'buf'.
In:
WORD handle = handle of file to write to
PTR buf = buffer to write from
long size = number of bytes to write
Out:
if successful:
long = number of bytes written
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if the destination
buffer is in extended memory.
) long __pascal filedelete (STR fnm);
Delete a file.
In:
STR fnm = file to delete, drive and path allowed (no wildcards)
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if the filename is
in
extended memory.
) long __pascal filefind (STR buf, STR mask, int mode, int attr);
Search for a file.
In:
STR buf = 13 byte buffer for filename found
STR mask = search mask, drive, path, and wildcards allowed
int mode = FF_FIRST for first search, FF_NEXT for subsequent searches
int attr = search attributes
Out:
if successful:
long = 0
STR buf = filename found, not including drive or path
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if the mask is in
extended memory.
) This function modifies the DTA which is, by default, located in the
PSP
at the same location as the command line parameters. Be aware they
are
destroyed by this function.
) long __pascal filesize (WORD handle);
Get the size of a file.
In:
WORD handle = handle of file to get size of
Out:
if successful:
long = file size
if failed:
long = DOS or DPMI error code
) long __pascal filerename (STR dst, STR src);
Rename file 'src' to file 'dst'.
In:
STR dst = destination filename
STR src = source filename, drive and path allowed (no wildcards)
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if either filename
is
in extended memory.
) long __pascal filecopy (WORD dst, WORD src, long size);
Copy 'size' bytes from file 'src' to file 'dst' at the current location
in
both files.
In:
WORD dst = destination file handle
WORD src = source file handle
long size = number of bytes to copy
Out:
if successful:
long = number of bytes copied
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer.
) If there are fewer bytes left in the source file than requested to
copy,
only that many bytes will be copied.
) long __pascal fileexec (STR fnm, STR cline);
Execute a child program.
In:
STR fnm = file to execute, drive and path allowed
STR cline = command line for program (no longer than 126 bytes)
Out:
if successful:
long = child program return code
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer.
) long __pascal dircurrent (STR buf);
Store the current directory in 'buf'. The drive and initial backslash
are
not included in the name.
In:
STR buf = pointer to 64 byte buffer for directory name
Out:
if successful:
long = 0
STR buf = current directory on current drive
if failed:
long = DOS or DPMI error code
) long __pascal dircreate (STR dir);
Create a directory.
In:
STR dir = directory name
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
) long __pascal dirdelete (STR dir);
Delete a directory.
In:
STR dir = directory name
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
) long __pascal dirchange (STR dir);
Change the current directory.
In:
STR dir = directory name
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
) long __pascal drivecurrent (void);
Return the current logical drive.
Out:
if successful:
long = logical drive (1=A, 2=B, 3=C, etc...)
if failed:
long = DOS or DPMI error code
) long __pascal drivechange (int drive);
Change the current logical drive to 'drive'.
In:
int drive = logical drive (1=A, 2=B, 3=C, etc...)
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
) long __pascal drivesize (int drive);
Return the total size of logical drive 'drive'.
In:
int drive = logical drive (0=current, 1=A, 2=B, 3=C, etc...)
Out:
if successful:
long = size of logical drive
if failed:
long = DOS or DPMI error code
) long __pascal drivefree (int drive);
Return the free space available on logical drive 'drive'.
In:
int drive = logical drive (0=current, 1=A, 2=B, 3=C, etc...)
Out:
if successful:
long = free space on logical drive
if failed:
long = DOS or DPMI error code
8.5 - IRQ and mode switch functions:
-----------------------------------These are the low level PMC specific functions. They deal with mode
switching, code stubs, and IRQs.
) BOOL __pascal callrmnrint (int intr);
Call a real mode interrupt using the PMC low level mode switching
routines.
This function does not pass registers to or from the real mode
interrupt
handler. The handler is called with interrupts disabled.
In:
int intr = real mode interrupt to call
Out:
if successful:
BOOL = TRUE
if failed:
BOOL = FALSE (real mode nested stack unavailable)
) BOOL __pascal callrmnrfar (DWORD segoff);
Call a real mode FAR procedure using the PMC low level mode switching
routines. This function does not pass registers to or from the real
mode
procedure. The procedure is called with the interrupt flag unmodified.
In:
DWORD segoff = real mode segment:offset to call
Out:
if successful:
BOOL = TRUE
if failed:
BOOL = FALSE (real mode nested stack unavailable)
) BOOL __pascal callrmnriret (DWORD segoff);
Call a real mode IRET procedure using the PMC low level mode switching
routines. This function does not pass registers to or from the real
mode
procedure. The procedure is called with the interrupt flag unmodified.
In:
DWORD segoff = real mode segment:offset to call
Out:
if successful:
BOOL = TRUE
if failed:
BOOL = FALSE (real mode nested stack unavailable)
) BOOL __pascal callrmrint (int intr);
Call a real mode interrupt using the PMC low level mode switching
routines.
This function passes registers, including FLAGS, to and from the real
mode
interrupt handler. The handler is called with interrupts disabled.
In:
int intr = real mode interrupt to call
REGSTRUCT rs = register values to pass, CS, IP, SS, SP fields unused
Out:
if successful:
BOOL = TRUE
REGSTRUCT rs = register return values, CS, IP, SS, SP fields
unmodified
if failed:
BOOL = FALSE (real mode nested stack unavailable)
) BOOL __pascal callrmrfar (DWORD segoff);
Call a real mode FAR procedure using the PMC low level mode switching
routines. This function passes registers, including FLAGS, to and from
the
real mode procedure. The procedure is called with the interrupt flag
set
from the FLAGS field in REGSTRUCT rs.
In:
DWORD segoff = real mode segment:offset to call
REGSTRUCT rs = register values to pass, CS, IP, SS, SP fields unused
Out:
if successful:
BOOL = TRUE
REGSTRUCT rs = register return values, CS, IP, SS, SP fields
unmodified
if failed:
BOOL = FALSE (real mode nested stack unavailable)
) BOOL __pascal callrmriret (DWORD segoff);
Call a real mode IRET procedure using the PMC low level mode switching
routines. This function passes registers, including FLAGS, to and from
the
real mode procedure. The procedure is called with interrupts disabled.
In:
DWORD segoff = real mode segment:offset to call
REGSTRUCT rs = register values to pass, CS, IP, SS, SP fields unused
Out:
if successful:
BOOL = TRUE
REGSTRUCT rs = register return values, CS, IP, SS, SP fields
unmodified
if failed:
BOOL = FALSE (real mode nested stack unavailable)
) void __pascal setrmstub (PTR stub, void (*func) (void), int type);
Create a real mode to protected mode call stub.
In:
PTR stub = pointer to low memory block for stub
void (*func) (void) = protected mode function stub will call
int type = type of stub
Notes:
) Stubs will be described in more detail later.
) void __pascal setpmstub (PTR stub, void (*func) (void));
Create a protected mode IRQ/INT entry stub for a C function.
In:
PTR stub = pointer to low memory block for stub
void (*func) (void) = protected mode function stub will call
Notes:
) Stubs will be described in more detail later.
) void __pascal setcbstub (PTR stub, REGSTRUCT *__pascal (*func)
(REGSTRUCT *r));
Create a protected mode DPMI real mode callback entry stub for a C
function.
In:
PTR stub = pointer to low memory block for stub
REGSTRUCT *__pascal (*func) (REGSTRUCT *r) = C callback handler
function
Notes:
) Stubs will be described in more detail later.
) PTR __pascal setcb (SEGOFF *cb, PTR stub, REGSTRUCT *rs, REGSTRUCT
*__pascal (*func) (REGSTRUCT *r));
Set up a DPMI real mode callback. This function provides a quick way of
setting up a DPMI real mode callback handler. The handler is just a
plain
C function. It is the handler's responsibility to update the correct
address
for resumption of real mode execution in the register structure. The
handler
is entered with interrupts disabled. If it reenables interrupts, it
must
copy the register structure to another location and return a pointer to
that
structure rather than the one it was passed.
In:
SEGOFF *cb = SEGOFF structure for real mode callback address
PTR stub = low memory area for stub, if NULL, one will be allocated
REGSTRUCT *rs = register structure callback will use
REGSTRUCT *__pascal (*func) (REGSTRUCT *r) = C callback handler
function
Out:
if successful:
PTR = pointer to stub passed to this function or allocated
if failed:
PTR = NULL (DPMI callback or stub memory could not be allocated)
) void __pascal resetcb (DWORD cbsegoff, PTR stub);
Free a DPMI real mode callback.
In:
DWORD cbsegoff = real mode segment:offset of callback to free
PTR stub = stub low memory block to free
) PTR __pascal setirq (int irq, SEGOFF *ormiv, SELOFF *opmiv, PTR stub,
DWORD segoff, void (*func) (void), int type);
Set up an IRQ handler. This function provides a quick way of setting up
an
IRQ handler. You may specify the type of handler. A protected mode IRET
routine, a protected mode C function, or you may specify seperate
handlers
for real and protected mode.
In:
int IRQ = IRQ number to set up handler for
SEGOFF *ormiv = SEGOFF structure for old real mode IRQ vector
SELOFF *ormiv = SELOFF structure for old protected mode vector
PTR stub = low memory area for stub, if NULL, one will be allocated
DWORD segoff = segment:offset of real mode handler if you are
specifying
your own
void (*func) (void) = protected mode IRQ handler function
int type = type of IRQ hook, IRQI, IRQC, or IRQOWN
Out:
if successful:
PTR = pointer to stub passed to this function or allocated
if failed:
PTR = NULL (stub memory could not be allocated)
Notes:
) This function does not alter the mask for the IRQ hooked.
) Keep in mind that IRQ 2 is really IRQ 9, within the handler you must
do
the extra EOI to the slave PIC.
) void __pascal resetirq (int irq, SEGOFF *ormiv, SELOFF *opmiv, PTR
stub);
Free an IRQ that has been hooked.
In:
int IRQ = IRQ number to free
SEGOFF *ormiv = SEGOFF structure containing old real mode IRQ vector
SELOFF *ormiv = SELOFF structure containing old protected mode vector
PTR stub = stub low memory block to free
Notes:
) This function does not alter the mask for the IRQ hooked.
) BOOL __pascal getirqmask (int irq);
Get hardware IRQ PIC mask.
In:
int irq = IRQ number mask to get
Out:
BOOL = current mask status, IRQENABLED or IRQDISABLED
) void __pascal setirqmask (int irq, BOOL status);
Set hardware IRQ PIC mask.
In:
int irq = IRQ number mask to set
BOOL status = new mask status, IRQENABLED or IRQDISABLED
8.6 - Other functions:
---------------------These are mostly misc misc functions that did not fit any of the other
categories.
) void emit_ (BYTE, ...);
This macro inserts literal values directly into object code.
) void cli_ (void);
This macro disables the interrupt flag.
) void sti_ (void);
This macro enables the interrupt flag.
) BOOL __pascal kbhit (void);
Check for keystroke using BIOS keyboard routines.
Out:
BOOL = TRUE if keypress available, else FALSE
) WORD __pascal getch (void);
Get keystroke using BIOS keyboard routines.
Out:
WORD = scan code and character as returned by BIOS INT 16h AH=0
) STR __pascal getenv (STR var);
Get an environment string.
In:
STR var = environment variable to get string of, must be upper case
Out:
STR = environment string if variable exists, else NULL
Notes:
) This function uses the real mode low memory buffer to store the
string
because the environment segment is located below the start of the
program.
) STR __pascal getexe (void);
Get the full pathname of the current program.
Out:
STR = full pathname of this program as it was run
Notes:
) This function should only be called under DOS 3.0 or greater.
) This function uses the real mode low memory buffer to store the
string
because DOS stores the pathname in the environment segment, which is
located below the start of the program.
) long __pascal dosfunc (STR str, int AXval);
Call DOS function 'AXval' buffering 'str' through the real mode low
memory
buffer if it is in extended memory. The address of 'str', in low
memory, is
passed to the DOS function in DS:DX. If the DOS function returns with
the
carry flag set, it is taken as an error.
In:
STR = string to pass pointed to by DS:DX
Out:
if successful:
long = AX returned from DOS
if failed:
long = AX returned from DOS | 0xffff0000
Notes:
) This function is used internally by some PMC functions, but it is
made
avaliable to you if you need it.
) This function uses the real mode low memory buffer if the string is
in
extended memory.
) long __pascal dosputstr (STR str);
Put a string to the default output device using DOS.
In:
STR = string to put
Out:
if successful:
long = 0
if failed:
long = DOS or DPMI error code
Notes:
) This function uses the real mode low memory buffer if the string is
in
extended memory.
) void __pascal exit (int retval);
Immediately terminate execution and return to DOS.
In:
int retval = byte value to return to DOS
Notes:
) This function obviously never returns.
8.7 - Low level functions:
-------------------------The PMC low level mode switching functions are a fast alternative to
DPMI
translation functions. They use the DPMI raw mode switching services.
Registers may or may not be passed, depending on your needs. Real mode
FAR
and IRET procedures can be called from protected mode. Protected mode C
and
IRET procedures may be called from real mode. All calls to protected mode
from
real mode set up the C standard system. That is, DS = SS = ES, and the
direction flag clear.
If registers are to be passed, the REGSTRUCT structure 'rs' is used.
Real
mode calls from protected mode pass the general registers EAX, EBX, ECX,
EDX,
ESI, EDI, and EBP, the segment registers DS, ES, FS, and GS, and the
FLAGS
register. If a call to a real mode IRET routine is done, the interrupt
flag
is cleared for the call to the routine, but the image of the flags on the
IRET
frame is as was passed in the 'rs' structure. These same registers are
put
back in the register structure before switching back to protected mode. A
stack is provided by PMC for real mode execution, you may not specify
your
own stack. No checking is done to see if stack space is available, so you
must make sure of this yourself (the C 'callrm' function does this).
For a call to protected mode from real mode, only the general registers
EAX,
EBX, ECX, EDX, ESI, EDI, and EBP are passed. The DS, ES, and SS registers
are
aet to the data selector. The FS and GS selectors are undefined. The same
general registers are passed back from protected mode along with FLAGS
(in the
'rs' structure ofcourse).
The addresses of the mode switch functions are stored within global
variables available both in real mode and in protected mode. The low
level
switching functions should be called only from assembly code as they
destroy
the contents of the general registers and FS and GS in protected mode.
The
protected to real switch functions expect the standard protected mode C
system. That is, DS = ES = SS, and the direction flag clear. The
functions are
as follows (as seen from ASM):
) _pmcrmnoregs
Call real mode FAR procedure passing no registers.
In:
EBP = real mode segment:offset to call
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP = ? (destroyed)
) _pmcrmregs
Call real mode FAR procedure passing registers.
In:
EBP = real mode segment:offset to call
REGSTRUCT rs = register values to pass, CS, IP, SS, SP fields unused
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP, FS, GS = ? (destroyed)
REGSTRUCT rs = register return values, CS, IP, SS, SP fields
unmodified
) _pmcrminoregs
Call real mode IRET procedure passing no registers.
In:
EBP = real mode segment:offset to call
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP = ? (destroyed)
Notes:
) The real mode procedure is entered with interrupts clear.
) _pmcrmiregs
Call real mode IRET procedure passing registers.
In:
EBP = real mode segment:offset to call
REGSTRUCT rs = register values to pass, CS, IP, SS, SP fields unused
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP, FS, GS = ? (destroyed)
REGSTRUCT rs = register return values, CS, IP, SS, SP fields
unmodified
Notes:
) The real mode procedure is entered with interrupts clear.
The real mode to protected mode functions are stored in dword memory
variables. In real mode this is a 16bit segment:offset rather than a
32bit
offset. They obviously must be called as FAR procedures. All registers
are
destroyed by the real mode functions. This is EAX, EBX, ECX, EDX, ESI,
EDI,
EBP, DS, ES, FS, and GS. FLAGS are NOT passed to protected mode functions
that
are called, but they are passed back. You may call protected mode C
functions
directly using these functions because they set up the standard C system
once
in protected mode. Though you will obviously not be able to pass stack
parameters. EBP is used to pass the 32bit offset of the procedure to call
in
protected mode. Regular procedures (RET terminated) and IRET terminated
procedures may be called. The real mode functions are as follows:
) rmcpmnoregs
Call a protected mode near procedure passing no registers.
In:
EBP = protected mode code offset to call
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP, DS, ES, FS, GS = ? (destroyed)
) rmcpmregs
Call a protected mode near procedure passing no registers.
In:
EBP = protected mode code offset to call
REGSTRUCT rs = register values to pass, DS, ES, FS, GS, CS, IP, SS,
SP,
and FLAGS fields unused
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP, DS, ES, FS, GS = ? (destroyed)
REGSTRUCT rs = register return values, DS, ES, FS, GS, CS, IP, SS,
and SP
fields unmodified
) rmcpminoregs
Call a protected mode IRET procedure passing no registers.
In:
EBP = protected mode code offset to call
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP, DS, ES, FS, GS = ? (destroyed)
Notes:
) The protected mode procedure is entered with interrupts clear.
) rmcpmiregs
Call a protected mode IRET procedure passing no registers.
In:
EBP = protected mode code offset to call
REGSTRUCT rs = register values to pass, DS, ES, FS, GS, CS, IP, SS,
SP,
and FLAGS fields unused
Out:
EAX, EBX, ECX, EDX, ESI, EDI, EBP, DS, ES, FS, GS = ? (destroyed)
REGSTRUCT rs = register return values, DS, ES, FS, GS, CS, IP, SS,
and SP
fields unmodified
Notes:
) The protected mode procedure is entered with interrupts clear.
8.8 - Code stubs:
----------------PMC provides functions for creating three basic types of code stubs.
Code
stubs are simply little pieces of code that set up some things and
redirect
execution to another routine. The functions that create the code stubs
take,
as parameters, a pointer to a low memory buffer for the code stub, and a
pointer to a protected mode function. Code stubs are transient things,
you may
create and destroy them at will. The number of code stubs you can create
is
only limited by the amount of low memory available.
The first type of code stub is a type of real mode callback to
protected
mode. Its main purpose is to hook IRQs in real mode and redirect them to
protected mode, but it is more versatile than just that. The code stub is
a
real mode routine which must be called in real mode. It can return via a
RETF
or IRET instruction. This means it can be called as a FAR procedure or as
an
IRQ/INT. The code stub can call a protected mode C function or an IRETD
terminated function. The code stub may also store the values of the real
mode
registers in the 'rs' register structure before calling protected mode,
and
copy the values from the 'rs' structure back into registers before it
returns.
When setting up a real mode stub, you must specify the type. There are
eight
types of stubs. This encompasses all the possible types of entry, exit,
and
register passing code. The logic for the two main types of stubs (passing
and
not passing registers) follows shortly. The eight sub-types are as
follows:
RMSTUBINRI:
Call protected mode IRETD routine, do not pass registers, exit with
IRET.
RMSTUBINRC:
Call protected mode near routine, do not pass registers, exit with
IRET.
RMSTUBFNRI:
Call protected mode IRETD routine, do not pass registers, exit with
RETF.
RMSTUBFNRC:
Call protected mode near routine, do not pass registers, exit with
RETF.
RMSTUBIRI:
Call protected mode IRETD routine, pass registers, exit with IRET.
RMSTUBIRC:
Call protected mode near routine, pass registers, exit with IRET.
RMSTUBFRI:
Call protected mode IRETD routine, pass registers, exit with RETF.
RMSTUBFRC:
Call protected mode near routine, pass registers, exit with RETF.
No passing registers:
PUSH EAX
PUSH EBX
PUSH ECX
PUSH EDX
PUSH ESI
PUSH EDI
PUSH EBP
PUSH DS
PUSH ES
PUSH FS
PUSH GS
EBP = protected_mode_function_address
CALL _rmcpmnoregs or _rmcpminoregs
POP GS
POP FS
POP ES
POP
POP
POP
POP
POP
POP
POP
POP
DS
EBP
EDI
ESI
EDX
ECX
EBX
EAX
RETF or IRET
Passing registers:
PUSH rs.d.EAX
PUSH rs.d.EBX
PUSH rs.d.ECX
PUSH rs.d.EDX
PUSH rs.d.ESI
PUSH rs.d.EDI
PUSH rs.d.EBP
PUSH rs.w.DS
PUSH rs.w.ES
PUSH rs.w.FS
PUSH rs.w.GS
PUSH rs.w.SS
PUSH rs.w.SP
PUSH rs.w.FLAGS
rs.d.EAX = EAX
rs.d.EBX = EBX
rs.d.ECX = ECX
rs.d.EDX = EDX
rs.d.ESI = ESI
rs.d.EDI = EDI
rs.d.EBP = EBP
rs.w.DS = DS
rs.w.ES = ES
rs.w.FS = FS
rs.w.GS = GS
rs.w.SS = SS
rs.w.SP = SP (value of SP at entry to code stub, before all the
PUSHing)
rs.w.FLAGS = FLAGS
EBP = protected_mode_function_address
CALL _rmcpmnoregs or _rmcpminoregs
EAX = rs.d.EAX
EBX = rs.d.EBX
ECX = rs.d.ECX
EDX = rs.d.EDX
ESI = rs.d.ESI
EDI = rs.d.EDI
EBP = rs.d.EBP
DS = rs.w.DS
ES = rs.w.ES
FS = rs.w.FS
GS = rs.w.GS
FLAGS = rs.w.FLAGS
POP
POP
POP
POP
POP
POP
POP
POP
POP
POP
POP
POP
POP
POP
rs.w.FLAGS
rs.w.SP
rs.w.SS
rs.w.GS
rs.w.FS
rs.w.ES
rs.w.DS
rs.d.EBP
rs.d.EDI
rs.d.ESI
rs.d.EDX
rs.d.ECX
rs.d.EBX
rs.d.EAX
RETF or IRET
Notice that in the passing registers version, the rs.w.FLAGS value is
put
into the FLAGS register directly. If the return is via an IRET, that
FLAGS
value is destroyed. If you want to pass FLAGS back when using an IRET
return,
you must modify the FLAGS image on the real mode stack. This is the
reason
SS and SP are passed to (but not from) your code in the 'rs' structure
when
passing registers.
The second main type of code stub is a protected mode one. This one
just
sets up the default C system, DS = ES = SS, direction flag clear, and a
good
stack. It is for setting up C functions as IRQ/INT handlers in protected
mode.
This is only needed for the protected mode INT redirection. The real mode
code
stubs always enter their target protected mode functions with a valid C
setup.
The logic is as follows:
PUSH
PUSH
PUSH
PUSH
PUSH
PUSH
PUSH
PUSH
EAX
EBX
ECX
EDX
DS
ES
FS
GS
PUSH SS
PUSH ESP
DS = datasel
ES = datasel
CLD
SS:ESP = next nested stack (SS = datasel)
CALL protected_mode_function_address
POP
POP
POP
POP
POP
POP
POP
POP
POP
POP
ESP
SS
GS
FS
ES
DS
EDX
ECX
EBX
EAX
STI
IRETD
Notice that ESI, EDI, and EBP are not preserved. The C function is
assumed
to do that (as they all do). The STI is done just before the IRETD for
DPMI
compliance.
The final type is another protected mode stub. This one is an interface
to
DPMI real mode callbacks. The C function must pass back a pointer to a
register structure within the data segment. The incoming structure is
also
assumed to be within the data segment (offset from the base of the data
segment). The logic is:
PUSH SS
PUSH ESP
DS = datasel (ES assumed to be datasel on entry)
CLD
SS:ESP = next nested stack (SS = datasel)
PUSH EDI (pointer to register structure for function)
CALL protected_mode_function_address
EDI = EAX (pointer to register structure returned from function)
POP ESP
POP SS
IRETD
----------------------------------------------------------------------------9 - Miscellaneous:
-----------------Well, its been a long document. We are getting to the end here, so hang
on.
9.0 - Notes:
-----------These are just some minor technical notes on PMODE and PMC, as well as
some
other things that might help in certain situations.
) Every segment defined MUST have a class. Else TLINK might put it after
the
terminating STACK segment, which is used at startup for the base of
free
low memory. In simple terms, if every defined segment does not have a
class
(eg: 'CODE'), the code might screw up.
) BCC32 uses TASM32 to compile code through assembly. This is just a
32bit
version of TASM. If you don't have it, or if you have it but do not
feel
like waiting as long for it to compile as BCC32, copy the real mode
TASM.EXE
to TASM32.EXE and it will be used instead.
) The PMC real mode calls to protected mode will not preserve the high
word
of ESP.
) Some PMC variables are defined as an int (4 bytes) instead of a BYTE or
WORD
to keep BCC32 from expanding and storing them as ints locally.
) Be warned, many DPMI hosts out there are VERY buggy.
) BCC32 4.00 buggies (at least the ones I know of):
) Temporary register variables may not be preserved across ASM blocks
and
intrinsic __int__ instructions. These are not the the kind of
register
that can be explicitly defined, but rather internal TEMPORARY values
of
variables. BCC32 does not seem to keep track of them correctly. To
fix
this, explicitly preserve all registers across ASM blocks. Keep
inline
code in seperate functions. Or use seperate ASM modules.
) The intrinsic version of memcmp compares signed chars, it should be
unsigned chars.
) The intrinsic port functions assume only the low 10 bits of the port
number are valid in hardware. In other words, they may read or write
to
the wrong port. For example, a 'mov dx,-57' (-57 = 0xfc7) might be
done
when youre trying to inport from 0x3c7. You should do any port
accesses
through inline assembly code.
) Uninitialized global and static variables will be put in the BSS
segment
and any reference to their offset will use the offset from the start
of
the BSS segment, not DGROUP. Compiling through assembly fixes this
problem.
) For some reason, I can't get certain __fastcall functions to return a
value.
) And a whole bunch of other stuff which makes it almost impossible to
use.
) Unlike PMODE, most of my PMC code is totally uncommented. Didn't feel
like
it.
9.1 - Final word:
----------------I really dislike writing documentation. But alas... It is necessary. If
some
things are not made very clear by this doc, see the library sources for
many
practical examples. Also experiment with everything. I have tried to make
sure
it is as bug free as possible. And I think I did a better job than
Borland,
but then again who couldn't. There is always the possiblity of that one
little
erratum escaping notice.
As noted at the top, PMC is freeware. You may use/abuse it in any
manner you
wish (I don't want to hear what you do to your code, keep those sick
ideas to
yourself). All source is provided because, well, why not? It does more to
help
you understand the system, as well as allows you to put it through your
own
scrutiny. And hey, if you learn something from it, thats good too. If you
market this thing and make millions from it, FUCK YOU! Unless ofcourse
you
pass some of that money on to something like Amnesty International. And
if you
EVER donate ANY of that money to some political campaign! FOR ALL OF
INFINITE
TIME, YOU SHALL ROT IN THE DEEPEST BOWELS OF ABYSSES UNBEKNOWNST TO THE
MOST
ANCIENT OF ALL OF THE DEMONS OF THE COSMOS. IMAGES OF THE MOST HORRID
KIND
WILL FILL YOUR SLEEPLESS BRAIN. VISIONS OF THINGS DEAD, AND NOT QUITE
DEAD,
SHALL DANCE UPON THE BLOODIED EARTH. CRAWLING DEFORMED SHAPES SCAMPER
ABOUT,
TEARING EACH OTHER APART AND FEASTING UPON THE CARNAGE. APPARITIONS OF
ALL
FORMS ARISE FROM EVERY SHADOW CAST BY THE EVIL TREES, PERPETUALLY STARING
AT
YOU FROM DIRECTIONS NOT CONCEIVABLE UNDER THE LAWS OF A UNIVERSE SANE.
THESE
MANIFESTATIONS AND OTHERS, NOT ENTIRELY DESCRIBABLE, SHALL ASSAULT YOU
MERCILESSLY. BUT NONE WILL BE QUITE SO AWFUL AS THAT BLASPHEMOUS ENTITY.
THAT
HIDEOUS DEVIL WHICH PRESIDES OVER THIS MACABRE PAGEANT. ME!!! (Note, this
is
when I am angry. When I am happy, the crawling deformed shapes turn into
butterflies, and the evil trees become lovely daisies, etc...).
I must again thank Dave Cooper (White Shadow) for the writing up some
of the
code and dox. Now it is for the world to do with as it pleases. It would
be
nice if someone out there coded up a nice ANSI C library for PMC. Or used
the
PMODE extender for some other languages. After all, it is all free. I
would
love to code my own C compiler especially for protected mode, but time
and
lazieness are against me. Maybe someone out there feels like doing it.
Anyway, if you find this stuff useful, great, if not, too bad. If you
wish
to tell me how much you hate me, I seem to be at tran@phantom.com. Though
I
usually ignore most of my mail, except when I feel like answering, which
is
very rare, so I'll probably ignore you.
I code too much...
Tran...
Download