Contents of the OOPASM.TXT file
***** Computer Select, October 1992 : Doc #41540 *****
Journal: Dr. Dobb's Journal March 1992 v17 n3 p24(10)
* Full Text COPYRIGHT M&T Publishing 1992.
Title: An object-oriented assembly language macro library. (Tutorial)
Author: McSwain, Donald J.
AttFile: Program: OOPASM.ASC Source code listing.
Program: OOPASM.EXE Self extracting archive.
Abstract: Object-oriented programming (OOP) language can be used by 80x86
assembly language programmers to develop such applications as an
object-oriented assembly language macro library. The use of
object-oriented language with assembly language improves the
reusability of assembly codes because the codes developed are
modular. An object-oriented system consists of a collection of
objects related to each other through inheritance. Each object
contains procedures that operate upon some data or a description
of the given data. Those procedures and descriptions are referred
to as 'Messages'. Assembly language programmers can use OOP
language via a method of combining messages. To combine messages,
one must first identify all of an object's ancestors and then the
collecting methods from each ancestor organized by message.
Redundant objects are then eradicated to avoid 'duplication of
Record#: 11 968 137.
Object-oriented programming techniques allow you to develop reusable,
maintainable code by providing mechanisms for inheritance, data abstraction,
and encapsulation--features which C++, CLOS, and Smalltalk programmers
currently enjoy. But 80x86 assembly language programmers can also use
object-oriented programming techniques. For example, I've used the
techniques described in this article to develop an object-oriented assembly
language macro library that provides windows, pop-up menus, mouse support,
horizontal and vertical scroll bars, sound support, and the like.
In object-oriented systems, programs are organized as a collection of objects
related to one another through inheritance. An object is the embodiment of a
local state. In contains a description of some data and procedures that
operate upon it. A message is the genetic name for a set of procedures or
An object may use methods form other objects through inheritance. For the
purpose of this discussion, objects that inherit methods from others are
derived objects, and those that do not are base objects. An ancestor is any
object that bestows methods. In this object-oriented programming scheme,
ancestral objects may contribute up to two methods per message.
A combined method is the runtime version of a message. Methods are assembled
into a combined method in a well defined manner based on an object's ancestor
list. However, only objects directly sent as messages may have combined
Method combining is done by first finding all of an object's ancestors, and
then collecting methods from each ancestor grouped by message. Object
collection starts with an object, its ancestors, their ancestors, and so on
recursively. Once this depth-first search is completed, duplicate objects
are removed to prevent duplication of effort. This object collection becomes
the basis of a search for method addresses grouped under the given message
In this scheme, there may be up to three methods per message per object.
This makes the combining of methods nontrivial. One of the many ways to
combine methods is through daemon combination, which specifies how these
three element sets of methods may be ordered to form a combined method.
The three methods that make up an object's local message are known as Before,
Primary, and After. Before methods execute "before" and After methods
execute "after" the Primary. When a combined method is assembled, only the
local (that is, uninherited) Primary method is included. In other words,
ancestors can contribute only Before and After methods to a combined method.
Combined methods are created only for objects which receive directly sent
messages. Methods received indirectly through inheritance will not have
combined methods. This optimizes message passing so that runtime searching
is not required to resolve a message pass into its associated set of methods.
Combined methods make message passing a simple matter of fetching and
executing method addresses.
The daemon combination scheme specifies methods combination in the following
manner: the local Before, ancestral Befores (in-depth first order), the local
Primary, ancestral Afters (in reverse order of Befores), and the local After
To reiterate, combined method construction involves two steps. First,
objects found in a depth-first search of an object's ancestor list are placed
onto a list with duplicates removed. Second, this list is used to create an
ordered list of methods based on the daemon combination scheme.
Message passing becomes possible after combined methods are constructed.
Consequently, when an object is sent a message, it responds by executing the
corresponding combined method. This involves locating a pointer to a
combined method table and then fetching and executing the address in that
An object is known to ancestral objects through the object variable Self and
to other objects by name. Self provides a means for anonymous message
passing and access to instance data. This makes code generalization possible
by providing a way to easily share code and data.
Message arguments are passed by way of the stack to all methods in a combined
method. Therefore, methods must be stack neutral--they must not increase or
decrease the stack depth. If a method is to return a value on the stack,
space must be allocated prior to the message pass.
Example 1 demonstrates message passing with the send macro. send takes an
object name, a message name, and an optional message argument list. In the
first example, Self is assigned to Screen, the constant, DoubleBdr is pushed
onto the stack, and the combined Screen-Refresh method is called. Upon
return, DoubleBdr is removed from the stack.
Listing One (page 84) is the source code for the send macro. It pushes
arguments onto the stack, moves an object address into a register, moves the
message number into a register, calls the sendMsg procedure, and pops message
arguments off the stack.
sendMsg assigns Self, searching the object's message list for a matching
message number. If a match isn't found, it returns. Otherwise, it gets a
pointer to the combined method table, and selects a method count. Using the
method count as a loop counter, it then fetches and executes method addresses
located in the method table.
Using Microsoft MASM 5.1 conventions, source files are comprised of code and
data segments. For our purposes, the code segment will contain methods and
procedures, while the data segment holds object ad message definitions along
with other data.
Example 2 shows the use of the def-Obj macro for object definition. defObj
takes an object name, an ancestor list, an instance variable list, and a
message list. The object name may be any valid variable name. The ancestor
list may be empty, as in the case of base objects, or may contain the names
of objects to inherit from, as in the case of derived objects. The instance
variable list may be empty, or may contain three element entries of instance
variable name, size, and initial value. The last argument, the message list,
contains the names of messages which the object will respond to.
The Screen object inherits some of its methods from Window and Border.
Object and message names are public symbols, and are the only data visible to
nonancestral objects. Ancestors, however, have access to all instance data
through the object variable Self.
Listing Two (page 84) shows the source code for the deObj macro. It
assembles ancestor, instance variable, and message tables in memory. The
[underscore]Object structure is used by defObj to assemble pointers to these
A message describes a set of operations on some data. To associate
operations to a message name, the defMsg macro is used. Example 3
demonstrates how you can use defMsg to define messages. defMsg takes an
object name, message name, and a method list. The method list may contain up
to three method names representing the Before, Primary, and After methods.
Many objects respond to the same message, but each will use a different set
of methods. Recall that this set may contain local and inherited methods.
Thus the combined Screen-Refresh method contains: clrWin, DrawBackDrop,
drawBdr, and drawLabel.
Listing Three (page 85) shows source code for the defMsg macro. Using the
[underscore] Message structure, it assembles three entries containing a
method address, or null pointer. In turn, this table is pointed to by the
concatenated object-message name (that is, ScreenRefresh). This name is used
to locate local methods for method combining at initialization time.
Example 4 shows how you might generalize window labeling by creating a Label
object to handle the specifics. Label must be declared a Screen ancestor
after Border. drawLabel is declared under Label's Refresh message as an
After method. This produces the same combined method as before, but affords
a greater degree of modularity that makes your code easier to enhance and
Ancestor lists determine who contributes code, and in what order they
contribute it. By changing the order of objects on an ancestor list, you
alter an object's behavior. For example, if Screen's ancestor list was
changed to Window, Label, Border, the combined Screen-Refresh message would
instead become clrWin, drawBackDrop, drawLabel, and drawBdr. Consequently,
the label would have been drawn prior to the border, thus overwriting it.
Object initialization is a runtime activity invoked with the initObj macro.
It transforms an object's ancestor list into a table where duplicates have
been removed. It also creates combined methods for each of an object's
If an object is not initialized, combined methods will not be created for it.
This is desirable for objects such as Window, Border, and Label which are
never directly sent messages, but which receive them only through the
Example 5 shows how you initialize an object. Initialization order is
significant. An object must be initialized before its ancestors so that
method pointer information can be accessed before being overwritten.
Listing Four (page 85) is the source code for the initObj macro. It moves an
object address into a register, and calls the initObject procedure.
initObject performs a depth-first search of the ancestor list to assemble a
temporary table of ancestor pointers, then builds combined method tables for
each declared message. initObject then relaces the local method list
pointers in the message table (assembled by defMsg) with pointers to combined
Using Instance Data
Instance variable values may be retrieved with the getInst macro, and changed
with the setInst macro. Example 6 demonstrates usage of these macros.
getInst takes a destination register, an instance variable name, and an
optional object name. setInst takes an instance variable name, a source
register, an optional object name, and an optional variable size. The
optional object name specifies the source of the instance data. If not
provided, it is assumed that the SI register already contains the address of
the source object. This would be the case after one use of the getInst or
setInst macro that included an object name. Listing Five (page 86) is the
source code for this macro.
getInst assembles instructions to place the object address in a register, and
based on the register size, moves instructions to copy data from the variable
to the register. setInst assembles code to place the object address into a
register, and move instructions to copy data from the register to memory. If
the move is from memory to memory then the optional size argument must also
The getInst$ macro allows source object specification through an instance
variable instead of by name or by the object variable Self. getInst$ and
setInst$ work the same as getInst and setInst except the specified instance
variable points to the source object. It is assumed that Self points to the
object supplying this instance data.
Example 7 and Listing Six (page 87) show how you might use these macros.
Master is one of Self's instance variables and points to some object. This
allows the Color instance variable of any object pointed to by Master to be
accessed. Local object variables just provide another mechanism for code
As stated, the code presented in this article is part of a larger assembly
language macro library implemented using object-oriented programming schemes.
The program, supporting object-oriented concepts such as multiple
inheritance, was developed using Microsoft's MASM 5.1 and provides windows,
pop-up menus, mouse support, horizontal and vertical scroll bars, sound
support, and the like. Because of space constraints, the complete source
code for this example is available electronically; see "Availability" on page
3 for more details.
The use of object-oriented programming techniques with assembly language
allows for the development of highly modular code. Thus, reusability and
ease of maintenance of assembly code improves. However, taking advantage of
these features requires some trade-offs.
Object initialization must be done prior to message passing. This slows
program start-up, and adds a move and call instruction for every initialized
object. To overcome this limitation, you could add code to write your
initialized program to an executable file, possibly as a final step before
software delivery. Once initialization is done, however, message passing
becomes very efficient.
Another trade-off arises with message look-up. When a message is passed to
an object, its message table is searched to locate a pointer to a combined
method. Some search code optimizations could be made. For example, the test
for null pointers could be removed, but program corruption may occur when an
undeclared message is passed to an object. However, the most significant
optimization you can make is through intelligent object class design, which
suggests that you make complete use of inheritance, Before and After methods,
and generic objects.
Although no formal comparisons with other object-oriented programming
languages have been done, practical experience with this system has shown it
to be robust. In addition, programming productivity increases were very
noticeable after the system was learned.
Barstow, David R. et al. Interactive Programming Environments. New York,
N.Y.: McGraw-Hill, 1984.
Bobrow, David G., et al. Common LISP Object System Specification. X3J13
Cannon, Howard I. Flavors: A Non-Hierarchical Approach to Object-Oriented
Programming. Unpublished paper, 1983.
Cox, Brad J. Object-Oriented Programming: An Evolutionary Approach.
Reading, Mass.: Addison-Wesley, 1986.
Dorfman, Len. Object-Oriented Assembly Language. Blue Ridge Summit, Penn.:
Duncan, Ray. Advanced MS-DOS. Redmond, Wash.: Microsoft Press, 1986.
Hyde, Randall L. "Object-Oriented Programming in Assembly Language." Dr.
Dobb's Journal (March 1990).
Moon, David. "Object-Oriented Programming with Flavors." Proceedings of
Toutonghi, Michael. "21st Century Assembler." Computer Language (June,
Wegner, P., ed. The Object-Oriented Classification Paradigm: Research
Directions in Object-Oriented Programming. Cambridge, Mass.: MIT Press,
Wyatt, Allen. Using Assembly Language. Carmel, Ind.: Que Corp., 1987.
Donald is a programmer for Digital Alchemy Inc., a start-up firm specializing
in process control and communications software. His experience includes
systems and applications software development for Asymetrix Corp., MITRE
Corp., Computer Sciences Corp., and the Kingdom of Saudi Arabia. He has been
involved in object-oriented programming for the past four years and can be
reached at Digital Alchemy, P.O. Box 254801, Sacramento, CA 95865, fax: