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...