Dec 052017
Utility needed for writing doors for the Falken BBS system. .
File DOORUTIL.ZIP from The Programmer’s Corner in
Category Recently Uploaded Files
Utility needed for writing doors for the Falken BBS system. .
File Name File Size Zip Size Zip Type
BBSCFG.H 2946 1147 deflated
CSWITCHR.DOC 50471 14278 deflated
DATABASE.TXT 8304 2919 deflated
DOORS.DOC 32606 9606 deflated
DOORUTIL.C 20023 5744 deflated
DOORUTIL.H 7336 2262 deflated
DOORUTLS.OBJ 5809 2955 deflated
EDITOR.OBJ 8481 4563 deflated
FEEDBACK.C 1850 855 deflated
FOSSIL.DOC 35952 10947 deflated
FSUBS.DOC 3717 1038 deflated
FSUBS.OBJ 1128 561 deflated
GFILES.C 3930 1607 deflated
GFILES.DOC 1094 493 deflated
LMTCA.ASM 9156 1668 deflated
LMTCA.OBJ 872 486 deflated
MEMMODEL.TXT 6303 2561 deflated
NUQ.C 2736 1116 deflated
NUQ.DOC 1504 748 deflated
NUQ.EXE 24622 13876 deflated
SERIAL.DOC 5638 2254 deflated
SMTCA.ASM 8756 1616 deflated
SMTCA.OBJ 857 487 deflated
STRUCTS.H 10475 3598 deflated
WRAP.OBJ 472 418 deflated

Download File DOORUTIL.ZIP Here

Contents of the CSWITCHR.DOC file


Multitasking Functions for DOS Programs

Copyright 1990 by Herb Rose
All Rights Reserved

Version 3.1 release date : Sep 9, 1992

This file is an introduction to the Cswitch functions. These
functions provide limited multitasking capabilities for programs
running under MS-DOS or PC-DOS.

The term 'limited multitasking' is used because these routines do not
provide a multitasking user interface like DESQVIEW or WINDOWS. These
routines are programming tools that will enable a C programmer to
write applications that consist of independent, simultaneously executing

Using Cswitch, you can execute functions as independent tasks, which
execute concurrently in a time-slicing environment. You may also load
and execute programs separately. Cswitch provides you with a priority
driven scheduler and dispatcher, DOS interface, and task control
functions to implement true multitasking under your program's control.

Before I continue the explanation of Cswitch, please bear with me as
I detail the legalities of using these routines.


This software is distributed as is, with no warranty of any kind,
either expressed or implied. No warranty or guarantee is made that
the software is complete or error free. All liability and risk
associated with the use of this software is assumed by the user.
Under no circumstances will ADEPT Software, its owners, or its
agents be responsible for any damages arising from the use, or the
inability to use this product. This applies to all damages and
remedies including, but not limited to, loss of or damage to property,
and loss of profits.

This software has been tested in many configurations, under various
conditions, and has consistently performed acceptably. To the best
of my knowledge, there are no errors or omissions in the software
that will pose a problem to the user.

The user must be aware, however, that this is a programmer's tool,
and by it's very nature will affect the software that controls the
user's computer. All reasonable precautions should be taken to
protect your computer from the affects of an errant operating system.

In short -

Version 2.0 Update Information

When loading programs from disk, Cswitch now searches the current
directory, and those directories specified in the DOS PATH environment

There was a report of Cswitch not working properly with PS/2 Model 70
computers. The report suggested a problem with the MCA failsafe
timer. I have not yet been able to verify that there is a problem,
but will continue to monitor and test.

Version 3.0 Update Information

When EMS is enabled, Cswitch now detects back-filled conventional
memory, and allocates handles so that tasks can be loaded into the
backfilled portion of conventional memory. This makes it possible to
load multiple tasks into conventional memory. In my tests, using a
80386 PC, with 4 meg, and QEMM, I showed task sizes up to 440K being
loaded into backfilled conventional ram, and switching properly.

Also - several new methods of loading tasks are available :
A 'pipes' system is now available, so that a tasks STDIN/STDOUT are
redirected to a CSWITCH internal buffer (pipe). The call LOADPIPE
acts like LOADTASK, but redirects the new tasks STDIN/STDOUT to the
pipe. The calling task can send data to the new task by calling
SEND_PIPE with the tasks TCB number. Data from the task can be read
using RECV_PIPE. This is similar to UNIX's POPEN() function.

In addition, EXECPROG causes the calling task to be completely replaced
by the new task. The new task inherits the caller's TCB, pipes, and
files. This is similar to the EXEC() function in UNIX.

A function similar to UNIX SIGNAL() processing is available.
KSIGNAL() is used to inform Cswitch of the address of a function to be
executed if the task is terminated prematurely. This function should
NOT terminate the task (that is being done already) but should close
any open files, and perform basic cleanup services.

If it sounds like I am mimicking a lot of UNIX functions, it is because
they are a useful reference when adding features!

Shareware Distribution

The shareware version of Cswitch is available on my BBS, INFO*SHARE,
for downloading. The number is (703) 803-8000, 300-1200-2400, 24 Hrs.

The source files included in this registered distribution are NOT
to be distributed. They are intended only for registered users of
Cswitch. Please download the shareware distribution set if you wish
to share this package with others.

Introduction to Multitasking

Multitasking is not a new concept. Computers have been programmed to
execute multiple tasks concurrently for many years. It was observed
a long time ago that most programs spent the majority of their time
waiting for data input or output. Since users and most peripheral devices
are notoriously slow compared to the execution speeds of modern computers,
most programs simply sit there 'twiddling their thumbs' waiting for data.
It was observed that the computer's computational capabilities could be
used more efficiently if another program were allowed to use the processor
while a program that was just waiting was put into a dormant state.
Carrying this observation one step farther, we find true multitasking.
Multitasking is the ability to have several programs loaded into the
computers memory, and apparently executing simultaneously. In reality,
there is a controlling program, called the Kernel, or Scheduler, that
causes the computer to execute some of each programs instructions in
a round-robin fashion. Programs that are waiting for something to
happen, such as waiting for a keystroke, or for a disk transfer to
complete, are normally placed into a 'waiting' state, and do not get
placed back into execution until the event they are waiting for has
actually happened.

Notice that I said programs were 'apparently' running simultaneously.
It is impossible for a microcomputer, such as the PC, to execute more
that one program simultaneously. Other computers may have several micro-
processors, and can actually execute more than one task simultaneously.
When there is only one microprocessor, the tasks must be executed one
at a time. The term 'time-slicing' is used to describe the operation
of a multi-tasking operating system, meaning that the processor will
execute each task's instructions for a short time, then will execute
another task's instructions for a while, eventually allowing all the
tasks to get some of their work done in a round-robin fashion. If the
amount of time given to each task is very large, say one or two seconds,
there could be a significant amount of time for each task to remain
idle while it awaited it's turn to execute. If the amount of time
given to each task (called the 'time slice') is too short, then too
much of the computer's time will be spent saving and restoring task
context information, diminishing the efficiency of the system. A
good time slice setting is one that allows many different tasks to
execute in a short period of time, without noticably affecting the
efficiency of the system. Cswitch uses the timer interrupt for the
PC to trigger a task switch. This occurs approximately 18 times per
second, or once every 55 milliseconds. Since a normal time slice on
a real multitasking operating system is usually around 50 microseconds,
our time slice is actually very large. In fact, 55 milliseconds is
way too big for a real-time operating system, which has to react to
external stimulus as quickly as possible. With 18 tasks loaded into
Cswitch, each task will only get to run once per second, for about
1/18 second. If some of the tasks are higher priority than others,
the lower priority tasks may not run at all for several seconds. This
is clearly not acceptable for programs that must react to external

The 55 millisecond time slice, however, is fine for running one or
two tasks at a high priority, and allowing several other tasks to
run at a lower priority. This gives the effect of 'background'
processes running.

If you find the 55 millisecond time slice inadequate, you should
consider altering the timer chip's programming so that it provides
a timer interrupt more often. Your interrupt handler will have to
count the number of interrupts, and activate the DOS time-of-day
routine (the normal destination of interrupt vector 8) once every
55 milliseconds, otherwise the computer's internal date and time
settings will not be accurate.

Now we will focus on how you get multiple tasks to cooperate so that a
given job will get done. Basically, there are only 2 areas that we are
concerned with -

1. Passing information from one task to another, and
2. Preventing another task from interfering with a resource you are using.

Moving Data Between Tasks

The first area is handled in Cswitch via Message Queues. A queue is
simply a list of messages. In this case, Cswitch has 64 queues, numbered
0-63. Any task may place a message onto any of the queues, and any
task may fetch a message from any of the queues. If there is more than
one message waiting on a queue, they will be fetched sequentially, on a
first-in, first-out basis.

A message may be any format, containing any information that may be needed
by another task. When placing a message on a queue, you must tell Cswitch
which queue to put it on, how long the message is (in bytes), and the
address of the first byte of the message. Cswitch will copy as many
bytes as you have told it into a temporary buffer, and will place a
pointer to that buffer into the message list for that queue.

When you fetch a message from a queue, you must tell Cswitch which queue
to read a message from, the maximum number of bytes you are willing to
receive, and where to put the message. After Cswitch has copied the
message (or as much of the message as you will allow) into your memory,
the temp buffer is released, and the message pointer is deleted from
the queue's list. Note : if the message contained more bytes than
you received (by specifying a maximum byte count lower than the actual
size of the message), the message will still be removed from the
queue. You should make certain you allow enough room to receive any
message your task is likely to receive.

It is up to the individual tasks to coordinate which queue numbers will
be used to pass messages back and forth. In the Falken BBS, a multi-
line Bulletin Board System program written with Cswitch, the main
BBS program assigns input and output queues to each task when it is
started. Queue number 1 is reserved, and is used to send the queue
assignments to the tasks. In other words, when a task first runs
under control of Falken, it must read a message from queue number 1.
That message will tell it which queue it must read messages from, and
which queue it can write messages to, for the rest of the time it is


The second area of concern is to prevent another task from interfering
with a resource that you are using. A resource may be almost anything,
from a disk file to a memory buffer, to a peripheral device. Cswitch
uses a semaphore system to control access to these resources.

You must know in advance which resources must be controlled with
semaphores. If you are going to allow several tasks to access a
memory area simultaneously, it may be a good idea to control it, since
one task may try to write new information to the memory while another
task is trying to read the old information.

Semaphores work like this -
A semaphore has an owner and a waiting list. If your task attempts to
'attach' to the semaphore, but another task already 'owns' the semaphore,
your task gets placed onto the 'waiting list', and does not get to run
anymore (it is taken out of the ready queue). Your task will remain
dormant until it is the 'owner' of the semaphore.

When the owner of the semaphore 'releases' it, the first task in the
'waiting list' is made the new owner, and is allowed to run again.

Note : When you 'attach' to a semaphore, you will become the owner of
the semaphore before you are allowed to continue. Your
program remains dormant until it is the owner, and you must release the
semaphore when you are done with it, to allow other programs to run,
which may be waiting for the semaphore.

If you attempt to 'attach' to a semaphore, and it is unowned, your
task becomes the owner and is allowed to continue running immediately.
You must still release the semaphore when you are done.

Cswitch Functions

Cswitch allows tasks to perform the following functions :

- All normal DOS and BIOS functions
- Send information between tasks with Message Queues
- Control system resources with Semaphores
- Alter task priority
- Suspend / Resume task execution
- Delay for a time period (sleep)
- Start new tasks, either sharing existing code, or loading a new
program file from disk.
- Load small programs into expanded memory to allow more programs
to execute simultaneously.

To use Cswitch, you must link your main program with the CSWITCH1.OBJ,
CSWITCH2.OBJ and LMTC.OBJ files. These files provide interrupt level
control for task switching, and the operating system functions, such as
message passing and semaphore control. In addition, programs which
will be loaded and executed under control of Cswitch must be linked
with LMTC.OBJ or SMTC.OBJ (for large or small model, respectively).
These files contain interface routines for calling system services.

Your main program must call the function START_MT().

START_MT() initializes the Cswitch data areas and enables multitasking.
It intercepts several interrupt vectors, and builds Task Control
Blocks for your main program and for an 'idle task'. The idle task
runs at a low priority, and checks the delay and termination queues
regularly to service tasks waiting for those functions.
After the call to START_MT(), multitasking is fully operational,
and you may spawn tasks and load programs. Note that your main
program has a Task Control Block (TCB) and gets scheduled for time-
slicing just as every other task does. Your main program runs at a
priority of 1 (the highest priority allowed).

Before terminating, your main program MUST call the function END_MT().
END_MT restores the interrupt vectors, releases all unnecessary
memory, and halts all multitasking. Essentially, it restores the
computer to the same state it was in prior to the START_MT() call.


If you allow your program to terminate without calling END_MT, almost
anything can happen, since the interrupt vectors are still pointing
to Cswitch code which may or may not be overwritten by DOS. At a
minimum, your system will lock up. A worst-case scenario could
involve unrecoverable damage to disk files.

It is highly recommended that you test all your programs that use
multitasking on a 'test' computer, a separate machine that does not
contain any critical files or programs, to prevent loss of valuable
data or programs. At a minimum, make sure all your important files
are backed up before testing new programs that use Cswitch routines.

Executing Tasks

There are 2 methods of starting new tasks under Cswitch. The first
method is to SPAWN a new task that will execute a portion of your code
independently. Normally, this facility will be used to execute a
function as a separate task. The new task will be given it's own
stack and TCB, and will be scheduled for time-slicing normally. It
will begin executing code at the address you specify (normally a
function name). It may call other functions, and may manipulate
global data normally. Any automatic (local) variables will reside
on the new tasks stack, so there will not be any interference when
a function is called by 2 different tasks. In fact, you can spawn
several new tasks which execute the same function if you like.

When the function being executed returns to the caller, the task will
be terminated. Any functions that are called by the spawned task will
return normally, of course. Only when the function being spawned
returns will the task terminate.

The second method of starting new tasks is to load a program from
disk and execute it. The function LOADTASK() is used to load an EXE
or COM file from disk and execute it. You must supply 3 parameters
to the LOADTASK function - the command line to run the program and
the priority to be assigned to the task, and a flag indicating whether
the task may be loaded into expanded memory or not.

For instance, to load and run the program MYPROG.EXE with the parameter
'myfile.dat', you would normally enter a DOS command line like this :

C:>myprog myfile.dat

To load and run the program under Cswitch, you would call the LOADTASK
function like this :

loadtask("myprog myfile.dat",4,1);

This loads and runs 'myprog.exe', gives it the command line parameter
'myfile.dat', and allows it to run at priority 4, and allows it to be
loaded into expanded memory, if possible.

The .EXE extension is assumed if no extension is provided. You can
load and execute .COM files by specifying the .COM extension. You
must specify the full path of the program to be executed, if it is not
in the current default directory. Cswitch will search the directories
in your your PATH ebvironment variable for the program, if it is not
found in the current disrectory.


The Cswitch scheduler maintains a list of tasks that are waiting to
execute, called the 'ready queue'. When a task switch occurs, the
TCB at the head of the ready queue is fetched, and allowed to run.
Normally, the TCB that was just executing is inserted back into the
ready queue. In some cases, such as waiting for semaphore, delaying,
etc. the task will not be re-inserted into the ready queue.

Tasks are inserted into the ready queue according to their relative
priority. Each task is assigned a 'base priority' from 1 to 10.
1 is the highest priority, and 10 is the lowest. Each task also has
a 'current priority', which is used to insert TCBs into the ready
queue. Each time a task is fetched from the ready queue, the 'current
priority' of all the other TCBs on the ready queue is decremented
by 1. When a task is re-inserted into the ready list, it's current
priority is set equal to it's base priority, then it is inserted
into the ready queue so that it is in front of all tasks whose current
priority is lower. In this way, tasks with a higher priority will
be allowed to execute more often than tasks of lower priority. The
tasks with lower priority will remain on the ready queue until their
current priority increases to the point that the other tasks get
inserted behind them, and they work their way to the head of the queue.

Using Expanded Memory

Cswitch can use expanded memory to load and run tasks that require less
than 64K. By calling START_SWAPPING(), you tell Cswitch to start using
expanded memory. Any task that is loaded after calling START_SWAPPING()
is eligible to be loaded into expanded memory.

Cswitch System Services

Cswitch must replace some of the DOS system services with it's own
services. In every case, the calling sequence and register setup
is identical do the DOS call. Return values are also identical
to DOS return values. In short, if your program ran OK under DOS,
it should run OK under Cswitch.

Cswitch replaces the following DOS (interrupt 21) services :

48h : allocate memory

Cswitch controls memory allocation for all tasks. DOS uses
a 'first fit' algorithm for allocating memory, meaning that
it allocates a chunk of memory from the first block that
is large enough to fulfill the request. This leads to memory
fragmentation, limiting the number of tasks that can run.
Cswitch uses a 'best fit' algorithm, searching all the
available memory blocks to find a block that is exactly the
size needed, or is closest to it. This way, memory fragmentation
is kept to a minimum.

Note : starting with DOS 3.3, you can alter the memory
allocation strategy that DOS uses, but 'first fit' is still
the default.

49h : release memory

Cswitch must also handle the memory release function. When a
block of memory is released, all memory is re-combined as much
as possible to limit memory fragmentation.

4ah : set memory block size

This DOS function is used to adjust the size of a memory block.
Since DOS tasks expect to be the only task running, they assume
that a memory block can be altered by simply increasing the size
of the block when they need more memory. Since several tasks
may occupy memory blocks adjacent to the block to be modified,
this is not always possible under Cswitch. Cswitch will attempt
to fulfill the request by looking for a free block adjacent to
the specified block, and combining them to form a larger block.
If unsuccessful, it will return an error.

Note that programs that allocate memory dynamically with CALLOC
or MALLOC calls may run into problems with this limitation.
The problem is that the library routines for allocating memory
(MALLOC, et al) use a table of structures to control the memory
blocks they obtain from DOS. Normally, this table will only
hold 20 entries (this is true for Microsoft C and QuickC).
Since Cswitch will return an error on some of the 'set memory
block' calls, the library routines may execute an 'allocate
memory' call every time you execute MALLOC. After 20 calls,
the table is full, and you can't allocate any more memory!

You can avoid problems by using the EXEMOD program to change
the minimum memory requirement of your task, so that enough
memory will be reserved when your task is loaded to handle
dynamic memory allocation demands without having to request
more memory from DOS.

Or, you can avoid using the MALLOC/CALLOC routines wherever
possible, and instead call the DOS allocate memory call
directly. Be careful, though, because Cswitch can only
control a total of 512 memory blocks.

When a task in expanded memory wants to extend the memory block,
it is much easier to do, provided the limit of 64K is not exceeded.
Since each task loaded into expanded memory is allocated the
entire 64K expanded memory region to work in, it is usually
possible to extend the memory allocation to the desired size.

4ch : terminate task

Cswitch handles task termination internally. It will release
memory held by the task, close all open files, and release the
Task Control Block.

1ah : set new DTA area

Cswitch records the address of the tasks new DTA in the TCB, then
passes this request to DOS normally. This function is normally
handled by the library routines, and is seldom called explicitly
by a programmer.

2fh : get DTA address

Cswitch returns the DTA address from the tasks TCB.

Cswitch Functions

The following functions are provided in the file CSWITCH1.C, and may
only be called by the main program. i.e. these functions may only be
called by the program that is linked with Cswitch1 and Cswitch2.


Parameters :

Description :
Starts the multitasking kernel, and enables all system
functions. This function must be called once, and only
once, before executing any of the other functions or
system services.

Return Value :

Notes/Comments :


Parameters :

Description :
This function sets Cswitch up to swap tasks out to
expanded memory.

Return Value :
The amount of expanded memory available is returned,

Notes/Comments :
All tasks started prior to this call are memory-resident,
and all spawned tasks are memory resident. This routine
may only be called once, and there is no way to turn
off usage of expanded memory once it is started.


Parameters :

Description :
Ends multitasking. The program that called START_MT()
must call END_MT() before terminating.

Return Value :

Notes/Comments :
See the warning above concerning the dire consequences
of terminating without restoring the interrupt vectors
and turning off multitasking.

char *cmd_string;
int pri;
int expmemflag;

Parameters :
cmd_string is a character pointer to a command string
to load the program, including command line parameters
just as you would type in to load the program under

pri is the base priority of the new task

if expmemflag is true, the task is eligible to be loaded
into expanded memory. if set to 0, the task may not be
loaded into expanded memory, even if START_SWAPPING()
has been called previously.

Description :
Loads and executes an .EXE or .COM file from disk.
The full path to the program file must be specified,
and command line parameters may be used.

Return Value :
0 = no error
-1 = not enough memory to create new task stack
-2 = no free task control blocks
-3 = no free memory control blocks

Notes/Comments :
This function performs the same service as LOADTASK()
below. The difference is that this service is called
directly as a function. LOADTASK is invoked via the
INT 62H. Functionally, there is no difference, since
LOADTASK() eventually calls LOADPRG to do the work.

Cswitch Global Variables

The following variables are available to the main program. These are
provided for information only.

int dos48, dos49, dos4a, dos4c;

These integers represent a running count of how many DOS calls
(INT 21H) have been made for these function codes.

unsigned int base_mem_segment;

This is the segment value of the memory segment controlled by

int idlecount;

This is a running count of how many times the idle task has
run. The idle task is spawned when START_MT() is called, and
handles the tasks that are delaying and terminating.

int swap_count;

This is a running count of how many task switches have taken
place. When the scheduler is called, this count is incremented
even if no task switch takes place due to a task locking out
switching or some other reason.

int extmem_pages;

This is the total number of expanded memory pages available
for task swapping. This variable is loaded after a successful

int exmemfree;

This is the number of free pages of expanded memory
left in the system. Pages are allocated as needed by memory
allocation calls, so this number may vary somewhat during

Cswitch Internal Services

The following functions are provided in the file LMTC.OBJ and SMTC.OBJ,
and may be called by any task executing under Cswitch control. These
functions allow access to the semaphore, messaging, and task control
functions of Cswitch.

These functions are actually invoked via an INT 62H call. The parameters
are loaded into registers, and the INT 62H transfers control to an
assembly language routine. The registers are then pushed onto the stack,
and the C function for system calls is executed.

Most of the work done by Cswitch is done by C routines, with assembly
language used only for interrupt control.

All of these functions are of type INT, although some do not have return
values assigned.


Parameters :

Description :
Gives up the rest of the tasks time slice.
This is graceful way of not wasting processor time if
your task has nothing else to do for a short while.

Return Value :

Notes/Comments :

int *func_addr();
int pri;

Parameters :
func_addr is the address of a function or subroutine
that is to be executed as a separate task

pri is the base priority for the new task (1-10)

Description :
Causes the specified function or subroutine to be
executed as a separate task. A new stack is allocated
for the task, allowing it to have exclusive access to
local variables. It will still share global variables
with the calling task.

STDIN, STDOUT, and STDERR are inherited from the caller.

The function can be spawned more than once, allowing
multiple tasks to execute the same code thread.

Return Value :
>0 = the tcb number of the new task, indicating no error
-1 = not enough memory to create new task stack
-2 = no free task control blocks
-3 = no free memory control blocks

Notes/Comments :
A task that is swappable to expanded memory (i.e. one
that was loaded after a call to START_SWAPPING() )
cannot spawn new tasks.

char *cmd_string;
int pri;
int expmemflag;

Parameters :
cmd_string is a character pointer to a command string
to load the program, including command line parameters
just as you would type in to load the program under

pri is the base priority of the new task

if expmemflag is non-zero, the task may be loaded into
expanded memory, if it is enabled. If exmemflag is
0, the task will not be loaded into expanded memory.

Description :
Loads and executes an .EXE or .COM file from disk.
The full path to the program file must be specified,
and command line parameters may be used.

STDIN, STDOUT, and STDERR are inherited from the caller.

LOADPIPE creates an internal pipe for passing data to
and from the task via the tasks STDIN/STDOUT files,
similar to the UNIX POPEN() call.

EXECPROG causes the calling task to be replaced by the
new task.

Return Value :
0 = task loader queue is full
1 = no error

Notes/Comments :
This call actually places a load request onto a queue
to be executed by the 'idle' task. The status of the
load may be checked with the GET_LOAD_STATUS() call.

.COM files are given exactly 64K memory. .EXE files
are sized according to the information in the file
header. The MINALLOC parameter is used to determine
how much memory, in addition to the task size and
data space, is needed for the program.

SEND_PIPE(tcbnum, buffer, length)
int tcbnum;
char *buffer;
int length;

Parameters :
tcbnum is the identifier for the pipe. it is returned
by the LOADPIPE() call.

buffer is a pointer to the buffer to be sent to the pipe.

length is the number of bytes to be sent to the pipe.

Description :
Places a given number of bytes onto a Cswitch pipe.
A task loaded with LOADPIPE() will read from this pipe
when reading from STDIN, via an internal redirection of
the DOS call. This is a convenient method for allowing
tasks to communicate without using inter-task message
queues and semaphores. The task that was loaded with
LOADPIPE() will not even realize that it's input and
output are being redirected into an internal pipe - it
will see results as though output went to the screen,
and input comes from the keyboard.

Data should be terminated with a CR, to satisfy the
line oriented input that DOS and C functions use.

Return Value :

The number of bytes waiting on the pipe, or -1 if
the pipe does not exist.

RECV_PIPE(tcbnum, buffer, length)
int tcbnum;
char *buffer;
int length;

Parameters :
tcbnum is the identifier for the pipe. it is returned
by the LOADPIPE() call.

buffer is a pointer to the buffer to receive the data.

length is the maximum number of bytes to be read.

Description :

Reads data from an internal pipe, as it was placed there
by a tasks call to STDOUT. Tasks loaded with the LOADPIPE()
call will have their output redirected to the internal pipe,
where it can be read with this call.

Return Value :

The number of bytes returned from the pipe, or -1 if
the pipe does not exist.

int tcbnum;

Parameters :
tcbnum is the identifier for the pipe. it is returned
by the LOADPIPE() call.

Description :

Returns the number of bytes waiting on the specified

Return Value :

The number of bytes waiting on the pipe, or -1 if
the pipe does not exist.

int sleep;

Parameters :
seconds is the number of seconds the task is to remain

Description :
puts the calling task into a sleeping state, where it
will not execute at all, for a specified number of

Return Value :

Notes/Comments :

int queue;
char *message_addr;
int count;

Parameters :
queue is the message queue to place a message on
message_addr is a pointer to the data to be placed on queue
count is the number of bytes to place on the queue

Description :
places the specified number of bytes onto a message
queue. queues are numbered 0-63.

Return Value :
-1 = bad queue number
other wise, returns number of bytes transferred

Notes/Comments :

int queue;

Parameters :
queue is the queue number to test

Description :
tests whether there are any messages waiting on the
specified queue.

Return Value :
-1 = bad queue number
0 = nothing on queue
else, returns the number of bytes contained in the
first message on the queue

Notes/Comments :

int queue;
char *message_addr;
int count;

Parameters :
queue is the message queue to place a message on
message_addr is a pointer a data area to receive the data
count is the maximum number of bytes to transfer

Description :
reads the first message from the specified queue
into the data area pointed to by message_addr. only
the first 'count' bytes of the message are transferred.
if more than 'count' bytes are in the message, the
extra is lost.

If there are no messages on the specified queue, the
task is placed into a dormant state until a message is
put on the queue. If you do not want your task to remain
dormant if the queue is empty, you should call TESTMSG()
to make sure something is available before calling this

Return Value :
-1 = bad queue number
0 = no message was on queue
else, returns the number of bytes transferred

Notes/Comments :

int semaphore_number;

Parameters :
semaphore number is the number of the semaphore to
attach (0-63)

Description :
attaches to the semaphore. see semaphore description
elsewhere in documentation

Return Value :
-1 = invalid semaphore number
all other values = success

Notes/Comments :

int semaphore_number;

Parameters :
semaphore number is the number of the semaphore to
be released (0-63)

Description :
releases the semaphore. see semaphore description
elsewhere in documentation

Return Value :
-1 = invalid semaphore number
all other values = success

int semaphore_number;

Parameters :
semaphore number is the number of the semaphore to
test (0-63)

Description :
tests to see if the semaphore is already owned by
another task. see semaphore description
elsewhere in documentation

Return Value :
-1 = semaphore is unowned
all other values = semaphore is owned

int new_priority;

Parameters :
new_priority is the new base priority of the calling

Description :
allows a task to change its priority, affecting how
often the task gets to execute.

Return Value :

Notes/Comments :
only values 1-10 are legal. the lower the number, the
more often a task will run. the main program is assigned
a prioroty of 1, and usually should be the only task with
such a low priority.


Parameters :

Description :
suspends the task until it is awakened by a WAKEUP()

Return Value :

Notes/Comments :

int tcb_number;

Parameters :
tcb_number is the tcb identifier for the task that is
to be awakened.

Description :
allows a task that was suspended with a SUSPEND() call
to continue processing.

Return Value :

Notes/Comments :
This is the ONLY way to wake up a SUSPENDed task.
This call will also wake up a task that is in a
SLEEP state (see SLEEP() above). In this way, a task
may go into a sleep state, and another task can cause
it to start executing again before the sleep delay is


Parameters :

Description :
prevents this task from being swapped out until a
corresponding NOHOG() is issued. In effect, this
halts multitasking, allowing the caller to run
forever, if desired.

Return Value :

Notes/Comments :
Use this function when your task must not be interrupted.
DOS functions like I/O and other critical functions are
already protected, so this function should not be used
too often.


Parameters :

Description :
allows multitasking to continue after being halted by
a HOG() call.

Return Value :

Notes/Comments :


Parameters :

Description :
halts execution of a SPAWNed task.

Return Value :

Notes/Comments :
this is the preferred method of halting execution of a
spawned task. When the spawned task is finished, it
should call this function to terminate.

struct tcb_rec far **pointer_address;

Parameters :
pointer_address is the address of a pointer into
which the address of the calling tasks TCB will be placed.

Description :
this function is used to access your Task Control Block
you pass in the address of a pointer, and the address
of your TCB is placed into the pointer. The pointer
must be a LONG pointer, as supported in the LARGE
memory model.

Return Value :
the TCB identifier (an index into the TCB array) is

Notes/Comments :
Normally, tasks do not need access to their TCB. Since
TCBs contain critical task control information, great
care must be taken when accessing the TCB.

struct tcb_rec far **pointer_address;

Parameters :
pointer_address is the address of a pointer into
which the address of the TCB array will be placed.

Description :
similar to 'GET_TCB_INFO()' above, but this call
returns the address of the TCB array in memory, to
allow your task to access any given TCB, based on
the task number.

Return Value :
the TCB identifier (an index into the TCB array) of
the calling task returned.

Notes/Comments :
Normally, tasks do not need access to their TCB. Since
TCBs contain critical task control information, great
care must be taken when accessing the TCB.


Parameters :

Description :
returns the status of the last 'LOAD_TASK()' call
made by your program.

Return Value :
0 = load in progress (not done yet)
-1 = cannot open file
-2 = insufficient memory
-3 = no TCBs available
-4 = no Memory Control Blocks available

otherwise, the TCB number (index into the TCB array)
of the new task is returned.

Notes/Comments :

Sample Programs

2 sample program are included, with full source code.
MTTEST.C will load and execute the programs TEST1.EXE, TEST2.EXE,
TEST3.EXE, TEST4.EXE and TEST5.EXE. These 5 programs read
and write a disk file. Each time they finish reading and writing
the file, they send a message to MTTEST. MTTEST receives and
displays the messages. It's not particularly impressive to
watch, but it does illustrate that several programs can perform
concurrent I/O, and that programs can easily be loaded and executed
from disk.

MT.C is another sample program. It demonstrates one use of the
semaphores, and the sleep() function.

The source files for these programs, and a make file for Microsoft
C is included.

Writing Programs With CSWITCH

When you write your multitasking program, there are several things
to remember :

1. You must use the LARGE memory model. Programs that are linked with
SMTC?, or that are simply loaded and run, may be small model. But
your main program must be LARGE model.

2. You must compile your main program, and any program that will SPAWN
tasks, with the /Gs option to turn off stack checking. This is
imperative, and without it, your program will not run.

3. C library routines are not re-entrant. Therefore, you cannot
do a PRINTF() from your main program, and one from a SPAWNED task
at the same time. In general, if a spawned task needs to do I/O,
do it with DOS interrupts rather than with the library calls. If
this is impractical or impossible, then make the spawned function
a separate program and execute it with LOADTASK().

If several different programs perform simultaneous I/O there is no
problem. It is only when the same PRINTF or FOPEN routines are
called from 2 different places in the same program at the same time
that there is a problem.

This probably applies to other languages as well.

Also, most library routines perform stack checking upon entry. Since
a SPAWNED task is assigned a stack from free memory, the stack check
will often fail, causing SPAWNED tasks to abort with a false stack
overflow error. If SPAWNed tasks are going to be performing complex
functions and making a lot of library calls, they probably should
be made into separate programs and executed with LOADTASK().

Language Support

Currently, the only language directly supported is C. Most languages
can be made to mimic C's function calls, so there should not be a
problem with linking these routines to other languages.

Identifiers are prefixed with an underscore ( _ ) by the C compiler, so
if your compiler does not automatically add the underscore, you will have
to use it explicitly in the module names, and in the global data names.

The interface files, LMTC.OBJ and SMTC.OBJ, have been coded both in
C and assembly now. Some C compilers do not use Microsoft's calling
conventions, so I coded the routines in C so they could be compiled
with various compilers. The names have changed accordingly - they are

Old Name New Name C Source ASM Source Memory Model


? = A or C depending on the source file used to create the OBJ

Cswitch Limitations

Cswitch routines will not be appropriate in every situation requiring
multitasking. In some cases, products like Desqview, Omniview, Windows,
OS/2, and Unix are more appropriate. Cswitch does give a programmer
an option, however. Writing programs that multitask in the DOS
environment is a clean solution to many problems. In general, if your
application must somehow do several things all at once, then writing
your application as a set of cooperating tasks is a lot easier than
trying to build every function into a single task. Let your
imagination take it from there.

As stated previously, Cswitch does not attempt to provide a multi-
tasking user interface. You must prevent tasks from writing to the
monitor or reading the keyboard simultaneously. This can easily be
done with semaphores or I/O redirection.

Tasks that are created using the SPAWN call inherit their owners STDIN,
STDOUT and STDERR, as do tasks loaded from disk. Normally these handles
refer to the CON device - the keyboard and monitor. No other file
handles are inherited.

 December 5, 2017  Add comments

Leave a Reply