Jak wiemy, wszystkich danych nie zmieścimy w pamięci. A nawet jeśli zmieścimy, to pozostaną tam tylko do najbliższego wyłączenia prądu. Dlatego trzeba je zapisywać do pliku, a potem umieć je z tego pliku odczytać. W tej części zajmiemy się właśnie operacjami na plikach.
Do operowania na plikach posłużymy się kilkoma funkcjami przerwania 21h:
W rejestrze CX podajemy atrybuty
pliku (ustawiony bit 0 oznacza plik tylko do odczytu, bit 1 - czy plik ma być ukryty,
bit 2 - plik systemowy, 3 - etykieta woluminu, 4 - zawsze zero, 5 - plik archiwalny),
a DS:DX wskazuje na nazwę pliku.
UWAGA: Nazwa musi być zakończona bajtem zerowym
Niewypełnienie powyższego warunku jest przyczyną wielu błędów w programach.
W rejestrze AX otrzymujemy uchwyt do pliku
(file handle)
- specjalną wartość przydzielaną nam przy otwieraniu pliku.
W rejestrze AL podajemy tryb dostępu,
DS:DX wskazuje na nazwę pliku.
Tryby dostępu określa się następującymi bitami w AL (pomijam nieistotne informacje,
całość możecie znaleźć w RBIL):
Bit Opis 2-0 tryb dostępu 000 tylko do odczytu 001 tylko do zapisu 010 odczyt i zapis 3 zarezerwowany, musi być 0 6-4 tryb współdzielenia (DOS 3.0+) 000 tryb zgodności 001 DENYALL - zabroń innym odczytu i zapisu 010 DENYWRITE - zabroń innym zapisu 011 DENYREAD - zabroń innym odczytu 100 DENYNONE - niczego nie zabraniaj 7 dziedziczenie Jeśli ten bit jest ustawiony, plik jest prywatny dla bieżącego procesu i nie będzie dziedziczony przez procesy potomne
UWAGA: Nazwa musi być zakończona bajtem zerowym
W rejestrze AX otrzymujemy uchwyt do pliku
(file handle)
- specjalną wartość przydzielaną nam przy otwieraniu pliku.
W rejestrze BX podajemy uchwyt do pliku.
W rejestrze BX podajemy uchwyt do pliku, w CX - liczba bajtów do odczytania, DS:DX wskazuje na miejsce, dokąd będziemy zapisywać.
W rejestrze BX podajemy uchwyt do pliku, w CX - liczba bajtów do zapisania, DS:DX wskazuje na miejsce, z którego będziemy czytać dane do zapisania.
Rejestr AL mówi DOSowi, skąd wyruszamy: 0 - z początku pliku, 1 - z bieżącej pozycji, 2 - z końca pliku. BX = uchwyt pliku, CX : DX - odległość, o którą się przesuwamy (może być ujemna).
DS:DX wskazuje na nazwę pliku.
UWAGA: Nazwa musi być zakończona bajtem zerowym
Wszystkie te funkcje ustawiają flagę carry (CF=1), gdy wystąpił jakiś błąd.
Po szczegóły (w tym kody błędów) odsyłam do
Listy Przerwań Ralfa Brown'a.
Przykładowe użycie tych funkcji:
mov ah, 3ch ; numer funkcji - utworzenie mov dx, nazwa ; adres nazwy pliku xor cx, cx ; atrybuty. Zero oznacza normalny plik. int 21h ; utworzenie pliku jc blad ; sprawdzamy, czy nie ma błędu. mov [uchwyt], ax mov bx, ax ; zapisujemy uchwyt mov ah, 40h ; numer funkcji - zapis ; BX = uchwyt do pliku mov cx, 1024 ; liczba bajtów do zapisania mov dx, bufor ; adres bufora, z którego bierzemy bajty int 21h ; zapis do pliku jc blad ; sprawdzamy, czy nie ma błędu. mov ah, 3eh ; numer funkcji - zamknięcie pliku ; BX = uchwyt do pliku int 21h ; zamykamy plik jc blad ; sprawdzamy, czy nie ma błędu.
Otwarcie istniejącego pliku, odczytanie i zapisanie czegoś do niego:
mov ah, 3dh ; numer funkcji - otwieranie pliku mov al, 00010010b ; wyłączny dostęp do odczytu i zapisu mov dx, nazwa ; adres nazwy pliku int 21h ; utworzenie pliku jc blad ; sprawdzamy, czy nie ma błędu. mov [uchwyt], ax mov bx, ax ; zapisujemy uchwyt mov ah, 3fh ; numer funkcji - odczyt ; BX = uchwyt do pliku mov cx, 1024 ; liczba bajtów do odczytania mov dx, bufor ; adres bufora, do którego czytamy int 21h ; czytamy z pliku jc blad ; sprawdzamy, czy nie ma błędu. ; .... operacje na bajtach z pliku, na przykład xor byte [bufor], 0ffh mov ah, 40h ; numer funkcji - zapis ; BX = uchwyt do pliku mov cx, 1024 ; liczba bajtów do zapisania mov dx, bufor ; adres bufora, z którego bierzemy bajty int 21h ; zapis do pliku jc blad ; sprawdzamy, czy nie ma błędu. ; Zauważcie, że zapisane bajty wylądowały po odczytanych, gdyż nie ; zmieniliśmy pozycji w pliku, a ostatnia operacja (odczyt) zostawiła ; ją tuż po odczytanych bajtach mov ah, 3eh ; numer funkcji - zamknięcie pliku ; BX = uchwyt do pliku int 21h ; zamykamy plik jc blad ; sprawdzamy, czy nie ma błędu.
A teraz prawdziwy
przykład.
Będzie to nieco uszczuplona (pominąłem wczytywanie nazwy pliku)
wersja mojego programu na_male.asm
. Program ten zamienia wszystkie wielkie litery w podanym
pliku na ich małe odpowiedniki. Reszta znaków pozostaje bez zmian. Jedna rzecz jest warta
uwagi - nigdzie nie zmieniam rejestru BX, więc ciągle w nim jest uchwyt do pliku i nie muszę
tego uchwytu zapisywać do pamięci.
A teraz kod:
; Program zamienia wszystkie litery w podanym pliku z wielkich na male. ; ; Autor: Bogdan D. ; kontakt: bogdandr (at) op (dot) pl ; ; nasm -O999 -o na_male.com -f bin na_male.asm ; fasm na_male.asm na_male.com org 100h start: mov dx, info mov ah, 9 int 21h mov ax, 3d02h ; otwórz do odczytu i zapisu, ; zabroń wszystkim dostępu mov dx, plik ; adres nazwy pliku int 21h jnc otw_ok call plik_blad ; uruchamiamy tę procedurę, gdy wystąpił błąd jmp zamk_ok ; jeśli nie udało się nam nawet otworzyć ; pliku, to od razu wychodzimy z programu. otw_ok: mov bx, ax ; zapisujemy uchwyt do pliku mov bp, 400h ; BP = rozmiar bufora. czytaj: mov ah, 3fh ; funkcja czytania ; BX = uchwyt mov dx, bufor ; adres bufora, dokąd czytamy mov cx, bp ; kilobajt - rozmiar bufora int 21h ; odczyt jnc czyt_ok call plik_blad ; uruchamiamy tę procedurę, gdy wystąpił błąd czyt_ok: xor di, di ; DI będzie wskaźnikiem do bufora. ; Na początku go zerujemy. cmp ax, cx ; Czy liczba bajtów odczytana (AX) = ; = liczba żądana (CX) ? jne przy_eof ; jeśli nie, to plik się skończył zamiana: mov dl, [bufor+di] ; wczytujemy znak z bufora do DL cmp dl, "A" jb znak_ok cmp dl, "Z" ja znak_ok or dl, 20h ; jeśli okazał się wielką literą, zamieniamy ; go na małą mov [bufor+di], dl ; i zapisujemy w miejsce, ; gdzie poprzednio był znak_ok: inc di ; przechodzimy do innych znaków loop zamiana ; aż przejdziemy przez cały bufor ; (CX=BP=400h) mov dx, ax ; DX = liczba przeczytanych bajtów mov ax, 4201h ; idź do ... od pozycji bieżącej. ; aby zapisać zmienione litery, ; musimy przejść ; się w pliku o 1 kilobajt wstecz. ; Do CX:DX wpisujemy odległość neg dx ; DX = -DX ; dec cx ; CX po wyjściu z pętli jest zerem, ; więc wykonanie DEC zrobi z niego -1. mov cx, 0ffffh ; CX = -1 ; CX:DX = -DX = -liczba przeczytanych bajtów ; BX = uchwyt int 21h ; wykonujemy przeskok w pliku jnc idz_ok call plik_blad idz_ok: ; po udanym przeskoku mov dx, bufor ; DX = adres bufora, skąd będziemy brać dane ; do zapisania ; BX = uchwyt mov ah, 40h ; funkcja zapisz mov cx, bp ; CX = BP = 400h = długość bufora. int 21h ; zapisujemy jmp short czytaj ; i idziemy czytać nową partię danych. przy_eof: ; gdy jesteśmy już przy końcu pliku. ; xor di, di ; DI już = 0 (wcześniej to zrobiliśmy) mov bp, ax ; BP = liczba przeczytanych znaków mov cx, ax ; CX = liczba przeczytanych znaków zamiana2: mov dl, [bufor+di] ; pobieramy znak z bufora do DL cmp dl, "A" jb znak_ok2 cmp dl, "Z" ja znak_ok2 or dl, 20h ; jeśli okazał się wielką literą, ; zamieniamy go na małą mov [bufor+di], dl ; i zapisujemy w miejsce, ; gdzie poprzednio był znak_ok2: inc di ; przechodzimy do innych znaków loop zamiana2 ; aż przejdziemy przez cały bufor ; (CX = BP = liczba bajtów) mov dx, bp ; dec cx ; CX po wyjściu z pętli jest zerem, więc ; wykonanie DEC zrobi z niego -1. mov cx, 0ffffh ; CX = -1 ; CX:DX = -DX mov ax, 4201h ; idź do ... od pozycji bieżącej. neg dx ; DX = -DX. ; CX:DX = -DX = -liczba przeczytanych bajtów ; BX = uchwyt int 21h ; wykonujemy przeskok w pliku jnc idz_ok2 call plik_blad idz_ok2: ; po udanym przeskoku mov dx, bufor ; zapiszemy do pliku resztę danych. ; DX = adres bufora. ; BX = uchwyt mov cx, bp ; CX = liczba bajtów uprzednio odczytanych mov ah, 40h ; funkcja zapisu do pliku int 21h ; zapisujemy jnc zamk ; gdy nie ma błędu, to zamkniemy plik call plik_blad zamk: mov ah, 3eh ; BX = uchwyt int 21h ; zamykamy nasz plik jnc zamk_ok call plik_blad zamk_ok: mov ax, 4c00h int 21h ; wyjście... plik_blad: ; procedura wyświetla informację o tym, że ; wystąpił błąd i wypisuje numer tego błędu push ax push bx mov dx, blad_plik mov bx, ax mov ah, 9 int 21h mov ax, bx call pl pop bx pop ax ret pl: ; procedura wypisuje liczbę (4 znaki szesnastkowe) mov bx, ax shr ax, 12 call pc2 mov al, bh and al, 0fh call pc2 mov al, bl shr al, 4 and al, 0fh call pc2 mov al, bl and al, 0fh call pc2 ret pc2: ;we: AL - cyfra hex ;wy: wyświetla cyfrę, niszczone ax cmp al, 9 mov ah, 0eh ja hex or al, "0" jmp short pz hex: add al, "A"-10 pz: int 10h ret bufor: times 400h db 0 ; bufor wielkości jednego kilobajta ;plik: times 80 db 0 plik db "aaa.txt",0 ; nazwa pliku info db "Program zamienia wielkie litery w pliku na male.",10,13,"$" input1 db "Podaj nazwe pliku do przetworzenia: $" zla_nazwa db 10,13,"Zla nazwa pliku.$" blad_plik db 10,13,"Blad operacji na pliku. Kod: $"
Ten program chyba nie był za trudny, prawda? Cała treść skupia się na odczytaniu paczki bajtów, ewentualnej ich podmianie i zapisaniu ich w to samo miejsce, gdzie były wcześniej.
Pliki są podstawowym sposobem przechowywania danych. Myślę więc, że się ze mną zgodzicie, iż opanowanie ich obsługi jest ważne i nie jest to aż tak trudne, jakby się mogło wydawać.
00 00 00 01 00 02 00 03 00 04 .... 00 FD 00 FE 00 FFczyli każdy oddzielony bajtem zerowym (należy przeczytać wszystkie bajty, po czym ręcznie je przenieść gdzie indziej i
wzbogacić)