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: 916-481-6467.
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. It contains a description some data and procedures that operate upon it. A message is the generic name for a set of procedures or methods.
An object may use methods from 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 methods.
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 name.
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.
Method Combining
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 method.
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
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 table.
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.
Example 1: Message passing using the send macro
send Screen, Refresh, DoubleBdr ;Send Screen a Refresh msg send Self, Read ;Send Self a Read msg send Self, <WORD PTR[bx]> ;Send Self msg pointed to by BX register
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.
Object Definition
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 defObj 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.
Example 2: Using the defObj macro for object definition
defObj Window,\ ;Define Window object <>,\ ;As a base object <>,\ ;With no inst vars <Refresh> ;Responds to Refresh msg defObj Border,\ ;Define Border object <>,\ ;As a base object <>,\ ;With no inst vars <Refresh> ;Responds to Refresh msg defObj Screen,\ ;Define Screen object <Window, Border>,\ ;As a derived object <Row1, 1, 1,\ ;With these inst vars Col1, 1, 0,\ Row2, 1, 23,\ Col2, 1, 79,\ Color, 1, 34h, BdrColor, 1, 30h,\ MemSeg, 2, Nil>,\ <Refresh> ;Responds to Refresh msg
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 defObj macro. It assembles ancestor, instance variable, and message tables in memory. The _Object structure is used by defObj to assemble pointers to these tables.
Message Definition
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.
Example 3: Using defMsg to define messages
defMsg Window,\ ;Define for Window object Refresh,\ ;The Refresh msg <clrWin,,> ;To clear window defMsg Border,\ ;Define for Border object Refresh,\ ;The Refresh msg <,,drawBdr> ;To draw border defMsg Screen,\ ;Define for Screen object Refresh,\ ;The Refresh msg <, drawBackDrop, drawLabel> ;To draw back drop and label
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 _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 maintain.
Example 4: Generalizing window labeling by creating a Label object to handle the specifics
defMsg Label,\ ;Define for Label object Refresh,\ ;The Refresh msg <, ,drawLabel> ;To draw label defObj label,\ ;Define Label object <>,\ ;As a base object <>,\ ;With no inst vars <Refresh> ;Responds to Refresh msg defMsg Screen,\ ;Define for Screen object Refresh,\ ;The Refresh msg <,drawBackDrop,> ;To draw back drop defObj Screen,\ ;Define Screen object <Window, Border, Label>,\ ;As a derived object <Row1, 1, 1,\ ;With these inst vars Col1, 1, 0,\ Row2, 1, 23,\ Col2, 1, 79,\ Color, 1, 34h,\ BdrColor, 1, 30h,\ MemSeg, 2, Nil>,\ <Refresh> ;Responds to Refresh msg
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
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 declared messages.
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 inheritance mechanism.
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.
Example 5: Initializing an object
initObj Screen ;Combine methods for Screen
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 replaces the local method list pointers in the message table (assembled by defMsg) with pointers to combined methods.
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.
Example 6: Retrieving instance variable values using the getInst macro
getInst bl, Color, Screen ;Fetch Screen color setInst BdrColor, bl ;Copy it to BdrColor setInst Color, bl, Self ;And Self's color
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 be provided.
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 generalization.
Example 7: Using the getInst$ and setinst$ macros
getInst$ bl, Color, Master ;Fetch color from object pointed to by Master setInst Color, bl, Self ;Copy it to Self's color
An Example
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.
Limitations
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.
References
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 Document 88-002R.
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.: Windcrest, 1990.
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 OOPSLA '86.
Toutonghi, Michael. "21st Century Assembler." Computer Language (June, 1990).
Wegner, P., ed. The Object-Oriented Classification Paradigm: Research Directions in Object-Oriented Programming. Cambridge, Mass.: MIT Press, 1987.
Wyatt, Allen. Using Assembly Language. Carmel, Ind.: Que Corp., 1987.
_AN OBJECT-ORIENTED ASSEMBLY LANGUAGE MACRO LIBRARY_ by Donald J. McSwain[LISTING ONE]
<a name="0088_0015"> Macro File: objects.mac COMMENT % =============================================================== Sets up stack, SI with object pointer, DX with message number, and calls sendMsg procedure. Passed: Obj - Name of receiving object; Msg - Message number =========================================================================% send MACRO Obj,Msg,ArgList pushArgs ArgList ;Push arguments onto stack IFIDN <Obj>,<Self> ;If object is Self mov si,Wptr[Self] ;Get object ptr from it ELSE IFDIF <Obj>,<si> ;If object ptr not in SI lea si,Obj ;Load SI with ptr to object ENDIF ENDIF IFDIF <Msg>,<dx> ;If msg number not in DX mov dx,Msg ;Put it in DX ENDIF call sendMsg ;Send message IFNB <ArgList> ;If arguments X = 0 ;Init stack depth counter IRP Arg,<ArgList> ;For every arg on stack X = X+2 ;Increment depth counter ENDM add sp,X ;Reset stack pointer ENDIF ENDM COMMENT % =============================================================== Pushes up to ten arguments onto the stack. =========================================================================% pushArgs MACRO A0,A1,A2,A3,A4,A5,A6,A7,A8,A9 IFB <A0> ;If no more arguments EXITM ;Exit macro ENDIF IFIDN <A0>,<ax> ;If arg in AX push ax ;Push AX ELSE IFIDN <A0>,<bx> ;If arg in BX push bx ;Push BX ELSE IFIDN <A0>,<cx> ;If arg in CX push cx ;Push CX ELSE IFIDN <A0>,<dx> ;If arg in DX push dx ;Push DX ELSE mov bx,A0 ;Else move into BX push bx ;Push BX ENDIF ENDIF ENDIF ENDIF pushArgs A1,A2,A3,A4,A5,A6,A7,A8,A9 ENDM COMMENT % ============================================================= Finds the specified message for specified object. Passed: Msg - Message number; Obj - Addr ptr to object structure Passes: si - Pointer to combined method pointer =========================================================================% findMsg MACRO Obj,Msg,Lbl LOCAL fdmg1,fdmg2 IFDIF <Obj>,<si> ;If object ptr is not in SI mov si,Obj ;Put it there ENDIF mov di,Wptr[si].Instances ;Addr of msg tbl end mov si,Wptr[si].Messages ;Addr of msg tbl beginning fdmg1: lodsb ;Fetch msg number eq al,Msg,fdmg2 ;Exit if message is found add si,2 ;Else point to next message cmp si,di ;More messages? jb fdmg1 ;If so continue search IFNB <Lbl> ;If label provided jmp Lbl ;Jump to it upon failure ENDIF fdmg2: ENDM Source File: objects.asm PUBLIC sendMsg COMMENT % =================================================================== Sends the specified object the given message. This causes the execution of the combined message for the object. Passed: dx - Message number; si - Combined method ptr =============================================================================% sendMsg PROC NEAR findMsg si,dl,smg2 ;Search for message mov si,Wptr[si] ;Get method addr mov cx,Wptr[si] ;Get method count smg1: add si,2 ;Point to method pushData <cx,si> ;Save loop cnt, addr ptr call Wptr[si] ;Execute method popData <si,cx> ;Restore addr ptr, loop cnt loop smg1 ;Loop smg2: ret sendMsg ENDP <a name="0088_0016"> <a name="0088_0017">[LISTING TWO]
<a name="0088_0017"> Include File: objects.inc COMMENT % ================================================================== Data structure used to hold pointers to an object's ancestors, messages, and instance variables. ===========================================================================% _Object STRUC Objects DW Nil Messages DW Nil Instances DW Nil _Object ENDS Macro File: objects.mac COMMENT % =================================================================== Defines an object. Passed: Obj - Object name; Objs - Ancestor list; Instances - Instance variable list; Messages - Message list =============================================================================% defObj MACRO Obj,Objs,Instances,Messages LOCAL ObjTbl,MsgTbl,InstTbl ObjTbl LABEL WORD objsDef Obj,<Objs> MsgTbl LABEL WORD msgsDef Obj,<Messages> InstTbl LABEL WORD instDef <Instances> ALIGN 2 PUBLIC Obj Obj _Object <ObjTbl,MsgTbl,InstTbl> ENDM COMMENT % =================================================================== Defines objects. Passed: Obj - Object name; Objs - Ancestor list =============================================================================% objsDef MACRO Obj,Objs DW Obj IRP Obj,<Objs> DW Obj ENDM ENDM COMMENT % ==================================================================== Defines messages. Passed: Obj - Object name; Messages - Message list =============================================================================% msgsDef MACRO Obj,Messages IRP Msg,<Messages> DB Msg ;Msg# identifies msg IFNDEF Obj&&Msg DW Nil ;Obj has no local methods ELSE DW Obj&&Msg ;Obj has local methods ENDIF ENDM ENDM COMMENT % =================================================================== Defines instances variables. Passed: Instances - Instance variable list =============================================================================% instDef MACRO Instances X = 0 Y = 0 IRP Inst,<Instances> defInst Inst,%X,%Y ENDM ENDM COMMENT % ==================================================================== Defines an instance variable. Passed: Inst - Instance variable name; Cnt - Instance variable field number; Size - Size of instance variable =============================================================================% defInst MACRO Inst,Cnt,Size IFIDN <Cnt>,<0> X = X+1 ELSE IFIDN <Cnt>,<1> X = X+1 Y = Inst ELSE X = 0 defVar Size,Inst ENDIF ENDIF ENDM COMMENT % ==================================================================== Defines a data item. Passed: Size - Size of data in bytes; Value - Value of data item =============================================================================% defVar MACRO Size,Value IFIDN <Size>,<1> DB Value ELSE IFIDN <Size>,<2> DW Value ELSE IFIDN <Size>,<4> DD Value ELSE IFIDN <Size>,<8> DQ Value ELSE IFIDN <Size>,<10> DT Value ENDIF ENDIF ENDIF ENDIF ENDIF ENDM <a name="0088_0018"> <a name="0088_0019">[LISTING THREE]
<a name="0088_0019"> Include File: objects.inc COMMENT % ================================================================== Data structure used to hold pointers to a message's Before, Primary, and After methods. ===========================================================================% _Message STRUC Before DW Nil Primary DW Nil After DW Nil _Message ENDS Macro File: objects.mac COMMENT % ==================================================================== Defines a message. Passed: Obj - Object name; Msg - Message name; Methods - Method list =============================================================================% defMsg MACRO Obj,Msg,Methods ALIGN 2 Obj&Msg _Message <Methods> ENDM <a name="0088_001a"> <a name="0088_001b">[LISTING FOUR]
<a name="0088_001b"> Macro File: objects.mac COMMENT % ==================================================================== Sets us SI to point to object, and calls initObject procedure. =============================================================================% initObj MACRO Obj lea si,Obj ;Pass object ptr call initObject ;Find all ancestors ENDM Source File: objects.asm PUBLIC initObject COMMENT % ==================================================================== Initializes an object by flattening its inheritance lattice to create combined methods for its messages. Passed: si - Addr ptr to object structure =============================================================================% initObject PROC NEAR lea di,Buffer ;Get buffer addr call findAncestors ;Find/Save all ancestors call evalMsgs ;Evaluate messages ret initObject ENDP COMMENT % ==================================================================== Finds all of an object's ancestors and saves them for use by the message evaluator. Passed: bx - Addr ptr to message table (end of object table); di - Addr ptr to temporary object table; si - Addr ptr to object structure =============================================================================% findAncestors PROC NEAR pushData <bx,si> ;Save obj ptr mov bx,Wptr[si].Messages ;Get addr ptr to msg tbl mov si,Wptr[si].Objects ;Get addr of object tbl movsw ;Move obj ptr fas1: eq bx,si,fas2 ;Exit if end of tbl push si mov si,Wptr[si] ;Get next object call findAncestors ;Find others pop si add si,2 jmp fas1 ;More in tbl - Loop fas2: mov Wptr[di],Nil ;Mark end of list popData <si,bx> ;Restore obj ptr ret findAncestors ENDP COMMENT % ==================================================================== Creates combined methods for all of an object's messages. Passed: si - Addr ptr to object structure =============================================================================% evalMsgs PROC NEAR mov bx,Wptr[si].Messages ;Get addr of message tbl mov cx,Wptr[si].Instances ;Get addr of instance tbl ems1: mov dl,Bptr[bx] ;Get msg number xor dh,dh call combineMethods ;Combine methods add bx,3 ;Point to next tbl entry neq bx,cx,ems1 ;More in tbl? - loop ret evalMsgs ENDP COMMENT % ==================================================================== Combines methods for all included objects. Passed: dx - Message number; si - Addr ptr to object structure =============================================================================% combineMethods PROC NEAR push bx mov ?Compiled,Nil ;Clear compiled flag mov bx,Wptr[CompileStart] ;Get start of combined mthd mov Wptr[CompilePtr],bx ;Init location ptr mov di,Nil ;Zero count word call saveMethodAddr ;Save value call saveBefores ;Save Before methods mov bx,Primary ;Select Primary method type lea di,Buffer ;Get addr of tmp object tbl mov di,Wptr[di] ;Get tbl entry call saveMethod ;Save method call saveAfters ;Save After methods null ?Compiled,cms1 ;Nothing compiled? - Exit call updatePtrs ;Update message, location ptrs cms1: pop bx ret combineMethods ENDP COMMENT % ==================================================================== Updates the message and location pointers. Passed: dx - Message number; si - Addr ptr to object structure =============================================================================% updatePtrs PROC NEAR push si findMsg si,dl ;Find message mov di,Wptr[CompileStart] ;Get ptr to combined method mov Wptr[si],di ;Change message ptr mov bx,Wptr[CompilePtr] ;Get current compile location mov Wptr[CompileStart],bx ;Reset start of combined mthd pop si ret updatePtrs ENDP COMMENT % ==================================================================== Save the Before method type for the specified object. Passed: dx - Message number =============================================================================% saveBefores PROC NEAR push si mov bx,Before ;Select Before method type lea si,Buffer ;Get addr of tmp object tbl mov di,Wptr[si] ;Get tbl entry sbs1: call saveMethod ;Save method add si,2 ;Point to next tbl entry mov di,Wptr[si] ;Get next tbl entry identity di,sbs1 ;More in table? - loop pop si ret saveBefores ENDP COMMENT % =================================================================== Save the After method type for the specified object. Passed: dx - Message number =============================================================================% saveAfters PROC NEAR pushData <cx,si> mov bx,After ;Select After method type lea si,Buffer ;Get addr of tmp object tbl mov cx,si ;Save addr of object tbl sas1: mov ax,Wptr[si] ;Get tbl entry null ax,sas2 ;Null? - End of tbl, exit add si,2 ;Point to next tbl entry jmp sas1 ;Loop sas2: sub si,2 ;Point to previous tbl entry mov di,Wptr[si] ;Get next tbl entry call saveMethod ;Save method neq si,cx,sas2 popData <si,cx> ret saveAfters ENDP COMMENT % ==================================================================== Save the specified method for specified object. Passed: bx-Method type; di-Addr ptr to object structure; dx-Message number =============================================================================% saveMethod PROC NEAR pushData <bx,di,si> findMsg di,dl,svm3 ;Find message mov di,Wptr[si] ;Get method tbl addr ptr null di,svm3 ;Exit if no local methods mov di,Wptr[di+bx] ;Get method addr ptr null di,svm3 ;Exit if no message mov bx,Wptr[CompileStart] ;Get start of combined mthd svm1: eq bx,Wptr[CompilePtr],svm2 eq di,Wptr[bx],svm3 ;Exit if duplicate method add bx,2 ;Point to next addr jmp svm1 ;Check next addr svm2: call saveMethodAddr ;Save method addr svm3: popData <si,di,bx> ret saveMethod ENDP COMMENT % ==================================================================== Save value at current compile location, and increments location pointer. Passed: di - Value to store =============================================================================% saveMethodAddr PROC NEAR mov ?Compiled,1 ;Set compiled flag mov bx,Wptr[CompilePtr] ;Get ptr to combined mthd end mov Wptr[bx],di ;Save value add bx,2 ;Point to next location mov Wptr[CompilePtr],bx ;Reset location ptr mov bx,Wptr[CompileStart] ;Get ptr mthd count mov di,Wptr[bx] ;Get mthd count inc di ;Increments mthd count mov Wptr[bx],di ;Save value ret saveMethodAddr ENDP <a name="0088_001c"> <a name="0088_001d">[LISTING FIVE]
<a name="0088_001d"> Macro File: objects.mac COMMENT % ==================================================================== Gets an object's instance variable. Passed: Dest- Destination register; Var - Instance variable name; Obj - Source object =============================================================================% getInst MACRO Dest,Var,Obj IFNB <Obj> IFIDN <Obj>,<Self> mov si,WORD PTR[Self] mov si,WORD PTR[si].Instances ELSE IFIDN <si>,<Obj> mov si,WORD PTR[si].Instances ELSE mov si,Obj&.Instances ENDIF ENDIF ENDIF mov Dest,[si+Var] ENDM COMMENT % ==================================================================== Sets an object's instance variable. Passed: Var - Instance variable name; Source - Source register; Obj - Source object; Size - Size of data =============================================================================% setInst MACRO Var,Source,Obj,Size IFNB <Obj> IFIDN <Obj>,<Self> mov si,WORD PTR[Self] mov si,WORD PTR[si].Instances ELSE mov si,Obj&.Instances ENDIF ENDIF setInst_ Var,Source,Size ENDM COMMENT % ==================================================================== Assembles move instruction based on source register. =============================================================================% setInst_ MACRO Var,Source,Size IFIDN <Source>,<al> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<ah> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<bl> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<bh> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<cl> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<ch> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<dl> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<dh> mov BYTE PTR[si+Var],Source ELSE IFIDN <Source>,<ax> mov WORD PTR[si+Var],Source ELSE IFIDN <Source>,<bx> mov WORD PTR[si+Var],Source ELSE IFIDN <Source>,<cx> mov WORD PTR[si+Var],Source ELSE IFIDN <Source>,<dx> mov WORD PTR[si+Var],Source ELSE IFIDN <Source>,<di> mov WORD PTR[si+Var],Source ELSE IFIDN <Source>,<si> mov WORD PTR[si+Var],Source ELSE IFIDN <Size>,<1> mov BYTE PTR[si+Var],Source ELSE IFIDN <Size>,<2> mov WORD PTR[si+Var],Source ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDM <a name="0088_001e"> <a name="0088_001f">[LISTING SIX]
<a name="0088_001f"> Macro File: objects.mac COMMENT % ==================================================================== Gets an object's instance variable, but object is pointed to by one of Self's instance variables. Passed: Dest- Destination register; Var - Instance variable name; Obj - Source object instance variable =============================================================================% getInst$ MACRO Dest,Var,Obj mov si,WORD PTR[Self] mov si,WORD PTR[si].Instances mov si,[si+Obj] mov si,WORD PTR[si].Instances mov Dest,[si+Var] ENDM COMMENT % ==================================================================== Sets an object's instance variable, but object is pointed to by one of Self's instance variables. Passed: Var - Instance variable name; Source - Source register; Obj - Source object instance variable; Size - Size of data =============================================================================% setInst$ MACRO Var,Source,Obj,Size mov si,WORD PTR[Self] mov si,WORD PTR[si].Instances mov si,[si+Obj] mov si,WORD PTR[si].Instances setInst_ Var,Source,Size ENDM [Example 1] send Screen,Refresh,DoubleBdr ;Send Screen a Refresh msg send Self,Read ;Send Self a Read msg send Self,<WORD PTR[bx]> ;Send Self msg pointed to by ; BX register [Example 2] defObj Window,\ ;Define Window object <>,\ ;As a base object <>,\ ;With no inst vars <Refresh> ;Responds to Refresh msg defObj Border,\ ;Define Border object <>,\ ;As a base object <>,\ ;With no inst vars <Refresh> ;Responds to Refresh msg defObj Screen,\ ;Define Screen object <Window,Border>,\ ;As a derived object <Row1,1,1,\ ;With these inst vars Col1,1,0,\ Row2,1,23,\ Col2,1,79,\ Color,1,34h,\ BdrColor,1,30h,\ MemSeg,2,Nil>,\ <Refresh> ;Responds to Refresh msg [Example 3] defMsg Window,\ ;Define for Window object Refresh,\ ;The Refresh msg <clrWin,,> ;To clear window defMsg Border,\ ;Define for Border object Refresh,\ ;The Refresh msg <,,drawBdr> ;To draw border defMsg Screen,\ ;Define for Screen object Refresh,\ ;The Refresh msg <,drawBackDrop,drawLabel> ;To draw back drop and label [Example 4] defMsg Label,\ ;Define for Label object Refresh,\ ;The Refresh msg <,,drawLabel> ;To draw label defObj label,\ ;Define Label object <>,\ ;As a base object <>,\ ;With no inst vars <Refresh> ;Responds to Refresh msg defMsg Screen,\ ;Define for Screen object Refresh,\ ;The Refresh msg <,drawBackDrop,> ;To draw back drop defObj Screen,\ ;Define Screen object <Window,Border,Label>,\ ;As a derived object <Row1,1,1,\ ;With these inst vars Col1,1,0,\ Row2,1,23,\ Col2,1,79,\ Color,1,34h,\ BdrColor,1,30h,\ MemSeg,2,Nil>,\ <Refresh> ;Responds to Refresh msg [Example 5] initObj Screen ;Combine methods for Screen [Example 6] getInst bl,Color,Screen ;Fetch Screen color setInst BdrColor,bl ;Copy it to BdrColor setInst Color,bl,Self ;And Self's color [Example 7] getInst$ bl,Color,Master ;Fetch color from object ; pointed to by Master setInst Color,bl,Self ;Copy it to Self's color
Copyright © 1992, Dr. Dobb's Journal