Home

Die API ShellExecute als praktischer Ansatz
für das Drucken unter Windows

Sequentielles und direktes Lesen
von Sätzen einer Direktzugriffsdatei

Übersicht
Dateizugriffe

MASM32
Infoseite

MASM32-SDK (Direktzugriffsdateien bei Windows Konsolapplikationen:)
Testerei mit direktem Datenzugriff - Erstanlage der Datei

Dateien für direkten Datenzugriff bestehen aus Datensätzen gleicher Länge. Dies ermöglicht es, auf jeden einzelnen Datensatz einzeln zugreifen zu können, ohne ihn vorher suchen zu müssen. Man weiß, der Zugriff soll auf den 175. Datensatz erfolgen.

Typische Textdateien, wie sie mit einem Texteditor erstellt werden, haben unterschiedliche Zeilenlängen. Man kann jede Zeile als Datensatz auffassen, denn das Ende jeder Zeile wird bei Windows durch die Zeichenfolgen für das Zeilenende (CR und LF) oder das absolute Dateiende markiert.

Anders bei Direktzugriffsdateien: Sie benötigen keine Markierung des Satzendes, denn jeder Satz hat die gleiche Länge. Ist jeder Satz 50 Bytes lang, so beginnt der dritte Satz der Datei mit dem 101. Byte der Datei und hört mit dem 150. Byte auf.

Textdateien mit ihrer unterschiedlichen Satzlänge verändern ihre Länge, wenn Zeichen hinzugefügt oder entfernt werden.

Anders bei Direktzugriffsdateien: Sie werden vor ihrer Benutzung als inhaltslose Datei in der erkennbaren Länge angelegt. Die Inhalte der Datensätze können sich ändern, aber die Länge der Datei bleibt gleich. Zum Ändern der Länge der Datei sind besondere Maßnahmen erforderlich!

Auf dieser Webseite wird ein Programm zum Anlegen einer neuen Direktzugriffsdatei beschrieben. Damit man mit der Datei Versuche anstellen kann, steht in jedem ihrer Sätze die Nummer des Satzes. Dazu ist es erforderlich, aus einer binären Zahl eine ASCII-codierte Zahl zu machen. Dazu wurde ein kleines Makro entwickelt und mit dem nachfolgenden speziellen Testprogramm erprobt. Hinweise zum Testprogramms stehen unterhalb der Programmauflistung.

Man hätte die Umwandlung der binären Zahl in ASCII auch mit vorgefertigten Funktionen des MASM32-SDKs realisieren können. Wie dies funktioniert geht aus „Plausibilieren der Eingabe und Übersetzungen zwischen Ganzzahl und String” hervor.

Testprogramm für das Makro int2asc und Aufbau des Ausgabepuffers
Eine Interpretation steht hinter dem Programmlisting.
comment * ---------------------------
Testprogramm MACRO zur Codewandlung Binärzahl in AX nach ASCII
und Einspeichern der ASCII-Zahl rechtsbündig bis Adresse in edi

und anschliessend Aufbau des Ausgabepuffers aus dem die 
initialisierte Direktzugriffsdatei erstellt wird 

Build as a CONSOLE mode application
mymasm32rt.inc - siehe fredriks.de/8086asm/masm32-2.php#mymasm32rt
---------------------------------------- *
debugFT  equ FALSE
include  c:\masm32\include\mymasm32rt.inc
Main     PROTO                  ;erforderlich weil Main eine Prozedur ist

; ------------
; Lokales Makro
; erzeugt aus binaerzahl in ax eine dezimalzahl und baut sie rueckwaerts ab di
; im speicher auf.
; verfahren: division mit 10d, rest -> speicher
; ------------
int2asc MACRO
    mov     bx,10d          ;dezimalzahl 10 zum dividieren
    mov     cl,30h          ;fuer codewandlung hex 00 bis hexa 09 in ASCII
		
@@: xor     dx,dx           ;kurze zahl (dx war 0)
    div     bx              ;nun rest in dx, ganzzahl in ax
    or      dl,cl           ;rest nun im ASCII-code
    mov     [edi],dl
    dec     di
    or      ax,ax           ;fertig wenn ganzahl = 0
    jnz     @B
    ENDM
		
; Konstanten
; ------------------------------------------------

.data
satzanfang db    '0000'             ;gesamte satzlaenge 50 bytes 			
satzrest   db    '. Datensatz'
           db    35 dup(0)
satzlaenge equ   $ - satzanfang
satzanzahl equ   50
lbuffer    equ   satzanzahl * satzlaenge
buffer     db    lbuffer dup (1)

;Defaultwerte
fname      db    'C:\temp\versuch.txt',0	
lfname     equ    $ - fname

satznr     dw     1

.code
start:       SetConsoleCaption    "Erzeugt aus Binärzahl in ax eine Dezimalzahl"      
      invoke Main
      inkey
      invoke ExitProcess,eax
;-------------------------------

Main PROC
   DumpMem offset satzanfang, 32, "satzanfang vorher" 	
   DumpMem offset buffer, 32, "bufferanfang vorher"    
      mov    edi,offset satzrest
      dec    edi
      push   edi
      mov    ecx,satzanzahl
;--------------
; schleife tobuffer wandelt binaere satznr nach ASCII, fuegt 
; die ASCII-satznr in den satz ein und kopiert den satz in den
; ausgabedateipuffer buffer
;-------------- 	  
 
tobuffer:
      mov   ax,satznr	  
      push  ecx                 ;schleifenzaehler fuer tobuffer  
      int2asc                    
      pop   ecx 
      mov   ax,satznr           ;lfd. Satznummer         
      pop   edi                 ;Zieladresse fuer satz wieder herstellen		 

      push  edi	                ;adresse satzzaehler
      push  ecx	                ;schleifenzaehler fuer tobuffer
;--------------
; ermittelt zieladresse innerhalb des ausgabedateipuffers aus
; bufferanfang + (satznr - 1) * satzlaenge
;--------------	  
      xor   eax,eax
      mov   ax,satznr           ;lfd satznr
      dec   ax
      mov   bl,satzlaenge
      mul   bl					;abstand ab anfang buffer nun in eax
      mov   edi,offset buffer
      add   edi,eax
      mov   esi,offset satzanfang 	
      mov   ecx,satzlaenge

;--------------
; uebertraegt satz in satzlaenge zum buffer 	  
;--------------	  
@@:   mov   al,[esi]			
      mov   [edi],al
      inc   esi
      inc   edi
      loop   @B

      pop   ecx                ;schleifenzaehler fuer tobuffer
      pop   edi                ;zieladresse fuer satz wieder herstellen
	  
      push  edi                ;zieladresse sichern
      mov   ax,satznr          ;lfd satznr vorsetzen
      inc   ax                    
      mov   satznr,ax
      loop  tobuffer

      pop   edi                ;zieladresse fuer satz wieder herstellen
	  
  DumpMem offset satzanfang, 32, "satzanfang nachher"
  DumpMem offset buffer,32, "erster Satz im buffer"    
  DumpMem offset buffer+2450,32, "letzter Satz im buffer"     
      print offset satzanfang,13,10,0   
      mov   eax,TRUE
      ret
Main ENDP
 end start
Interpretation des Testprogramms für das Makro int2asc
Das Makro in dieser Form kann nur Binärzahlen von 00h bis 0ffh verarbeiten. Als Ergebnisse sind somit dezimal 0 bis 255 möglich.

div bx ;nun rest in dx, ganzzahl in ax: Der Divisionsbefehl führt die Division AX/BX=AX,DX durch. Das heißt, nach jeder Division durch 10 steht in DX der Rest als Ziffer zwischen von 0 bis 9. In AX steht der ganzzahlige Anteil aus der Division durch 10. Beispiel:

  • 235/10 ergibt 5 in DX und 23 in AX
  • 23/10 ergibt 3 in DX und 2 in AX
  • 2/10 ergibt 2 in DX und 0 in AX. 0 in AX bedeutet: Umwandlung ist fertig!
Die drei Ergebnisse in DX rückwärts gelesen ergeben die Zahl 235. Durch Verodern mit hexadezimal 30 werden die drei Ziffern vom Binärcode in ASCII-Code gewandelt. Wegen der Rückwärtsrichtung erfolgt die Abspeicherung im über das Register EDI adressierten Zielpuffer rückwärts:
mov     [edi],dl
dec     di

Es reicht aus, die Adressreduzierung auf di zu begrenzen, da die beiden höherwertigen Bytes von edi aus lauter Nullen bestehen und bestehen bleiben.

.data: Bei den Datendefinitionen fällt die Verwendung von equ besonders auf. Dadurch wird es ermöglicht, Satzanzahl und Satzlänge für das gesamte Programm durch Änderung von nur zwei Zahlen vollständig anzupassen.

Main PROC: Die „Prozedur” besteht auf einer Schleife mit der Schleifenvariablen von satzanzahl in Einerschritten absteigend. Die Satznummern sollen jedoch in Einerschritten ab 1 bis satzanzahl aufsteigend in den Datensätzen erscheinen. Deshalb wird in der Variablen satznr die Satznummer in der Gegenrichtung aufwärts gezählt.

Das gewählte Verfahren mit Pflege des Satzzählers ist zwar offensichtlich, aber es ist auch dümmlich. Eleganter und effizienter wäre es gewesen, die Satznummer aus dem laufenden Schleifenzähler zu ermitteln:
Satznummer=Satzanzahl + 1 - Schleifenzähler.

Die erstellten Datensätze könnten einzeln in die Datei geschrieben werden. Effizienter ist es jedoch, sie in einem großen Speicherpuffer abzulegen und den gesamten Puffer am Programmende mit einem einzigen Schreibbefehl in die Datei zu schreiben. Das Testprogramm baut den gesamten Puffer auf. Allerdings fehlt noch die Dateibehandlung.

Damit die erstellten Datensätze auf die richtige Position innerhalb des Ausgabedateipuffers kopiert werden, ist eine Adressberechnung mittels Satzlänge und Satznummer erforderlich. Bild: MemorydumpDie an verschiedene Stellen im Programm vorgesehenen Speicherabzüge erleichterten die Programmentwicklung. Der links gezeigte Speicherauszug entstand aus einem der ersten Teste zur Überprüfung des Makros mit der Satznummer 100. Nebenbei zeigt sich, dass der Code des Umlautes in „Binärzahl” für die Fensterüberschrift nicht passt.


Das Programm zum Erzeugen der anfänglichen Direktzugriffsdaei
Die Ergänzungen des Testprogramms zum fertigen Programm sind minimal. Es brauchte nur die Ausgabe in die Datei ergänzt zu werden. Außerdem wird am Programmende ein Hinweis auf den Dateinamen der erstellten Datei gezeigt. Letztlich erfolgte eine Optimierung bei der Kopie des Datensatz in den Ausgabedateipuffer durch Anwendung des Befehls rep movsb. Die Vorlage für eine noch schnellere laufzeitminimierte Variante ist bei den MASM32-SDK Beispielen unter \masm32\m32lib\memcopy.asm zu bewundern.

Zusätzlich aufgenommen wurde die Prüfung, ob die angeforderte Satznummer im zugelassenen Bereich ist. Ist sie es nicht, erfolgt keine Verarbeitung. Eine Verarbeitung mit einer angeforderten Satznummer größer als 50 würde zu einer Vergrößerung der Datei führen.

Hinweise zur Programmierung der Dateiausgabe stehen unterhalb der Programmauflistung.

Das Programm zum Erzeugen der anfänglichen Direktzugriffsdaei C:\temp\versuch.txt
comment * ---------------------------
Testprogramm zur Codewandlung Binärzahl in AX nach ASCII
und Einspeichern der ASCII-Zahl rechtsbündig bis Adresse in edi 

Build as a CONSOLE mode application
mymasm32rt.inc - siehe fredriks.de/8086asm/masm32-2.php#mymasm32rt
---------------------------------------- *
debugFT  equ FALSE
include  c:\masm32\include\mymasm32rt.inc
Main     PROTO                  ;erforderlich weil Main eine Prozedur ist

; ------------
; Locales Makro
; erzeugt aus binaerzahl in ax eine dezimalzahl und baut sie rueckwaerts ab di
; im speicher auf.
; verfahren: division mit 10d, rest -> speicher
; ------------

int2asc MACRO
      mov     bx,10d          ;dezimalzahl und baut sie rueckwaerts ab di
      mov     cl,30h          ;fuer codewandlung hex 00 bis hexa 09 in ASCII
		
@@:   xor     dx,dx           ;kurze zahl (dx war 0)
      div     bx              ;nun rest in dx, ganzzahl in ax
      or      dl,cl           ;rest nun im ASCII-code
      mov     [edi],dl
      dec     di
      or      ax,ax           ;fertig wenn ganzahl = 0
      jnz     @B
      ENDM
		
; Konstanten
; ------------------------------------------------
 
.data
satzanfang db    '0000'             ;gesamte satzlaenge 50 bytes 			
satzrest   db    '. Datensatz'
           db    35 dup(0)
satzlaenge equ   $ - satzanfang
satzanzahl equ   50
lbuffer    equ   satzanzahl * satzlaenge
buffer     db    lbuffer dup (1)

;Defaultwerte
fname	db      'C:\temp\versuch.txt',0	
lfname  equ     $ - fname
crlf	db      13,10,0
satznr	dw      1

 .code
start:       SetConsoleCaption    "Erzeugt aus Binärzahl in ax eine Dezimalzahl"      
      invoke Main
      inkey
      invoke ExitProcess,eax

;-------------------------------

Main PROC
    LOCAL hFile :DWORD
    LOCAL rval  :DWORD        	;Anzahl geschriebener Bytes  
   DumpMem offset satzanfang, 32, "satzanfang vorher" 	
   DumpMem offset buffer, 32, "bufferanfang vorher"    
      mov   edi,offset satzrest
      dec   edi
      push  edi
      mov   ecx,satzanzahl
;--------------
; schleife tobuffer wandelt binaere satznr nach ASCII, fuegt die ASDCII-satznr
; in den satz ein und kopiert den satz in den ausgabedateipuffer buffer
;-------------- 	  
tobuffer:
      mov   ax,satznr	  
      push  ecx                 ;schleifenzaehler fuer tobuffer  
      int2asc                    
      pop   ecx 
      mov   ax,satznr           ;lfd. Satznummer         
      pop   edi                 ;Zieladresse fuer satz wieder herstellen		 

      push  edi	                ;adresse satzzaehler
      push  ecx	                ;schleifenzaehler fuer tobuffer
;--------------
; ermittelt zieladresse innerhalb des ausgabedateipuffers aus
; bufferanfang + (satznr - 1) * satzlaenge
;--------------	  
      xor   eax,eax
      mov   ax,satznr			;lfd satznr
      dec   ax
      mov   bl,satzlaenge
      mul   bl					;abstand ab anfang buffer nun in eax
      mov   edi,offset buffer
      add   edi,eax
      mov   esi,offset satzanfang 	
      mov	ecx,satzlaenge
      rep   movsb

comment # -------------	  
 uebertraegt satz in satzlaenge zum buffer 	  
 die auskommentiere befehlsfolge leistet das gleiche wie obiger rep movsb 
-----------------------	  
@@:   mov   al,[esi]			
      mov   [edi],al
      inc   esi
      inc   edi
      loop   @B
-------------#
      pop   ecx                ;schleifenzaehler fuer tobuffer
      pop   edi                ;zieladresse fuer satz wieder herstellen
	  
      push  edi				   ;zieladresse sichern
      mov	ax,satznr          ;lfd satznr vorsetzen
      inc	ax                    
      mov	satznr,ax
      loop	tobuffer

      pop   edi                ;zieladresse fuer satz wieder herstellen
	  
  DumpMem offset satzanfang, 32, "satzanfang nachher"
  DumpMem offset buffer,32, "erster Satz im buffer"    
  DumpMem offset buffer+2450,32, "letzter Satz im buffer"     
;	  print offset satzanfang,13,10,0   ;nur zum Testen!        

; -----------------
; ausgabedatei erzeugen, buffer schreiben, Datei schließen und fertig
; -----------------
   .if rv(exist,ADDR fname) != 0       ;if file already exists
      test fdelete(ADDR fname), eax  ;delete it
   .endif
    
      mov    hFile, fcreate(ADDR fname) ;create the file
      mov    rval, fwrite(hFile, ADDR buffer,lbuffer)
      fclose hFile
	  
      print  "Die Ausgabedatei ist ",0
      print  offset fname
      print  offset crlf
      mov    eax,TRUE
      ret
Main ENDP
     end start
Erzeugen der anfänglichen Direktzugriffsdatei: Hinweise zu den Dateizugriffen
Vor dem Öffnen der Ausgabedatei wird mittels .if rv(exist,ADDR fname) != 0> überprüft, ob es bereits eine solche Datei gibt. Falls ja, wird diese gelöscht.

Anschließend wird die Ausgabedatei kreiert. Der im Dateiausgabepuffer angelegte vollständige Dateiinhalt wird mit einem einzigen Schreibbefehl in die Datei geschrieben. Die Datei wird dann mit fclose hFile geschlossen.

Zum Abschluss wird der Dateiname am Bildschirm angezeigt.


Bild: Die anfängliche DirektzugriffsdateiBild rechts:
Natürlich möchte man prüfen, ob es geklappt hat. Wie ersichtlich, hat die Datei die erwartete Länge von 2500 Bytes. Der Notepad-Editor zeigt, dass die Daten keine Codierung für den Zeilenwechsel enthalten. Beim ersten Datensatz wurde in ASCII-Codierung der Vermerk 0001. Datensatz eingetragen. Bei den nachfolgenden Datensätzen wurde die Satznummer bis einschließlich 0050 hochgezählt.
Letztes Upload: 22.02.2019 um 19:37:04