I started with a simple COM program because I actually think they are easier
to create than subroutines to be called from high level languages, but maybe
its really the latter you are interested in. Here, I think you should get comfortable
with the assembler FIRST with little exercises like the one above and also another
one which I will finish up with.

Next you are ready to look at the interface information for your particular
language. You usually find this in some sort of an appendix. For example, the
BASIC manual has Appendix C on Machine Language Subroutines. The PASCAL manual
buries the information a little more deeply: the interface to a separately compiled
routine can be found in the Chapter on Procedures
and Functions, in a subsection called Internal Calling Conventions.

Each language is slightly different, but here are what I think are some common
issues in subroutine construction.

1. NEAR versus FAR? Most of the time, your language will probably call your
assembler routine as a FAR routine. In this case, you need to make sure the
assembler will generate the right kind of return. You do this with a PROC...ENDP
statement pair. The PROC statement is probably a good idea for a NEAR routine
too even though it is not strictly required:

With FAR linkage, it doesn't really matter what you call the segment. you must
declare the name by which you will be called in a PUBLIC pseudo-op and also
show that it is a FAR procedure. Only CS will be initialized to your segment
when you are called. Generally, the other segment registers will continue to
point to the caller's segments.

With NEAR linkage, you are executing in the same segment as the caller. Therefore,
you must give the segment a specific name as instructed by the language manual.
However, you may be able to count on all segment registers pointing to your
own segment (sometimes the situation can be more complicated but I cannot really
go into all of the details). You should be aware that the code you write will
not be the only thing in the segment and will be physically relocated within
the segment by the linker. However, all OFFSET references will be relocated
and will be correct at execution time.

2. Parameters passed on the stack. Usually, high level languages pass parameters
to subroutines by pushing words onto the stack prior to calling you. What may
differ from language to language is the nature of what is pushed (OFFSET only
or OFFSET and SEGMENT) and the order in which it is pushed (left to right, right
to left within the CALL state ment). However, you will need to study the examples
to figure out how to retrieve the parameters from the stack. A useful fact to
exploit is the fact that a reference involving the BP register defaults to a
reference to the stack segment. So, the following strategy can work:

This example uses something called a structure, which is only available in
the large assembler; furthermore, it uses it without allocating it, which is
not a well-documented option. However, I find the above approach generally pleasing.
The STRUC is like a DSECT in that it establishes labels as being offset a certain
distance from an arbitrary point; these labels are then used in the body of
code by beginning them with a period; the construction ".ARG2" means,
basically, " + (ARG2-ARGS)."

What you are doing here is using BP to address the stack, accounting for the
word where you saved the caller's BP and also for the two words which were pushed
by the CALL instruction.

3. How big is the stack? BASIC only gives you an eight word stack to play with.
On the other hand, it doesn't require you to save any registers except the segment
registers. Other languages give you a liberal stack, which makes things a lot
easier. If you have to create a new stack segment for yourself, the easiest
thing is to place the stack at the end of your program and:

Later, you can reverse these steps before returning to the caller. At the
end of your program, you place the stack itself:

DW 128 DUP(?) ;stack of 128 words (liberal)
STACKTOP LABEL WORD

4. Make sure you save and restore those registers required by the caller.

5. Be sure to get the right kind of addressibility. In the FAR call example,
only CS addresses your segment. If you are careful with your ASSUME statements
the assembler will keep track of this fact and generate CS prefixes when you
make data references; however, you might want to do something like