Strukturiertes Programmieren

Der Assemblersprache hängt nach wie vor der Ruf an, daß sich mit ihr nur schwer lesbarer Spaghetticode erzeugen ließe. Dabei bieten gerade die neueren Assembler aus den Häusern Borland/Inprise und Microsoft vielfältige Möglichkeiten, ähnlich komfortabel wie in Hochsprachen zu programmieren (natürlich abgesehen von den Programmierwerkzeugen, wo man nur noch Bildschirmelemente zusammenschiebt und der Code dann vom Programm erzeugt wird).
In diesem Kapitel wird es um die Möglichkeiten gehen, die Ihnen TASM bietet, um sauberen, strukturierten und gut lesbaren Code zu erzeugen.

Konstanten

Es gibt zwei verschiedene Arten von Konstanten: Literalkonstanten und 'echte' Konstanten.
Mit Literalkonstanten arbeiten Sie immer dann, wenn Sie einen bestimmten Wert direkt angeben. So ist zum Beispiel in der Anweisung mov ax,34567 die Zahl 34567 eine Literalkonstante oder kurz ein Literal.
Eine 'echte' Konstante wird hingegen irgendwo im Quelltext definiert. Dabei sieht es so aus, daß einem Symbol (Name) ein Wert zugewiesen wird. Anders als bei einer Variablenvereinbarung wird aber kein Speicherplatz eingerichtet. Das bedeutet, daß Sie beruhigt viele Konstanten definieren können, allein durch diese Definition aber noch keinen Speicher verbrauchen.
Damit die Konstanten einen Sinn erfüllen, müssen Sie irgendwo im Quelltext (einer Variable oder einem Register) zugewiesen werden. Während der Übersetzung des Programms werden die Konstanten dann durch ihren Wert ersetzt. Daraus ergibt sich die Konsequenz, daß der Wert einer Konstanten während der Laufzeit eines Programms nicht verändert werden kann. Muß der Wert aber während der Laufzeit verändert werden, so müssen Sie auf Variablen ausweichen.

In Assembler haben Sie zwei Möglichkeiten, eine 'echte' Konstante zu definieren.
Möglichkeit 1:
Konstantenname EQU Wert
Möglichkeit 2:
Konstantenname = Wert
Beispiele:
USt = 16
PI = 3.14159
Version EQU 1

In den meisten Fällen besteht zwischen den beiden Möglichkeiten kein Unterschied. TASM gibt Ihnen jedoch die Möglichkeit, mit '=' definierte Konstanten im Verlaufe des Programmtextes zu ändern (wohlgemerkt nur im Quellcode, nicht zur Laufzeit).

TASM bietet Ihnen die Möglichkeit, mit Konstanten zu rechnen und Konstanten die Ergebnisse von Rechnungen zuzuweisen. Die Operanden der Rechnungen müssen natürlich zur Übersetzungszeit feststehen. Sollte dies nicht der Fall sein, so müssen Sie auf Variablen ausweichen.
Beispiele:

PI = 3.14159
Durchmesser = 4
Umfang = PI * Durchmesser
String db 'Ein kleiner Text',0
StringLaenge = SIZE String
Bei der vorletzten Anweisung wird eine Variable String definiert. Die folgende Anweisung weist der Konstanten StringLaenge die Größe dieser Variablen zu. Dabei wird die TASM-Direktive SIZE verwendet. Zu beachten ist, daß diese Direktive im IDEAL-Mode eine andere Wirkung als im MASM-Mode hat. Während im IDEAL-Mode tatsächlich die Länge des Strings zurückgegeben wird, erhält man im MASM-Mode lediglich die Größe des Datentyps in Byte, in diesem Fall also 1.

TASM skaliert Konstanten automatisch entsprechend dem Kontext. Wird die oben definierte Konstante Durchmesser beispielsweise einem Byte-Register wie AL oder CH zugewiesen, dann passiert nichts. Bei einer Zuweisung an ein Word-Register wie AX oder CX wird automatisch eine Erweiterung auf 16 Bit durchgeführt. Umgekehrt sieht es schon etwas kritischer aus. Definiert man folgende Konstante

BiggerThanByte = 400
und versucht dann, diese Konstante einem Byte-Register zuzuweisen
mov al,BiggerThanByte
dann erhält man vom Assemblierer eine Fehlermeldung, daß der Operand zu groß sei (was richtig ist, da 400 nicht mehr mit 8 Bit dargestellt werden kann).

Makros

Makros stellen in Assembler eine Möglichkeit dar, eine bestimmte Anzahl Befehle zusammenzufassen und über einen Namen wie einen eingebauten Assemblerbefehl zu verwenden.
Eine Makrodefinition wird durch die Worte MACRO und ENDM eingeschlossen. Zur Verdeutlichung:

        MACRO MacroName
                ...
        ENDM
Der MASM-Mode geht so vor:
        MacroName MACRO
                ...
        ENDM

Makros können erst nach ihrer Definition verwendet werden. Die Verwendung läuft analog zu allen anderen Assemblerbefehlen:

        mov ax,2
        MacroName
        cmp al,34
        jne Nein
        ...
        ...
        Nein:

Wählen wir ein einfaches Beispiel. In verschiedenen Publikationen zu Assembler wird zum Beispiel dieses Makro verwendet:

        MACRO DosInt
                int 21h
        ENDM

Wie Sie unschwer erkennen können, macht dieses Makro nichts anderes, als den sogenannten DOS-Interrupt mit der Nummer 21h aufzurufen.
Für ein Anwendungsbeispiel ein etwas längerer Codeausschnitt:

DATASEG
        msg db 'Hallo',13,10,'$'
        MACRO DosInt
                int 21h
        ENDM
CODESEG
  start:
          STARTUPCODE
          mov ah,09h
          mov dx,OFFSET msg
          DosInt
        EXITCODE
end start

Wir vereinbaren also im Datensegment die Byte-Variable msg, die einen String darstellt, der für eine Funktion des Betriebssystem ein zur Ausgabe gültiges Format besitzt.
Danach wird das Makro definiert. Wie Sie sehen können, ist es TASM relativ egal, wo die Definition stattfindet. Man könnte eigentlich davon ausgehen, daß Makros, die ja Programmcode darstellen, auch nur im Codesegment definiert werden dürften. Warum die Definition an anderer Stelle aber erlaubt ist, wird in Kürze erklärt.
Mit der Anweisung CODESEG eröffnen wir das Codesegment.
Nebenbei: sowohl STARTUPCODE als auch EXITCODE sind Makros, die abhängig vom eingestellten Prozessor-, Sprach- und vor allem Speichermodell automatisch Code für den Einstieg und das Beenden des Programms erzeugen.
Ins Register AH laden wir den Wert 09h, ins Register DX laden wir die Adresse des im Datensegment liegenden Strings und mit DosInt schließlich rufen wir den Interrupt 21h auf, der die in den Registern stehenden Werte interpretiert. Dabei stellt er anhand der Zahl 09h in AH fest, daß eine Stringausgabe verlangt wird. Aufgrunddessen erwartet er im Register DX die Offsetadresse des auszugebenden Strings. Die Funktion gibt ab der in DX stehenden Adresse so lange Zeichen auf dem Bildschirm aus, bis sie auf das Zeichen '$' trifft. Das hat die logische Konsequenz, daß Sie mit dieser Funktion keine Strings ausgeben können, die das Zeichen '$' enthalten. Jetzt wissen Sie, was das Dollarzeichen am Ende der Initialisierung von msg zu bedeuten hatte. Aber was sollen die beiden Zahlen davor? Ganz einfach: die Funktion interpretiert sie als ASCII-Codes für Carriage Return (13) und Line Feed (10), also ein normaler Zeilenvorschub.

Wie funktionieren nun Makros?
An jeder Stelle, an der TASM auf den Aufruf eines Makros trifft, wird eigentlich nichts anderes durchgeführt als eine Textersetzung. Dabei wird der Name des Makros durch den Code ersetzt, den es repräsentiert, im obigen Falle also int 21h. Nach der Ersetzung sähe der obige Programmcode also so aus:

        STARTUPCODE
          mov ah,09h
          mov dx,OFFSET msg
          int 21h
        EXITCODE

Mit den jetzigen Fähigkeiten wären Makros nur in der Lage, häufig wiederkehrende Codesequenzen zusammenzufassen. Doch Makros können einiges mehr.
So ist es zum Beispiel möglich, Makros mit Parametern zu versehen, die zudem auch noch als optionale Parameter behandelt werden können.
Es gibt im Video-Bios eine Funktion, die den Videomodus einstellen kann. Diese wird mit der Funktionsnummer 00 in AH und dem gewünschten Videomodus in Register AL aufgerufen. Der Standardvideomodus ist Modus 3 mit 25 Zeilen á 80 Zeichen, hellgrauer Schrift auf schwarzem Hintergrund. Wir können jetzt ein Makro verfassen, daß einen als Parameter übergebenen Videomodus einstellt oder, wenn kein Parameter angegeben wurde, den Standardmodus einstellt.

        MACRO SetVideoMode mode       ;setzt den Videomodus mode bzw Modus 003
                IFB <mode>
                  mov ax,00003h
                ELSE
                  xor ah,ah
                  mov al,mode
                ENDIF
                int 10h
        ENDM

Unser Makro trägt also den Namen SetVideoMode. Der Parameter trägt den Namen mode. Wie Sie sehen können, hat der Parameter keinen Datentyp wie Byte oder Word.
Mit der Anweisung IFB <mode> wird geprüft, ob der Parameter mode überhaupt vorhanden ist (IFB=IF Blank). Sollte das nicht der Fall sein (mode ist also 'blank'/leer), dann wird der Standardwert gesetzt. Hier sehen Sie auch gleich eine Möglichkeit der Optimierung. Statt bei bekannten Werten die beiden 8-Bit-Teilregister von AX mit zwei mov-Befehlen zu füllen, verwenden wir nur einen mov-Befehl, der das komplette Register AX füllt.
Für den Fall, daß der Parameter mode nicht leer ist, tritt der Teil hinter ELSE in Erscheinung. Da hier die Werte nicht bekannt sind, müssen AL und AH einzeln beschrieben werden. Sie können auch sehen, daß der Parameter mode wie eine Konstante behandelt wird - die Skalierung erfolgt entsprechend dem Kontext. Wäre mode größer als 255, dann würde TASM einen Fehler melden, weil der Wert nicht mehr in AL untergebracht werden kann. Mit ENDIF endet die Abfrage auf BLANK. Anschließend wird dann noch der Interrupt 10h aufgerufen, der die Registerinhalte interpretiert.

Im folgenden wird genau geschildert, welcher Code im Falle eines Makroaufrufes mit und ohne Parameter erzeugt wird.

Aufruf ohne Parameter:
DATASEG
        msg db 'Hallo',13,10,'$'
        MACRO OutString str              ;gibt den String str auf STDOUT aus
                mov ah,09h
                mov dx,OFFSET str
                int 21h
        ENDM
        ;
        MACRO SetVideoMode mode       ;setzt den Videomodus mode bzw Modus 003
                IFB <mode>
                  mov ax,00003h
                ELSE
                  xor ah,ah
                  mov al,mode
                ENDIF
                int 10h
        ENDM

CODESEG
  start:
        STARTUPCODE
          SetVideoMode
          OutString msg
        EXITCODE
end start
Der erzeugte Code im Codesegment sieht so aus:
CODESEG
  start:
        STARTUPCODE
          mov ax,00003h
          int 10h
          mov ah,09h                      ;Funktionsnummer
          mov dx,OFFSET msg
          int 21h
        EXITCODE
end start
Jetzt noch der Code mit Parameter
DATASEG
        msg db 'Hallo',13,10,'$'
        MACRO OutString str              ;gibt den String str auf STDOUT aus
                mov ah,09h
                mov dx,OFFSET str
                int 21h
        ENDM
        ;
        MACRO SetVideoMode mode       ;setzt den Videomodus mode bzw Modus 003
                IFB <mode>
                  mov ax,00003h
                ELSE
                  xor ah,ah
                  mov al,mode
                ENDIF
                int 10h
        ENDM

CODESEG
  start:
        STARTUPCODE
          SetVideoMode 13h
          OutString str
        EXITCODE
end start
Der erzeugte Code im Codesegment sieht jetzt so aus
CODESEG
  start:
        STARTUPCODE
          xor ah,ah                      ;Funktionsnummer
          mov al,13h                     ;Videomodus
          int 10h
          mov ah,09h                     ;Funktionsnummer
          mov dx,OFFSET msg
          int 21h
        EXITCODE
end start

Wie Sie gesehen haben, habe ich auch noch ein Makro hinzugefügt, das die Ausgabe eines mit '$' terminierten Strings erledigt. Es sollte jetzt eigentlich klar sein, wie mit Makros mit einem Parameter umzugehen ist.
Ein Makro mit mehreren Parametern wird folgendermaßen definiert und aufgerufen:

        MACRO ManyParams param1, param2, param3
        ...
        IFB <param2>
          ..
        ELSE
          ..
        ENDIF
        ...
        ...
        ENDM
        ;
        ;Im folgenden die Aufrufmöglichkeiten
        ;
        ManyParams p1,p2,p3
        ;oder
        ManyParams p1,,p3

Sowohl bei der Definition als auch beim Aufruf werden die Parameter durch Komma getrennt. Kann einer der Parameter als optional behandelt und beim Aufruf weggelassen werden, so muß zumindest das Komma gesetzt werden, um anzuzeigen, an welcher Stelle der Parameter ausgelassen wurde. Sollte der erste bzw. letzte Parameter optional sein, so muß die Parameterliste entsprechend mit einem Komma angefangen werden oder mit dem letzen nichtoptionalen Parameter beendet werden.

TASM bietet Ihnen die Möglichkeit, sich sogenannte Makrobibliotheken anzulegen. Diese Bibliotheken sind einfache Textdateien, die eine Anzahl Makrodefinitionen enthalten und dann bei Notwendigkeit in die eigentliche Programmdatei eingefügt werden können.
Im Laufe dieses Tutorials werden noch mehr Makros entstehen und die bereits erstellten Makros finden noch weitere Verwendung. Deswegen beginne ich bereits jetzt, mit den Makros OutString und SetVideoMode eine Bibliothek aufzubauen, die später erweitert und in den folgenden Programmen nur noch eingebunden wird. Die Makrobibliothek nenne ich imacros.mak (i wie IDEAL). Sie sieht so aus:

;;imacros.mak
        MACRO OutString str              ;;gibt den String str auf STDOUT aus
                mov ah,09h
                mov dx,OFFSET str
                int 21h
        ENDM
        ;
        MACRO SetVideoMode mode       ;;setzt den Videomodus mode bzw Modus 003
                IFB <mode>
                  mov ax,00003h
                ELSE
                  xor ah,ah
                  mov al,mode
                ENDIF
                int 10h
        ENDM
Die Einbindung in die Programme sieht so aus:
        CODESEG
          INCLUDE "imacros.mak"
          ...

Sie können auch einen kompletten oder relativen Pfadnamen angeben.
Ab dem Zeitpunkt der Einbindung sind alle Makros verwendbar.
Falls Sie sich über die doppelten Semikola wundern: TASM kann Ihnen als Resultat der Kompilierung eine Listingdatei erstellen, in der zu jedem Befehl (Mnemonic) der Maschinencode aufgeführt ist. Kommentare, die mit einem doppelten Semikolon eingeleitet werden, werden nicht in die Listingdatei übernommen.

Die Ersetzung der Makros und deren Parameter findet zum Zeitpunkt der Kompilierung statt. Das bedeutet, daß im Kompilat bereits der vollständig aufgelöste Makrocode steht.
Da Makros ihren eigenen Aufruf ersetzen, sind sie selbst nicht adressierbar. Es wäre also unsinnig, mit einem Befehl wie lea dx,SetVideoMode in das Register DX die Offsetadresse des Makros zu laden. TASM würde in diesem Fall auch einen Fehler anzeigen.
Es ist zu bedenken, daß bei jedem Makroaufruf der komplette aufgelöste Makrocode an der Aufrufstelle eingefügt wird. Ruft man also fünfmal SetVideoMode mit oder ohne Parameter auf, dann wird fünfmal der dem Aufruf entsprechende Code eingefügt. Dies hat zur Folge, daß ein Programm, das mehrere Male ein längeres Makro aufruft, u. U. dramatische Größenzunahmen zu verzeichnen hätte. Eine tiefere Diskussion dieser Problematik finden Sie im Abschnitt über Prozeduren.
Aufgrund dieser Problematik ist es auch nicht möglich, innerhalb von Makros Sprungziele festzulegen. Wie Sie wissen, müssen Sprungziele innerhalb eines Programms einmalig sein. Bei mehrmaliger Codeersetzung sind sie das aber nicht. Trotzdem haben Sie die Möglichkeit, innerhalb von Makros Sprünge durchzuführen, wenn Sie die Sprungziele bei der Makrodefinition deklarieren:

        MACRO Spruenge
        LOCAL j1,j2
          ;normaler Code
          j1:
            ..
          j2:
            ..
          ..
        ENDM

TASM kümmert sich selbst darum, daß bei der Kompilierung die Sprungziele in den Makros so aufgelöst werden, daß keine Namensprobleme entstehen.

Abgesehen von STARTUPCODE und EXITCODE verfügt TASM noch über drei weitere eingebaute Makros, die sehr leistungsfähig sind.
Zum ersten wäre da die Blockwiederholung mit REPT zu erwähnen:

        REPT 10
                db 0
        ENDM

Dieses Makro erzeugt zehn Bytes, die mit dem Wert 0 initialisiert werden. Es wird also alles, was zwischen REPT und ENDM so oft ausgeführt, wie es der Parameter von REPT angibt.

Nun werden Sie sich vielleicht fragen, wo der Sinn dieses Makros liegt, vor allem, wo man den obigen Effekt besser mit db 10 dup (0) erreicht hätte.
Wenn man die Blockwiederholung mit Konstanten kombiniert, dann kann man sich einiges an Tipparbeit ersparen:

        Zaehler = 0
        REPT 10
                db Zaehler
                Zaehler = Zaehler+1
        ENDM
Dadurch wird folgende Tabelle erstellt:
        db 0
        db 1
        db 2
        db 3
        ...
        db 9
Als zweites wäre die Blockwiederholung mit IRP anzuführen:
        IRP MeinWert, <0,2,4,8,16,32>
                db MeinWert
        ENDM

Dieses Makro wird so oft wiederholt, wie Werte zwischen den eckigen Klammern stehen (im Beispiel sechs mal). Beim ersten Durchlauf wird der erste Wert (also 0), beim zweiten Durchlauf der zweite Wert (2) usw. verwendet. Dieser Wert wird dem ersten Parameter von IRP zugewiesen. MeinWert erhält also nacheinander alle Werte in eckigen Klammern.
Im obigen Beispiel wird folgende Liste erstellt:

        db 0
        db 2
        db 4
        db 8
        db 16
        db 32
Genausogut ließe sich damit ein mehrfaches Pushen oder Poppen realisieren:
        IRP Register, <AX,CX,DI,SI,DS>
                push Register
        ENDM

Natürlich ist auch dieses Beispiel mit TASM unnötig, da TASM von sich aus über die Fähigkeit verfügt, mehrfach zu pushen/poppen.

Die dritte Möglichkeit ist die Blockwiederholung mit IRPC:

        ...
        IRPC Zeichen, ahgtz
                cmp al, '&Zeichen&'
                je Enthalten
        ENDM
        ...
        Enthalten:
        ...

IRPC arbeitet so ähnlich wie IRP, nur daß hier Zeichen eines Strings verwendet werden. Nacheinander erhält Zeichen also die Werte a, h, g, t und z. Innerhalb des Makros wird verglichen, ob das Register AL einen Wert enthält, der Zeichen entspricht. Um die Verwendung von '&' zu erklären, hier die Expansion des Makros und danach die Expansion, wie sie erfolgen würde, hätten wir keine '&' eingefügt:

        IRPC Zeichen, ahgtz
                cmp al, '&Zeichen&'
                je Enthalten
        ENDM

        ;wird im ersten Durchlauf expandiert zu

        cmp al,'a'

        ;
        IRPC Zeichen, ahgtz
                cmp al, 'Zeichen'
                je Enthalten
        ENDM

        ;wird im ersten Durchlauf expandiert zu

        cmp al,'Zeichen'

        ;und damit einen Fehler verursachen

Hätten wir im letzten Beispiel auch noch die Hochkommata entfernt, wäre es im ersten Durchlauf zu folgender Expansion gekommen:

cmp al,a

Dabei hätte TASM nach einer Konstante oder Variablen mit dem Namen 'a' gesucht. Wäre dieses Symbol nicht aufzufinden, würde eine entsprechende Fehlermeldung ausgegeben.

Wie Sie bemerkt haben, verfügen die Blockwiederholungen über keinen eigenen Namen (nach dem Muster: MehrfachPush IRP Reg,<AX,CX,DI,SI,DS>). Sie müssen diese Makros also tatsächlich dort einfügen, wo sie ausgeführt werden sollen.
Vorwiegend sind diese Blockwiederholungen zum erstellen von Daten oder für häufig nacheinander auszuführenden Code geeignet.
Denken Sie bei diesen Makros immer daran, daß sie keine Schleife darstellen, sondern abhängig von den Parametern, die die Anzahl der Wiederholungen vorgeben, mehrfach in den Quellcode expandiert werden.

Es kann vorkommen, daß sich innerhalb eines Makros die Situation einstellt, daß Sie das Makro vorzeitig verlassen müssen bzw. können. Diese Möglichkeit wird Ihnen durch die Anweisung EXITM zur Verfügung gestellt. Sie ist nur innerhalb eines Makros gültig und wird einfach an einer Stelle in das Makro eingefügt, an der ein normaler Assemblerbefehl oder eine Makrobedingung stehen könnte:

        MACRO MitExitM
                ...
                ...
                EXITM
                ...
                ...
        ENDM

Unterprogramme - Prozeduren

Prozeduren stellen unter TASM die zweite Möglichkeit dar, gleichartige Codeabschnitte zusammenzufassen und über einen Namen aufzurufen.
Die Syntax einer Prozedurdefinition sieht im IDEAL-Mode folgendermaßen aus:

        PROC name [[language modifier] language] [distance]
        [ARG argument_list] RETURNS [item_list]
        [LOCAL argument_list]
        [USES item_list]
                Befehle
        ENDP [name]
Im MASM-Mode:
        name PROC [[language modifier] language] [distance]
        [ARG argument_list] RETURNS [item_list]
        [LOCAL argument_list]
        [USES item_list]
                Befehle
        name ENDP

Der Name der Prozedur ist frei wählbar. Es ist allerdings zu beachten, dass TASM Symbolnamen nur bis zum 32. Buchstaben unterscheidet. Werden also mehrere Prozeduren definiert, deren Namen sich erst im 33. Buchstaben unterscheiden, dann wird TASM diesen Fehler bei der Übersetzung melden.
Durch die Definition einer Prozedur wird im Grunde nichts weiter als ein Label definiert, ähnlich einer Sprungmarke. Wie dieses Label verwendet wird und welche Möglichkeiten der genaueren Definition es gibt, dazu im folgenden mehr.

Untersuchen wir die einzelnen Bestandteile der Syntax.
Der language modifier weist TASM an, besonderen Prolog- oder Epilog-Code einzufügen, wenn die Prozeduren mit Windows oder dem VROOM-Overlay-Manager in Verbindung stehen. TASM stellt die Modifizierer NORMAL, WINDOWS, ODDNEAR und ODDFAR zur Verfügung.
Sie haben bereits bei der MODEL-Direktive die Möglichkeit, einen language modifier einzustellen. Diese Einstellung wird automatisch von allen Prozeduren verwendet, die ihrerseits keinen eigenen Modifizierer einstellen.
Ist kein Modifizierer angegeben, wird automatisch NORMAL verwendet. Diese Einstellung bewirkt die Erzeugung von Standard-Prolog- und Epilog-Code.
WINDOWS erzeugt Code, der die Prozedur von Windows aus aufrufbar macht. Eine Voraussetzung dafür ist allerdings, dass es sich um eine FAR-Prozedur handelt (dazu später mehr). ODDNEAR und ODDFAR werden in Verbindung mit dem VROOM-Overlay-Manager verwendet.

Höhere Programmiersprachen bieten in vielen Fällen für eine Reihe von Problemen keine adäquate Lösung. So sind sie teilweise zu langsam, nicht maschinennah genug oder verbieten bestimmte Dinge einfach. Für solche Fälle, so es die Programmiersprache anbietet, können Module aus anderen Sprachen eingebunden werden. So kann z. B. ein schneller Suchalgorithmus in Assembler geschrieben und dann über einen sprachabhängigen Mechanismus in die höhere Programmiersprache eingebunden werden.
Die verschiedenen Sprachmodelle unterstützen Sie als Programmierer dabei. Da viele höhere Sprachen unterschiedlichen Konventionen beim Aufruf von Unterprogrammen folgen, kann die Codeerzeugung für diesen schnittstellenspezifischen Teil TASM überlassen werden.
TASM stellt die folgenden Sprachmodelle zur Verfügung: NOLANGUAGE, BASIC, FORTRAN, PROLOG, C, CPP (für C++), SYSCALL, STDCALL und PASCAL.
Angaben zum Sprachmodell können auch schon in der MODEL-Direktive getätigt werden. Diese Angabe gilt als Voreinstellung für alle weiteren Prozeduren. Sollte keine Angabe vorliegen, wird automatisch NOLANGUAGE angenommen. In diesem Fall müssen Sie eventuell eigenen Prolog/Epilog-Code schreiben.

Abhängig vom von Ihnen verwendeten Speichermodell arbeiten Sie mit einem oder mehreren Codesegmenten. Dies kann Auswirkungen auf den Aufruf Ihrer Prozeduren haben. Da der Code einer Prozedur für gewöhnlich an anderer Stelle als der aufrufende Code steht, muß der Prozessor nach Beendigung der Prozedur wissen, an welcher Stelle im Codesegment er fortfahren soll. Diese Stelle ist der Befehl hinter dem Prozeduraufruf. Die Adresse dieses Befehls (Register IP) wird auf den Stack gepusht und nach Beendigung der Prozedur wieder vom Stack ins Register IP gepopt.
Befindet sich die Prozedur im gleichen Segment wie der aufrufende Code, dann kann die Prozedur als NEAR definiert werden. Bei einem NEAR-Aufruf wird nur das Register IP auf den Stack gepusht.
Befindet sie sich in einem anderen Segment, muss sie als FAR definiert werden. Die Definition findet über das Element distance statt. Als Voreinstellung dient das verwendete Speichermodell. So ist in den Modellen TINY, SMALL und COMPACT die Standardadressierungsart NEAR, in allen anderen FAR.

Parameter

Argumente können auf zwei Arten an eine Prozedur übergeben werden: über Register und über den Stack. Darüber, was sinnvoller ist, streiten die Gelehrten schon seit Jahren. Man kann davon ausgehen, dass der Weg über den Stack der etwas langsamere ist, da Speicherzugriffe immer langsamer als Zugriffe auf Register sind. Ab einer bestimmten Anzahl von Argumenten kann man allerdings in Bedrängnis mit den zur Verfügung stehenden Registern kommen. Da diese in den Prozeduren ebenfalls verwendet werden, kann man sinnvollerweise nur auf unbenutzte Register ausweichen, die je nach Aufgabe vielleicht nur spärlich vorhanden sind.
Für die Übergabe über den Stack bietet TASM einen einfachen Mechanismus an, der die umständliche manuelle Adressberechnung vermeidet.
Hinter ARG können Sie die Parameter, die Sie an die Prozedur übergeben wollen, mit einem Namen und einem Datentyp versehen.
Prozeduren die Parameter erwarten, erhalten in aller Regel einen eigenen sogenannten Stackrahmen. Durch diesen Stackrahmen erhält das Register BP den Wert der aktuellen Stackspitze. Würde man eine Prozedur ohne Sprachmodell und mit eigenem Stackrahmen erstellen wollen, müsste man etwa folgendes schreiben:

        PROC name
          push bp                      ;BP auf den Stack
          mov bp,sp                    ;aktuelle Stackspitze nach BP
          .
          .
          pop bp                       ;BP wieder vom Stack holen
          ret                          ;Rücksprung
        ENDP name

Wenn die Prozedur Parameter auf dem Stack erwartet, dann könnte das folgendermaßen aussehen:

MODEL SMALL
DATASEG
        msg DB 'Parameter 1',0
        wiederhole dw 1000
CODESEG
  start:
          STARTUPCODE
          push OFFSET msg
          push wiederhole
          call WriteX
        EXITCODE
        ;
        PROC WriteX
        ;einen String mit X Wiederholungen schreiben
          push bp                      ;BP auf den Stack
          mov bp,sp                    ;aktuelle Stackspitze nach BP
          mov cx,[bp+4]                ;Wiederholungszähler laden
          mov dx,[bp+6]                ;Stringoffset laden
          .
          .
          pop bp                       ;BP wieder vom Stack holen
          ret                          ;Rücksprung
        ENDP WriteX
end start

So würde üblicherweise (selbstverständlich gibt es auch hier mehrere Möglichkeiten) der Zugriff auf Parameter auf dem Stack aussehen.
Wie im Kapitel über den Stack angesprochen, wächst die Adresse der Stackspitze in Richtung kleiner werdender Adressen. Nachdem BP auf den Stack gebracht wurde, zeigt SP auf die Adresse, an der BP liegt. Um ohne Stackbefehle auf diese Adresse zugreifen zu können, würde man sie mit [SP + 0] oder einfach [SP] ansprechen.
Der Stackrahmen wird aufgabaut, damit man eine gleichbleibende Basis zum Zugriff auf die Parameter hat. Würde man SP zur Adressierung verwenden, dann müsste man bei jeder Stackveränderung (z. B. durch PUSH oder POP) die aktuelle Stackspitze neu berechnen, was einfach zuviel Aufwand bedeutet.
Da BP nach Eintritt in die Prozedur den Wert von SP erhält, zeigt also [BP + 0] auf seinen eigenen ehemaligen Wert. An der Adresse [BP + 2] liegt die Rücksprungadresse, die durch den CALL-Befehl auf den Stack gebracht wurde. Da wir uns (dank der Direktive SMALL) in einem NEAR-Speichermodell befinden, wird nur der Offset-Anteil der Rücksprungadresse (IP) auf den Stack gebracht. In einem FAR-Speichermodell oder bei einer FAR-Deklaration (die auch einen FAR-CALL notwendig gemacht hätte) wären 4 Bytes gepusht worden. Die beiden Parameter wurden vor dem Aufruf der Prozedur gepusht. Der zuletzt gepushte Wert (der Wiederholungszähler) ist über [BP + 4] adressierbar, der Stringoffset kann über [BP + 6] angesprochen werden. Unter den Bedingungen eines FAR-CALLs hätten zu korrekten Adressierung der beiden Parameter noch jeweils 2 Bytes addiert werden müssen ([BP + 6],[BP + 8]).

Wäre das Programm nach Aufruf der Prozedur nicht beendet gewesen, hätten wir uns noch um die Bereinigung des Stacks kümmern müssen. BP erhält vor dem Rücksprung zwar noch seinen ursprünglichen Wert, aber die vor dem Prozeduraufruf gepushten Parameter werden nicht wieder vom Stack entfernt. Dies könnte entweder in der Prozedur geschehen oder nach der Rückkehr hinter dem Aufruf. Der Einfachheit halber würde man in solchen Fällen nicht zu mehrfachen PUSH-Befehlen greifen, sondern einfach entsprechend dem beanspruchten Platz auf dem Stack (in unserem Fall 4 Bytes) durch einen Befehl wie add sp,4 die Stackkorrektur vornehmen (das klappt innerhalb der Prozedur nur dann, wenn die Korrektur nach dem Zurückladen von BP durchgeführt wird).

Durch die erweiterten Möglichkeiten bei der Prozedurdefinition wird einem die Last mit der manuellen Adressberechnung abgenommen. Die Parameter werden einfach über einen Namen angesprochen, die eine Adresse repräsentieren, deren Berechnung vom Assemblierer vorgenommen wird.

MODEL SMALL,PASCAL
IDEAL
DATASEG
        msg DB 'Parameter 1',0
        wiederhole dw 1000
CODESEG
  start:
        STARTUPCODE
          call WriteX,OFFSET msg,wiederhole
        EXITCODE
        ;
        PROC WriteX near MsgOfs:word,counter:word
        ;einen String mit X Wiederholungen schreiben
          mov cx,[counter]                   ;Wiederholungszähler laden
          mov dx,[MsgOfs]                    ;Stringoffset laden
          .
          .
          ret                                ;Rücksprung
        ENDP WriteX
end start

Wie man sieht, hat die Definition der Prozedur eine eher hochsprachenähnliche Form angenommen. Die erweiterte Definition ist nur in Verbindung mit einem Sprachmodell möglich. Die Angabe von Parametern kann auch ohne ARG erfolgen.
Was im Hintergrund passiert ist folgendes:
Abhängig vom Sprachmodell werden die Parameter auf den Stack gepusht. Beim hier festgelegten PASCAL-Sprachmodell werden die Parameter von links nach rechts in der Reihenfolge ihres Auftretens auf den Stack gepusht, also zuerst MsgOfs und dann counter (wie im Beispiel ohne Sprachmodell). Die Prozedur verwendet ebenfalls das PASCAL-Modell (programmweit eingestellt mit MODEL). Aus diesem Grunde weiß TASM, welche Adressen MsgOfs und counter repräsentieren. Wenn man zum Beispiel mit mov cx,[counter] auf den zweiten Parameter zugreift, dann erzeugt TASM daraus Code wie im vorherigen Beispiel (mov cx,[BP + 4]). Ebenso entfällt die manuelle Erzeugung eines Stackrahmens. Dies wird auch von TASM erledigt. Ein Stackrahmen wird bei Verwendung eines Sprachmodells nur dann erstellt, wenn auch Parameter definiert werden. Sollte dies der Fall sein, dann werden zum Auf- und Abbau des Stackrahmens die Befehle ENTER und LEAVE verwendet.
Eine Eigenschaft der PASCAL-Aufrufkonventionen ist, dass der Stack von der aufgerufenen Prozedur bereinigt wird. Der Code für die Bereinigung wird ebenfalls von TASM erzeugt.
Man kann also bereits an dieser Stelle sehen, dass einiges an Aufwand dem Assemblierer überlassen werden kann. Was man aber beim Prozeduraufruf unbedingt beachten muss, ist die Tatsache, dass Parameter mindestens Word-Größe haben müssen. Für den Fall, dass ein Byte übergeben werden soll, folgt hier eine andere Version des obigen Programmes.

MODEL SMALL,PASCAL
IDEAL
DATASEG
        msg DB 'Parameter 1',0
        wiederhole db 100              ;jetzt als Byte
CODESEG
  start:
        STARTUPCODE
          call WriteX,OFFSET msg,[word wiederhole]
        EXITCODE
        ;
        PROC WriteX near MsgOfs:word,counter:byte
        ;einen String mit X Wiederholungen schreiben
          mov cl,[counter]                   ;Wiederholungszähler laden
          xor ch,ch                          ;HighByte löschen
          mov dx,[MsgOfs]                    ;Stringoffset laden
          .
          .
          ret                                ;Rücksprung
        ENDP WriteX
end start

Beim Aufruf wird das Byte einfach in ein Word gecastet. Die Definition eines Parameters vom Typ Byte ist problemlos möglich. In der Prozedur kann ebenso problemlos auf den Parameter zugegriffen werden. Falls später das gesamte Register CX verwendet werden soll, dann muss noch das Register CH auf Null gesetzt werden, da der Inhalt dieses Registers bis zu dieser Stelle undefiniert ist.

Falls Sie bereits in PASCAL programmiert haben, dann werden sie die Ähnlichkeit zwischen PROC und PROCEDURE festgestellt haben und erwarten jetzt vielleicht auch ein Äquivalent zur FUNCTION. In Assembler gibt es keine expliziten Funktionen. Eigentlich ist auch in PASCAL eine Funktion nur eine Prozedur, die einen Wert zurückliefert. Dabei werden in PASCAL Bytes und Words in den Registern AL bzw. AX hinterlassen, größere Werte oder Pointer in Registerkombinationen. Dieses Prinzip ist selbstverständlich auch in Assembler anwendbar. Die Rückgabe von Werten im Register AX ist gewissermaßen als stille Übereinkunft anzusehen. Natürlich kann dafür auch jedes andere geeignete Register gewählt werden. Beim Schreiben von Modulen für eine Hochsprache ist nach deren Konventionen vorzugehen. Man kann also eine Prozedur schreiben, die zum Beispiel eine ganzzahlige Berechnung durchführt und das Ergebnis danach im Register EAX ablegt. Dort kann sich der aufrufende Programmteil dann das Ergebnis abholen.

Register sichern

Man sollte darauf achten, dass alle Register, die in einer Prozedur manipuliert werden, vorher gesichert werden (gilt nicht für Register, die Ergebnisse zurückliefern sollen). Am geeignetsten dafür ist der Stack. Werden in einer Prozedur die Register AX, CX, SI und DI verwendet, dann könnte man mit den erweiterten Stack-Befehlen folgendes schreiben:

        PROC WriteX near MsgOfs:word,counter:byte
        ;einen String mit X Wiederholungen schreiben
          push ax cx si di
          mov cl,[counter]                   ;Wiederholungszähler laden
          xor ch,ch                          ;HighByte löschen
          mov dx,[MsgOfs]                    ;Stringoffset laden
          .
          .
          pop di si cx ax
          ret                                ;Rücksprung
        ENDP WriteX

Als erstes in der Prozedur werden die Register gesichert, die verwendet werden. Unter TASM ist dafür nur ein Befehl notwendig. Vor dem Rücksprung aus der Prozedur werden die Register wiederhergestellt. Beachten Sie die umgekehrte Registerreihenfolge.
Insbesondere beim Zurückladen der Register drohen Fehlerquellen. Wie leicht wird die Reihenfolge verdreht oder ein Register ausgelassen.
Für diese Fälle existiert die Anweisung USES:

        PROC WriteX near MsgOfs:word,counter:byte
        ;einen String mit X Wiederholungen schreiben
          USES AX,CX,SI,DI
          mov cl,[counter]                   ;Wiederholungszähler laden
          xor ch,ch                          ;HighByte löschen
          mov dx,[MsgOfs]                    ;Stringoffset laden
          .
          .
          ret                                ;Rücksprung
        ENDP WriteX

Anstelle des POP-Befehls wird hier USES verwendet und die zu sichernden Register sind durch ein Komma getrennt. TASM fügt vor dem abschließenden Rücksprungbefehl selbständig Code ein, der die Register wiederherstellt. Man muss sich hier also nur noch um die Sicherung kümmern, (die Codeerzeugung für) die Rückspeicherung übernimmt TASM.

Lokale Variablen und Sprungmarken

Selbstverständlich kann auch in Assembler mit lokalen Variablen gearbeitet werden. Lokale Variablen werden auf dem Stack eingerichtet. Dazu muss nach Aufbau des Stackrahmens Platz geschaffen werden. Dazu wird von SP ein Wert abgezogen, der dem benötigten Speicherplatz entspricht (aufgerundet auf ein Vielfaches von 2). Der Zugriff auf eine lokale Variable erfolgt dann über Konstrukte wie [bp-2] (1. lokale Variable), [bp-4] (2. lokale Variable) usw. Bei Beendigung der Prozedur und noch vor Wiederherstellung des Registers BP muss durch eine Addition des vorher abgezogenen Wertes zum Register SP der Platz für die lokalen Variablen entfernt werden. Im Grunde genügt hier auch schon eine Übertragung des Wertes in BP in das Register SP.
TASM bietet eine Vereinfachung für die Adressberechnung bei lokalen Variablen:

        PROC WriteX near MsgOfs:word,counter:byte
        ;einen String mit X Wiederholungen schreiben
          USES AX,CX,SI,DI
          LOCAL MyLocalVar:word
          mov cl,[counter]                   ;Wiederholungszähler laden
          xor ch,ch                          ;HighByte löschen
          mov dx,[MsgOfs]                    ;Stringoffset laden
          mov [MyLocalVar],dx                ;die lokale Variable mit Wert belegen
          .
          ret                                ;Rücksprung
        ENDP WriteX

Durch die Anweisung LOCAL wird die Definition lokaler Variablen eingeleitet. Die Datentypdefinition erfolgt eher in Hochsprachenform als so, wie man es aus Variablendeklarationen im Datensegment kennt. Man kann an den Datentypen noch eine Art Multiplikator anhängen in der Form LOCAL MyLocalVar:word:10 . Der Multiplikator bezieht sich dabei immer auf den Datentyp, es werden also 20 Byte reserviert.
Das augenblickliche Problem ist, dass das Symbol MyLocalVar, obwohl eigentlich lokal definiert, im gesamten Modul sichtbar ist. Um das zu verhindern, gibt es die Direktive LOCALS. Durch Verwendung dieser Direktive ist es möglich, Symbole, die mit '@@' beginnen, als lokal zu betrachten. Statt der zwei @-Zeichen kann auch eine alternative Kennzeichnung festgelegt werden. So ist es mit LOCALS # möglich, ein anderes Präfix festzulegen. Im IDEAL-Modus ist die explizite Erwähnung von LOCALS nicht mehr notwendig. Hier wird automatisch angenommen, dass Symbole, die mit '@@' beginnen, als lokal zu betrachten sind.

        PROC WriteX near MsgOfs:word,counter:byte
        ;einen String mit X Wiederholungen schreiben
          USES AX,CX,SI,DI
          LOCAL @@MyLocalVar:word
          mov cl,[counter]                   ;Wiederholungszähler laden
          xor ch,ch                          ;HighByte löschen
          mov dx,[MsgOfs]                    ;Stringoffset laden
          mov [@@MyLocalVar],dx              ;die lokale Variable mit Wert belegen
          .
          ret                                ;Rücksprung
        ENDP WriteX

Auf die gleiche Art und Weise müssen Sprungzeile innerhalb einer Prozedur gekennzeichnet werden. Da auch Label im gesamten Modul sichtbar sind, kommt es zu Compilermeldungen, wenn in zwei Prozeduren das gleiche Sprungziel verwendet wird. Durch Voranstellen des Präfix wird nach den Sprungzielen nur noch im lokalen Bereich gesucht.

Auch in Assembler können Prozeduren verschachtelt werden. Die Prozedurdefinition unterscheidet sich nicht von der unverschachtelter Prozeduren. Ohne das LOCAL-Präfix ('@@') können die internen Prozeduren auch von anderer Stelle als der übergeordneten Prozedur aufgerufen werden.
Vorsicht ist geboten, wenn man in einer internen Prozedur auf lokale Variablen der übergeordneten Prozedur zugreifen möchte. Hat die interne Prozedur einen eigenen Stackrahmen, dann unterscheidet sich höchstwahrscheinlich der Inhalt von BP von dem Wert, auf dem die lokalen Variablen der übergeordneten Prozedur beruhen. Was in der äußeren Prozedur noch über [bp - 4] adressiert wird, müsste in der internen Prozedur vielleicht über [bp + 4] angesprochen werden. Trotzdem wird in der internen Prozedur die Adressierung über [bp - 4] verwendet.

Sichtbarkeit von Prozeduren

Vor allem im Hinblick auf Prozedurbibliotheken (s. nächstes Kapitel) und die Einbindung von Assemblermodulen in Hochsprachen muss noch geklärt werden, wie Prozeduren außerhalb des aktuellen Moduls sichtbar werden. Die Definition öffentlicher Prozeduren gestaltet sich ähnlich wie bei den Variablen:

MODEL SMALL,PASCAL
CODESEG
  start:
        PUBLIC WriteX
        PROC WriteX near MsgOfs:word,counter:byte
        ;einen String mit X Wiederholungen schreiben
          mov cl,[counter]                   ;Wiederholungszähler laden
          xor ch,ch                          ;HighByte löschen
          mov dx,[MsgOfs]                    ;Stringoffset laden
          .
          .
          ret                                ;Rücksprung
        ENDP WriteX
end start

Durch die Anweisung PUBLIC Symbolname wird das Symbol (Prozedurname, Variable...) außerhalb des aktuellen Moduls sichtbar.
Mit der Anweisung EXTRN Symbolname kann aus einem anderen Modul heraus auf das Symbol zugegriffen werden. Die Definition der Prozedur kann in diesem Modul entfallen. Ebenso entfällt die Definition eventueller Prozedurparameter innerhalb der EXTRN-Anweisung.

MODEL SMALL,PASCAL
DATASEG
        msg DB 'Parameter 1',0
        wiederhole db 100              ;jetzt als Byte
CODESEG
  EXTRN WriteX:PROC
  start:
          STARTUPCODE
          call WriteX,OFFSET msg,[word wiederhole]
        EXITCODE
end start

Die Anweisung GLOBAL Symbolname übernimmt eine Doppelrolle.
MODEL SMALL,PASCAL
DATASEG
        msg DB 'Parameter 1',0
        wiederhole db 100              ;jetzt als Byte
CODESEG
  GLOBAL WriteText:PROC WriteX:PROC
  start:
          STARTUPCODE
          call WriteX,OFFSET msg,[word wiederhole]
        EXITCODE
        ;
        PROC WriteX near MsgOfs:word,counter:byte
        ;einen String mit X Wiederholungen schreiben
          mov cl,[counter]                   ;Wiederholungszähler laden
          xor ch,ch                          ;HighByte löschen
          mov dx,[MsgOfs]                    ;Stringoffset laden
          .
          .
          ret                                ;Rücksprung
        ENDP WriteX
end start

Das Symbol WriteText ist im aktuellen Modul nicht definiert. Deshalb wird es als EXTRN behandelt. Da WriteX im aktuellen Modul definiert ist, wird es als PUBLIC behandelt.
Wie Sie ebenfalls sehen können, ist als Datentyp hinter den Prozedurnamen das Wort PROC angegeben. Dies ist nur als Platzhalter zu verstehen. Die tatsächliche Datentypgröße hängt vom aktuellen Speichermodell ab. In einem NEAR-Modell wäre es Word, in einem FAR-Modell wäre es DWord. Natürlich können Sie auch direkt Word oder DWord schreiben um die durchs Speichermodell bestimmte Größe zu überlagern.

Prozedurtypen

Sie können Prozedurtypen definieren und dann mit diesen Typen Prozeduren definieren

        PROCTYPE MyType near :word :byte
        ;
        PROC WriteX MyType near MsgOfs:word,counter:byte
          .
          .
          ret                              ;Rücksprung
        ENDP WriteX

Hier wird der Prozedurtyp MyType definiert und anschließend gewissermaßen eine Prozedur dieses Typs erstellt. Dabei wird geprüft, ob die Parameter sowohl in Art als auch in Zahl mit denen des Prozedurtypen übereinstimmen. Sollte dies nicht der Fall sein, so gibt es bei der Übersetzung Meldungen des Compilers.

Prozedurprototypen

Ebenso wie in der Sprache C haben Sie die Möglichkeit, Prototypen von Prozeduren (in C heißen sie Funktionsprototypen) zu erstellen. Ebenso wie die Prozedurtypen dienen die Prototypen der Überprüfung von Art und Anzahl sowie Aufrufkonvention von Prozeduren.

        PROCDESC WriteX near :word :byte
        ;

          PROC WriteX near MsgOfs:word,counter:byte
          .
          .
          ret                              ;Rücksprung
        ENDP WriteX

Sollte festgestellt werden, dass die Definition der Prozedur nicht mit ihrem Prototypen übereinstimmt, dann gibt es auch hier Fehlermeldungen.

Im Gegensatz zu Makros stehen Prozeduren nur einmal im Speicher und werden bei jedem Aufruf angesprungen. Bei einem Prozeduraufruf entsteht durch den call-Befehl selbst und durch das Pushen und Poppen von Parametern ein teilweise beträchtlicher Overhead. Dieser Overhead ist nur zu rechtfertigen, wenn der eigentliche Prozedurcode eine bestimmte Größe überschreitet. An dieser Größe läßt sich auch die Entscheidung festmachen, ob Sie einen Codeabschnitt als Makro oder als Prozedur in Ihre Anwendung aufnehmen. Ein weiteres Kriterium ist die Aufrufhäufigkeit. Da bei jedem Makroaufruf der komplette Makrocode erneut eingefügt wird, kann die Codegröße ganz schnell beträchtlich anwachsen. Man kann also sagen, wenn ein Makro länger als 10 Zeilen ist, lohnt bereits die Überlegung, ob man nicht lieber eine Prozedur verwendet.

Prozedurbibliotheken

Man kann den Quelltext der Prozeduren in Dateien hinterlegen und ähnlich wie bei den Makros zum Beispiel mit INCLUDE "procs.inc" in jedes Programm einbinden.
Das Problem dabei ist, dass bei jeder Übersetzung der Quelldatei alle in der Include-Datei enthaltenen Prozeduren übersetzt werden, auch wenn nur ein kleiner Teil der Prozeduren verwendet wird. Dadurch leidet natürlich die Übersetzungszeit, was besonders bei größeren Programmen den Compile-Test-Zyklus stark verlängern kann.
Stattdessen ist es möglich, die Object-Dateien in einer Bibliothek zusammenzufassen.
Für die Erstellung und Bearbeitung dieser Bibliotheken gibt es im TASM-Programmsystem die Datei tlib.exe.

Wenn Prozeduren aus einer Bibliothek verwendet werden, dann muss stätestens beim Linken der Object-Dateien (mit tlink.exe) die Bibliothek angegeben werden, in der sich die Prozeduren befinden. Um sich die Unannehmlichkeit zu ersparen, bei der Eingabe der Kommandozeile eine Bibliothek zu vergessen, kann die entsprechende Bibliothek bereits im Quelltext angegeben werden. Dazu dient folgende Anweisung:

      INCLUDELIB "datname.lib"

Leider ist hier die Angabe eines absoluten Pfades nicht möglich. Entweder wird die Bibliothek dann im aktuellen Verzeichnis gesucht oder in den Verzeichnissen, die Sie in der Kommandozeile von TLINK angeben können.

Damit die Prozeduren aus der Bibliothek in Ihrem Programm verwendet werden können, ist folgendes beispielhaftes Vorgehen notwendig.

      ...
      INCLUDELIB "datname.lib"
      EXTRN ProcName1:PROC ProcName2:PROC         ;auch GLOBAL moeglich
CODESEG
start:
      STARTUPCODE
        ..
        ..
        call ProcName1
      EXITCODE
END start

Wie schon bei den Prozeduren erklärt, bezieht sich die Angabe ':PROC' auf das aktuell eingestellte Speichermodell. Statt EXTRN wäre auch GLOBAL möglich gewesen.
Beim Aufruf der Prozedur ist gegenüber den internen Prozeduren nichts zu beachten. Auch eventuell vorhandene Parameter sind in gleicher Art und Weise wie bei den internen Prozeduren zu übergeben.

Der Location-Counter

Während der Kompilierung einer Datei führt TASM Buch über die aktuelle Position im Quelltext. Dieser Offset kann im Programmtext über die Spezialvariable '$' angesprochen werden.
Das macht genau dann Sinn, wenn man z. B. die Größe von Zeichenketten oder Strukturen ermitteln möchte. In dieser Form kann die Größe der Struktur nur direkt nach ihrer Definition erfolgen. Die Länge einer Speicherstruktur ergibt sich dann aus der Differenz zwischen aktueller Position und dem Offset der Speicherstruktur.

DATASEG
  MyWord dw ?
  msg db "Dieser Text ist zu lang um ihn manuell zu zaehlen",0
  msglen = $ - msg                      ;der Offset von msg steht beim Kompilieren fest

Die in msglen hinterlegte Größe kann dann zum Beispiel in Stringausgabefunktionen oder in Kopierfunktionen verwendet werden.