Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

Channels ▼


Smalltalk + C: the Power of Two

Source Code Accompanies This Article. Download It Now.


Dave is the president of Computer Based Information Systems, Inc., based in Ottawa, Ontario, Canada. He can be reached at 613-728-1558 or [email protected] Randolph is the executive vice president of engineering and development for Digital Composition Systems, Inc. He can be reached at 1715 W Northern Ave., Phoenix, AZ 85021, 602-870-7667.

Sophisticated applications require the integration of many software components and it has recently been argued that object-oriented technology is one of the most effective ways to deal with this complexity. Although many programmers advocate extending an existing language, such as C, into a hybrid language, such as C++ or Objective C, these systems lack the flexibility, library, environment, and memory management of pure object-oriented languages like Smalltalk.

In this article, we describe a pragmatic approach which we call "Smalltalk + C," that allows each language to be used where appropriate. Smalltalk, for example, is used to represent and manipulate high-level information while C is used to implement small, time/resource critical low-level facilities --an approach we've found to be effective. First, we'll illustrate the power of this unlikely marriage as implemented in dbPUBLISHER, a database publishing program we developed using Digitalk's Smalltalk/V. Second, we'll examine an embedded TCP/IP network application written in Smalltalk/V286.

The Strength of C

The major benefits and weaknesses of C are well known to most readers of DDJ. C is a low-level language for systems programming such as device drivers. It is portable, and in most cases, is the most efficient language available on a given hardware platform. Unfortunately, when C is used in a large project, its power leads to problems of reliability. (The market for hardware debuggers is testimony to the problems of debugging large C applications.) We use C for the necessary low-level routines for our applications. Because these routines typically have a single function and can be written in a small number of lines, they are readily understood and tested. Often these small modules can be reused in several quite different applications.

The Power of Smalltalk

Smalltalk provides all of the facilities for programming-in-the-large that C lacks, including a robust interactive environment for applications development. Because everything in Smalltalk is an object, pointer errors like those in C are eliminated. Smalltalk uses a sophisticated, high-speed, garbage collector to manage memory so the programmer doesn't need to worry about the complexities of storage management. Developers who work with Microsoft Windows, Presentation Manager, and the Macintosh are only too familiar with subtle storage management bugs, which are eliminated by using a garbage collector.

The class structure of Smalltalk provides the organizational framework needed for a large application. It allows the application programmer to concentrate on the application classes and to ignore low-level details. The Smalltalk language, however, doesn't allow the programmer to have direct access to the underlying hardware. While Digitalk's implementation is extremely fast, there are some things that require low-level programming, or at least access to a program written in another language. There are two ways to do this in Smalltalk/V, by "using the DOS shell" and through "user primitives." In Smalltalk/V286, a more sophisticated "virtual machine" interrupt strategy is available.

The dbPUBLISHER Challenge

Database publishing requires complete report generation, data conversion, formatting, and tightly coupled page composition tools in one integrated package, none of which is supported by traditional desktop publishing software. Our primary goal in the development of dbPUBLISHER was to provide these features in software that would run on 560K, 80286/386, MS-DOS/DOS hardware. Our biggest challenge was to do so with limited development funds and an outrageous 8-month delivery schedule.

Our existing library of C modules that perform page composition, image import, font management, and report generation required a completely new user interface. We needed a non-standard interface that used a small number of screens for report generation, report layout, professional typesetting, and markup tag insertion. Smalltalk/V proved to be the ideal tool for building the user interface and the main program. It dispatches to external C and Pascal programs for specialized services. Figure 1 illustrates dbPUBLISHER's architecture.

Figure 1: dbPUBLISHER architecture


                     Application primitives

  Composition  Output drivers  Font management  Report generation
    (Pascal)        (C)              (C)               (C)

Our user interface supports a full windows-style look-and-feel. An outliner style interface is used to define complex structured reports. This gives the user full visibility of the entire report structure. The user can construct complex nested SQL-style queries to compose a report directly from multiple databases and/or spreadsheets. To accommodate the demands of complex typography, dbPUBLISHER uses over 50 detailed dialog boxes. Page layout for reports is performed using a WYSIWYG MacDraw-style application. Unlike most drawing editors which use screen resolution, we use scaled pixels (1/4736287 inches). With this resolution dbPUBLISHER can be used for fine typography such as forms and tables.

Invoking Another Program

The DOS shell in Smalltalk/V rapidly dumps the current memory image to disk and runs an external program via COMMAND.COM. The resident stub is approximately 40 Kbytes. Our enhancement reduces the stub size and allows external programs outputting in text mode to pour properly into a Smalltalk/V graphics window. These facilities were implemented using our own Load-and-Execute primitives which also allow the current environment strings to be read and written.

Primitives are simply Smalltalk methods written in another language. A user of a primitive method doesn't see any difference in calling it versus calling a normal Smalltalk method. The code in Listing One illustrates both the definition and use of the primitive method doDCSPrimitive. In Smalltalk/V, you are forced to use numbered primitives, but in Smalltalk/V286 and V/Mac, you can have user primitives.

The Load-and-Execute primitive resides in low memory and swaps a block of high memory to a temporary file, adjusts the DOS memory handles so that DOS thinks there is enough memory available to run the application, and then performs a normal DOS EXEC function call (int 21h, ax=4h00h). When control returns to the primitive, the process is reversed; memory handles are reconstructed, the block is read from the temporary file (which is then deleted), and control is returned to Smalltalk. Unfortunately, this simple strategy does not work in real-life on 80x86-based computers running MS-DOS. The primary reason this approach fails is that the block of memory that is swapped out will quite likely contain data and code required by both DOS and any asynchronous events (notably, mouse events, control-break, and critical errors). In addition, the parameters that are passed to the primitive by Smalltalk also reside in this swappedout region.

In general, the solution is to move the offending objects to low memory where they can be protected by the primitive, or (in the case of unwanted asynchronous events such as the mouse event handler) be masked, disabled, or replaced, as appropriate. Smalltalk parameters and the DTA pointer can be moved into low memory, but asynchronous interrupt service routines (ISRs) are not easily detached and moved about. Thus, the only solutions available are to either disable them, or to replace the offending ISRs with dummy code that prevents the system from crashing.

It is not acceptable, however, to arbitrarily re-vector the entire interrupt vector table, nor is it acceptable to disable essential hardware interrupts (that is, diskette, keyboard, real-time clock, and so on). Actually, the only interrupts that need to be re-vectored are those that might get clobbered when the application is executed. In other words, if an interrupt vector currently points into the region of memory that we are about to make available, we should first point that vector to a dummy ISR' then restore it when we are through.

The source code in Listing Two illustrates the primitive implementation for an interface to DOS, special purpose dbPUBLISHER primitives, and running external C and Pascal programs.

A TCP/IP Ethernet Application

However unlikely it may seem, we used Smalltalk/V286 in a TCP/IP application. It provided split execution of the graphical human interface on an IBM AT, with a specialized Smalltalk/V286 embedded virtual machine running on a VME bus Unix system. This application proved that the speed of the byte code interpreter was up to the task.

This application required the processing of interrupts from an external hardware and software source. Digitalk provided virtual machine interrupts for this application. Listings Three and Four show source code that illustrates the interface between a TCP/IP Ethernet resident driver (that runs in Real mode) and Smalltalk/V286. (Listing Three shows a sample Smalltalk/V286 calling method, while Listing Four provides the Smalltalk/V286 assembly language primitive interface.) This interface allows a host PC to talk to an embedded target real-time system.

One Smalltalk user primitive is provided: socketPrimitive dispatches the appropriate code based on an opcode parameter. The arguments are passed in a Smalltalk Array. The primitive runs in protected mode, sets up the parameter block the installed driver expects, and switches to real mode (CALL_NETWORK macro in sockprim.inc) using the INT 50H call. This call then runs the doEthernetInt (in sockprim.asm) in real mode which does the INT 68H call to the installed driver as would any other application. On return, the error code is extracted from the parameter block and returned as an instance field of the receiver (the object to which the message causing the primitive was sent). This illustrates mode switching.

As part of initialization, the Smalltalk primitive calls the installed driver (using the procedure described earlier) with the address of a small routine (socket_event_handler). This address is in the segment of the primitive that is to be called when a Smalltalk VM Interrupt should be generated. Any hardware interrupts cause an automatic switch to real mode and a call to the handler that was installed before Smalltalk was invoked. Thus, upon servicing an interrupt from the Ethernet card, the installed driver can call socket_event_handler in real mode to cause a Smalltalk VM Interrupt. Virtual Machine Interrupts can be generated from either real or protected mode; both are handled by different Digitalk macros.


These examples illustrate how two proven programming languages can be combined into a single application. This approach enabled us to get a new product into production quickly, in spite of a small engineering budget. We now have time to do the OS/2 Presentation Manager version by re-directing Smalltalk/V286 primitives to the appropriate PM calls, and to improve performance with multitasking techniques. We can port the application to the Macintosh II with less trauma than typical Mac ports and with a guaranteed reliability factor. Smalltalk's inherent object protection yielded a bullet-proof application program for first release.

The success of Hypercard and Hypercard Xcommands (primitives) is another convincing example of the benefits of small, single-function primitives combined with a flexible user-programmable interpreter. The point is simple -- use the right language for the right job.


All source code for articles in this issue is available on a single disk. To order, send $14.95 (Calif. residents add sales tax) to Dr. Dobb's Journal, 501 Galveston Dr., Redwood City, CA 94063, or call 800-356-2002 (from inside Calif.) or 800-533-4372 (from outside Calif.). Please specify the issue number and format (MS-DOS, Macintosh, Kaypro).

SMALLTALK + C: THE POWER OF TWO by Dave Thomas and Randolph Best


<a name="016a_000c">

Object subclass: #Dos
    'registers temp1 temp2 '
  classVariableNames: ''
  poolDictionaries: '' !

!Dos methods !

doDCSPrimitive: opcode
     "PRIVATE - Call the DCS Primitives using an interrupt"
     <primitive: 96>
getEnvironmentValue: anEnvironmentString
     "Private- This is a method for getting the
     value of an environment variable(string). Answers
     the value if anEnvironmentString is valid or nil
     if not found.
     Here the instance variables are
     used as follows:
        temp1 = the name of the environment variable wanted
        temp2 = the value of the environment, if the
                      environment variable exists.      "
    temp1 := anEnvironmentString asAsciiZ.
    temp2 := String new: 128.
    (self doDcsPrimitive: -11)
        ifTrue:[ ^temp2 trimBlanks ]
        ifFalse:[ ^''].!

<a name="016a_000d"><a name="016a_000d">
<a name="016a_000e">
<a name="016a_000e">

;Copyright (C) 1986 - Digitalk, Inc. - Reprinted by Permission
; It is essential that this macro appear at the beginning of
; the primitive in that it saves certain registers.  Some of
; the registers may or may not have to be restored depending on
; whether or not the primitive is successful, and some
; must be restored before exiting the primitive.

enterPrimitive     MACRO

; At the end of this macro the stack will appear as below:
;  SP--> BP--> - saved BP - must be restored on exit
;          +2  - saved BX - must be restored in case of failure
;          +4  - saved DI - must be restored in case of failure
;          +6  - saved SI - must be restored on exit
;          +8  - saved DS - must be restored on exit
;          +10 - IP
;          +12 - CS
;          +14 - FLAGS
; if there are any argument passed to the primitive they are found here
;          +16 - last argument to primitive
;          +18 - second last argument to primitive
;              - etc...
; high address -
; Note: some of the following macros use BP assuming the value has not
;       changed from what this macro sets it to.  If you use BP be sure
;       to restore it before using the macros that make use of BP.

                   PUSH   DS                     ;set up stack as shown above
                   PUSH   SI
                   PUSH   DI
                   PUSH   BX
                   PUSH   BP
                   MOV    BP,SP                  ;set BP to Top of Stack

; This macro must be used when the primitive must be exited and the
; primitive was SUCCESSFUL.  The resulting pointer or small integer
; must be in BX before invoking this macro.  The macro will:
;   - mark the object (pointer) in BX so that the garbage collector
;     will not collect it as garbage
;   - certain registers are restored
;   - the return address and flags are popped into temporary registers
;   - the arguments are flushed and replaced with the result in BX
;   - the return address and flags are put back on the stack
;   - AX is set to zero (AH=0 indicates that the primitive was successful)
;   - and the IRET instruction is executed

exitWithSuccess    MACRO  numOfArgs

; On entry BX must contains the result to be pushed
; on the stack.

                   markPtr BX,ES                 ;mark result object in BX
                   POP    BP                     ;restore BP
                   ADD    SP,4
                   POP    SI                     ;restore DS:SI pair
                   POP    DS
                   POP    CX                     ;pop the offset,segment and
                   POP    DX                     ;flags into temp registers
                   POP    AX
IF numOfArgs
                   ADD    SP,numOfArgs * 2 + 2   ;flush all args of primitive
                   PUSH   BX                     ;push result on stack
                   PUSH   AX                     ;push flags, segment and
                   PUSH   DX                     ;offset back onto stack
                   PUSH   CX
                   XOR    AX,AX                  ;AH to 0 (prim was successful)
                   IRET                          ;interrupt return

; This macro must be used when the primitive must be exited and the
; primitive FAILED.  The macro will:
;   - restore all saved registers
;   - set AH to 1 indicating failure condition and AL to the number
;     of arguments passed to the primitive
;   - and the IRET instruction is executed

exitWithFailure    MACRO  numOfArgs

                   POP    BP                     ;restore all saved registers
                   POP    BX
                   POP    DI
                   POP    SI
                   POP    DS
                   MOV    AX,256 + numOfArgs     ;AH to 1 (prim failed)
                   IRET                          ;interrupt return

; This macro will return the address of the object with object pointer
; in objectReg.  The address will be returned in the register pair
; segmentReg:offsetReg.  segmentReg must be a segment register.

getObjectAddress   MACRO  objectReg,offsetReg,segmentReg

; All the arguments must be different registers.
; BP must not have changed from the value it was
; set to in the enterPrimitive macro.

                   MOV    segmentReg,[BP+12]
                   ROR    objectReg,1
                   MOV    offsetReg,segmentReg:[objectReg]
                   ROL    objectReg,1
                   MOV    segmentReg,SS:[objectReg]
                   AND    offsetReg,MASK offsetMask

; This macro will mark the object whose object pointer is in objectReg
; so the garbage collector will not collect as garbage.  It is to be used
; whenever an object pointer is stored.  If the object in objectReg is
; a small integer, no marking occurs.

markPtr            MACRO  objectReg,segmentReg
                   LOCAL  done

; BP must not have changed from the value it was
; set to in the enterPrimitive macro.

                   ROR    objectReg,1
                   JNC    done
                   MOV    segmentReg,[BP+12]
                   OR     BYTE PTR segmentReg:[objectReg],MASK grayMask
done:              ROL    objectReg,1


; This macro will get the class (pointer) of the object whose pointer
; is in objectReg.  If the object in objectReg is a small integer then
; the class pointer is set to ClassSmallInt.

getClass           MACRO  objectReg,classReg,segmentReg
                   LOCAL  done
;all the arguments must be different registers

                   MOV    classReg,ClassSmallInt
                   TEST   objectReg,1
                   JZ     done
                   MOV    classReg,SS
                   SHR    classReg,1
                   MOV    segmentReg,classReg
                   MOV    classReg,segmentReg:[objectReg]

; This macro will set the zero condition flag as to whether the object size
; is an even or odd number of bytes.  This test should be performed on
; byte-addressable objects.  A zero condition means that the object is an
; even number of bytes (actual size = object header - 2).  A non-zero
; condition means that the object is an odd number of bytes
; (actual size = object header - 3).

isSizeEven         MACRO  objectReg,workReg,segmentReg

; BP must not have changed from the value it was
; set to in the enterPrimitive macro.

                   MOV    workReg,objectReg
                   ROR    objectReg,1
                   MOV    segmentReg,[BP+12]
                   TEST   BYTE PTR segmentReg:[objectReg],MASK oddMask
                   MOV    objectReg,workReg


                DW      getCountryEnt
                DW      getEnvironEnt
      DW   exeApplicEnt
                DW      exeProgramEnt
      DW   setNoXlatEnt
      DW   setXlatEnt
      DW   atEndEnt
                DW      bufFlushEnt
      DW   bufWriteEnt
      DW   bufReadEnt
      DW   initOutputEnt
      DW   initInputEnt
jumpTable   DW   interruptEnt
      DW   inWordEnt
      DW   inByteEnt
      DW   outWordEnt
      DW   outByteEnt
      DW   peekEnt
      DW   pokeEnt
      DW   blockMoveEnt


dcsPrims        proc    far

; This is code that will be executed everytime this primitive
; is invoked from "Smalltalk/V"

        enterPrimitive                  ;enter primitive macro

        mov     bx,[bp+16]              ;get function code from first instance
        test    bl,1                    ; variable of receiving object.  if it
        jnz     failure                 ; isn't a number then something's very
                                        ; rotten in the state of Denmark.

        cmp     bx,16                   ;the function code is already shifted
        jge     failure                 ; left by one (courtesy of smalltalk's
        cmp     bx,-26                  ; way of identifying integers), so do
        jle     failure                 ; some range checks and abort if the
                                        ; value is out of bounds.

        jmp     cs:[bx+jumpTable]       ;otherwise, jump to the code associated
                                        ; with this function number.

        mov     cs:stackOfs,sp          ;save the current Smalltalk stack, and
        mov     cs:stackSeg,ss          ; replace it with a local stack in low
        mov     ax,cs                   ; (protected memory).
   mov   ss,ax
   lea   sp,cs:_stack

        mov     ah,2fh                  ;save the DOS DTA pointer, just in case
        int     21h                     ; it gets clobbered by the application.
   mov   cs:dtaOfs,bx
   mov   cs:dtaSeg,es

        call    exec                    ;load and execute the program
        mov     bx,truePtr              ; the errorlevel
        jnc     exec01                  ; code is returned in "retCode". If the
        mov     bx,falsePtr             ; carry was set, then an error occurred
exec01: mov     retVal,bx               ; and we answer "false", otherwise we
                                        ; answer "true".
        mov     ah,1ah                  ;restore the DOS DTA pointer.
   lds   dx,cs:dtaPtr
   int   21h

        cli                             ;and, restore the Smalltalk stack with
        mov     ss,cs:stackseg          ; the saved pointer.
   mov   sp,cs:stackofs

        mov     ax,retCode              ;convert the errorlevel to a Smalltalk
        shl     ax,1                    ; integer format, and return it in the
        les     bx,receiverPtr          ; fifth instance variable of receiver
        mov     es:[bx+12],ax           ; object.

        mov     bx,retVal               ;the Smalltalk convention is to put the
        jmp     success1                ; answer for this obj in bx, and call
                                        ; the "leavePrimitive" macro.  a jump
                                        ; to success will do this quite nicely
                                        ; thank yew!

<a name="016a_000f"><a name="016a_000f">
<a name="016a_0010">
<a name="016a_0010">

;queue an interpreter interrupt (in protected mode)
;  AL=interrupt number to queue
interruptVM MACRO
            CALL DWORD PTR SS:[queueVMinterrupt]
;queue an interpreter interrupt (in real mode)
ISVinterruptVM MACRO
            MOV ES,CS:[realParmSeg]
            CALL DWORD PTR ES:[ISVqueueVMinterrupt]
The code to call the socket primitive in Smalltalk is:
socketPrimitiveOpcode: opcode withArguments: argumentArray
    "PRIVATE: Call the socket primitive."
    &lt;primitive: socketPrimitive&gt;
    ^self error: 'Network Primitive failed - is sockprim.bin loaded?'

<a name="016a_0011"><a name="016a_0011">
<a name="016a_0012">
<a name="016a_0012">


;fixed segments

plusSmallSeg            = 6         ;segment of small positive integers
nilSegment              = 106H      ;segment of nil object
minusSmallSeg           = 116H      ;segment of small negative integers
booleanSeg              = 10EH      ;segment for true and false
characterSeg            = 11EH      ;segment for all character objects
fixedPtrSeg             = 126H      ;segment for all fixed ptr objects

;fixed offsets

nilOffset               = 106H      ;offset of nil object
trueOffset              = 0fff3H    ;offset of true object
falseOffset             = 0fff1H    ;offset of false object
firstCharOffset         = 2         ;offset of ascii char 0

;all of the following objects are in the segment fixedPtrSeg
;what is given below are their offsets
                                    ;array of classes in system
classArrayOffset    equ nilOffset+size objectHeader
Smalltalk           equ classArrayOffset + size assoc
ErrorCode           equ Smalltalk + size assoc


;Object header structure

objectHeader STRUC
ClassPtrHash    DW ?        ;see below for values for fixed classes
ObjectPtrHash   DW ?        ;usually contains object hash
GCreserved      DW ?
NumberFixed     DW ?        ;number of named instance variables
ObjectSize      DB 3 DUP(?) ;stored as low,middle,high order
                            ;  size is stored as # of instance variables
ObjectFlags     DB ?        ;defined below
objectHeader    ENDS

;object flags (contained in objectFlag byte of objectHeader)
PointerBit      EQU 10H     ;Object contains pointers
IndexedBit      EQU 8       ;Object has indexed instance variables
;other bits in byte are reserved

;Array Object
arrayObj STRUC
            DB size objectHeader DUP (?)
arrayObj    ENDS

;Character Object
charObj STRUC
            DB size objectHeader DUP (?)
asciiValue  DD ?            ;ascii value
charObj     ENDS
;Note that this is 16 bytes in size

; Association Object
assoc STRUC
            DB size objectHeader DUP (?)
assocKey    DD  ?
assocValue  DD  ?
assoc       ENDS

; Point object
pointObj STRUC
            DB size objectHeader DUP (?)
pointX      DD  ?
pointY      DD  ?
pointObj    ENDS

; Hash values for classes
SmallIntegerHash equ    0
emptySlotHash   equ     SmallIntegerHash + 8
StringHash      equ     emptySlotHash + 8
MessageHash     equ     StringHash + 8
SymbolClassHash equ     MessageHash + 8
LargePosIntHash equ     SymbolClassHash + 8
HomeContextHash equ     LargePosIntHash + 8
LargeNegIntHash equ     HomeContextHash + 8
ContextHash     equ     LargeNegIntHash + 8
PointHash       equ     ContextHash + 8
ArrayHash       equ     PointHash + 8
LinkHash        equ     ArrayHash + 8

; This is a useful struc for accessing arguments in primitives
; For example, to load the receiver into DS:SI
;           LDS SI,[BP+receiverPtr]

;stack after enterPrimitive macro
primitiveFrame STRUC
savedBP         DW  ?
returnAddr      DD  ?
receiverPtr     DD  ?
arg1Ptr         DD  ?
arg2Ptr         DD  ?
arg3Ptr         DD  ?
primitiveFrame  ENDS

;This struc defines the beginning of a user primitive load module
primLoadModule STRUC
installEntry        DW ?       ; 0 entry point for installation routine
reserved1           DW 0       ; 2
                    DW 0       ; 4
realCodeSeg         DW ?       ; 6 after loading, will contain real mode addr
primTableOffset     DW ?       ; 8 offset of table of primitive subroutines
realParmSeg         DW ?       ; A after loading, will contain real mode addr
                               ;   of virtual machine communication area.
reserved2           DW 0       ; C
                    DW 0       ; E
primLoadModule ENDS

;* Copyright (C) 1988 - Digitalk, Inc. - Reprinted by Permission

; It is essential that this macro appear at the beginning of
; the primitive in that it saves certain registers.  Some of
; the registers may or may not have to be restored depending on
; whether or not the primitive is successful, and some
; must be restored before exiting the primitive.

enterPrimitive MACRO

; At the end of this macro the stack will appear as below:
;  SP--> BP-->  - saved BP
;           +2  - return addr (offset)
;           +4  - return addr (segment)
; If there are any argument passed to the primitive they are found here.
; All arguments and the receiver are passed as 32 bit pointers
;           +6  - receiver of primitive  (offset)
;           +8  - receiver of primitive  (segment)
;           +10 - first argument to primitive  (offset)
;           +12 - first argument to primitive  (segment)
;               -           :
; high address  -           :
; Note: some of the following macros use BP assuming the value has not
;       changed from what this macro sets it to.  If you use BP be sure
;       to restore it before using the macros that make use of BP.

            PUSH    BP              ;save old BP
            MOV     BP,SP           ;set BP to Top of Stack

; This macro must be used when the primitive must be exited and the
; primitive was SUCCESSFUL.  The resulting pointer or small integer
; must be in DX,AX (DX=segment, AX=offset) before invoking this macro.

exitWithSuccess MACRO

; On entry DX,AX must contains the result to be pushed
; on the stack.
            MOV     SP,BP
            POP     BP              ;restore BP
            RETF                    ;far return

; This macro must be used when the primitive must be exited and the
; primitive FAILED.

exitWithFailure MACRO

            XOR     AX,AX           ;AX=DX=0, for failure return
            XOR     DX,DX
            MOV     SP,BP
            POP     BP              ;restore BP
            RETF                    ;far return

;Object testing macros
;in the following testing macros,
;the result is returned in the zero flag as follows:  z=no,  nz=yes

;Object has pointers?? jz=no, jnz=yes
isPointerObject MACRO objSeg,objOff
            TEST objSeg:[objOff+objectFlags],PointerBit

;Object is indexable?? jz=no, jnz=yes
isIndexedObject MACRO objSeg,objOff
            TEST objSeg:[objOff+objectFlags],IndexedBit

;Object is contained in single segment?? jz=no, jnz=yes
isSmallObject MACRO objSeg,objOff
            OR  objOff,objOff

;**** size extraction macros ****

;Object size is expressable in elements or bytes.
;   elements is the number of smalltalk objects it contains
;   bytes is the number of bytes it occupies (including header)
;       note that objects always occupy an even number of bytes
;For example:
;   #( 1 2 3 ) is an array with three elements and it occupies 24 bytes
;   'hello'    is a string with 5 elements and it occupies 18 bytes

;extract the size in elements
getElementSize MACRO objSeg,objOff,resultLowWord,resultHighByte
            MOV resultHighByte,byte ptr objSeg:[objOff+objectSize+2]
            MOV resultLowWord,word ptr objSeg:[objOff+objectSize]

;compute the size in bytes
getBigByteSize MACRO objSeg,objOff,resultLowWord,resultHighByte
            LOCAL addHeader
            getElementSize objSeg,objOff,resultLowWord,resultHighByte
            isPointerObject objSeg,objOff
            JZ  addHeader
            ADD resultLowWord,resultLowWord
            ADC resultHighByte,resultHighByte
            ADD resultLowWord,resultLowWord
            ADC resultHighByte,resultHighByte
addHeader:  ADD resultLowWord,size objectHeader+1
            ADC resultHighByte,0
            AND resultLowWord,0FFFEH

;user calls to interpreter routines

;routine vector offsets
ISVqueueVMinterrupt equ 0FFF0H - 4
queueVMInterrupt    equ ISVqueueVMinterrupt-4
oldToNewStore       equ queueVMinterrupt-4      ;used in oldToNewUpdate macro

;queue an interpreter interrupt (in protected mode)
;  AL=interrupt number to queue
interruptVM MACRO
            CALL DWORD PTR SS:[queueVMinterrupt]

;queue an interpreter interrupt (in real mode)
ISVinterruptVM MACRO
            MOV ES,CS:[realParmSeg]
            CALL DWORD PTR ES:[ISVqueueVMinterrupt]

;miscellaneous but usefull macros

;is object a small positive integer --  je=yes, jne=no
;(only segment needs to be tested)
isSmallPosInt MACRO segmentExpression
        CMP segmentExpression,plusSmallSeg

;is object a small negative integer --  je=yes, jne=no
;(only segment needs to be tested)
isSmallNegInt MACRO segmentExpression
        CMP segmentExpression,minusSmallSeg

;is object a character --   je=yes, jne=no
;(only segment needs to be tested)
isCharacter MACRO segmentExpression
        CMP segmentExpression,characterSeg

;is object static, i.e. constant, no stores allowed -- ja=no, jbe=yes
;(only segment needs to be tested)
isStaticObject MACRO segmentExpression
        CMP segmentExpression,characterSeg

;****** This macro must be called after EVERY pointer store *******
;****** Failure to do so will invalidate the garbage collector ****
;****** leading to catastrophic and unpredicable results **********

;This macro detects old space to new space pointer stores,
;and updates the GC data base accordingly.
;macro arguments are as follows:
;   segReg    = seg reg of object stored into
;   offReg    = offset reg of object stored into
;   valueSeg  = segment of pointer that was stored
;   workReg   = a work register
;example of use:  store ptr BX:AX into object ES:DI at slot 'contents'
;           MOV word ptr es:[di+contents],AX    ;store offset
;           MOV word ptr es:[di+contents+2],BX  ;store segment
;           OldToNewUpdate es,di,bx,ax
OldToNewUpdate macro segReg,offReg,valueSeg,workReg
            LOCAL done
            MOV workReg,segReg
            CMP workReg,92EH
            JAE done
            CMP valueSeg,92EH
            JB  done
            PUSH segReg
            PUSH offReg
            CALL DWORD PTR SS:[oldToNewStore]
            POP offReg
            POP segReg

;               The V286 Socket Primitives V2.0
;               In this implementation, recv() and accept() only operate in
;               a nonblocking fashion, returning errno EWOULDBLOCK if the
;               operation can not be completed immediately.  All other calls
;               block until completion or error.
;               Opcode 0 is implemented in this version as Socket closeAll.
;               Opcode 18 is implemented in this version as Socket version.
;               An additional non-standard errno EDRIVER (254) is returned if
;               installation fails or the installed driver behaves strangely.
TITLE Socket Primitives
            INCLUDE fixdptrs.usr
            INCLUDE objects.usr
            INCLUDE access.usr

            INCLUDE sockprim.inc

            ASSUME CS:code,DS:nothing,ES:nothing


;               Smalltalk/V286 Reserved Area
            ORG 0
            DW OFFSET install   ;installation routine entry point

            DW 0                                ;reserved for future use
            DW 0

                        DW 0                    ;real mode segment of this code

            DW OFFSET primTable ;addr of table of primitives and entry points
            DW 0                   ;real mode segment address of protected mode
                                   ;parameter area

            DW 0                   ;cell used for real mode calls to VM
            DW 0

;               Local Data
initialized_flag        DW      0
Socket_PB                       DB      32 DUP (0)
Name_Buffer                     DB      MAX_NAME_SIZE DUP (0)
Data_Buffer                     DB      MAX_BUFFER_SIZE DUP (0)

;               The socketPrimitive operation dispatcher
socketPrimitive    PROC   FAR
;   Dispatch the socket primitive specified by opcode.
;       PARAM(1) is the opcode
;       PARAM(2) is the argumentArray
            isSmallPosInt &lt;WORD PTR [BP+arg1ptr+2]&gt; ;opcode SmallInteger?
            JE              ok_opcode
            FAIL                                      ;FAIL if not
            MOV             AX,initialized_flag       ;perform initialization
            OR              AX,AX
            JNE             initialized
            PUSH    BP
            CALL    socket_install
            POP             BP
            CMP             AX,0
            JE              initialized
             MOV             AX,     WORD PTR [BP+arg1ptr]   ;opcode
;       check opcode bounds
                CMP             AX,0
                JL              exit_FAIL
                CMP             AX,MAX_OPCODE
                JG              exit_FAIL
                SHL             AX,1
                MOV             SI,AX
;       dispatch operation
                MOV             AX,WORD PTR DS:[Socket_Primitives+SI]
                JMP             AX


socketPrimitive ENDP

;       The socket event handler - called by resident driver in real mode
socket_event_handler    PROC    FAR
                MOV             AX,Network_VMInterrupt

socket_event_handler    ENDP

;               General purpose success exit
;               AX = integer value to be returned (positive or negative)
;               CMP             AX,0
;               JL              SUCCEED_NEGATIVE

;        Install Socket Event Handler - called automatically by dispatch
socket_install  PROC    NEAR
                MOV             AX,1
                MOV             SI,OFFSET initialized_flag
                MOV             DS:[SI],AX
                SET_PB_FOR      OPCODE_register_event_handler
                MOV             WORD PTR DS:PB_Event_Handler[BX],OFFSET socket_event_handler
                MOV             AX,DS:[realCodeSeg]
                MOV             WORD PTR DS:PB_Event_Handler+2[BX],AX
                MOV             BYTE PTR DS:PB_errno[BX],EDRIVER
                MOV             WORD PTR DS:PB_Return_Code[BX],-1
socket_install  ENDP

;               The socket operations

op_unimplemented        PROC    NEAR
                MOV             AX,EINVAL
op_unimplemented        ENDP

op_closeAll                     PROC    NEAR
                SET_PB_FOR      OPCODE_socket_close_all
op_closeAll                     ENDP

;               deinstall() - perform required cleanup before Smalltalk/V exit
op_deinstall    PROC    NEAR
                MOV             AX,0
                MOV             SI,OFFSET initialized_flag
                MOV             DS:[SI],AX
                SET_PB_FOR      OPCODE_register_event_handler
                MOV             WORD PTR DS:PB_Event_Handler[BX],0
                MOV             WORD PTR DS:PB_Event_Handler+2[BX],0
op_deinstall    ENDP

;               socket()
;               ARG 1   Address_Format
;               ARG 2   Type
;               ARG 3   Protocol
op_socket                       PROC    NEAR
                SET_PB_FOR      OPCODE_socket
                GET_POSITIVE_INTEGER_ARG        1
                MOV             DS:PB_Address_Format[BX],AX
                GET_POSITIVE_INTEGER_ARG        2
                MOV             DS:PB_Type[BX],AX
                GET_POSITIVE_INTEGER_ARG        3
                MOV             DS:PB_Protocol[BX],AX
                CALL_NETWORK            ;sets errno, result in AX
op_socket                       ENDP


; doEthernetInt
;    This procedure executes in REAL MODE. The parameter block has
;    been filled. setup es:bx to point to the parameter block and
;    call the ethernet driver.

doEthernetInt   PROC    FAR
        PUSH    AX                              ; Save registers
        PUSH    BX                              ;
        PUSH    ES                              ;
        MOV             AX,CS                   ;
        MOV             ES,AX                   ; es points to this segment
        MOV             BX, OFFSET Socket_PB    ; bx contains offset to pblock
        INT             Resident_Driver_Interrupt       ; call driver
        POP             ES                              ; restore registers
        POP             BX                              ;
        POP             AX                              ;
doEthernetInt   ENDP

;table of primitive names and entry points
            DB  'socketPrimitive'          ;Smalltalk name of primitive
            DB 0
            DW offset socketPrimitive      ;offset of entry point
;     more entries can go here

            DW 0                           ;end of table

;installation routine, called at the time the module is loaded
install     PROC FAR
            ret                            ;we have nothing to do, so return
install     endp

code        ENDS

Copyright © 1989, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.