Powrót do strony wyboru lekcji

Programowanie konsoli 1

Parametry uruchomieniowe + podstawowe operacje na plikach



W przypadku aplikacji, które powinny być przede wszystkim użyteczne, np. w przypadku przetwarzania dużej ilości danych, bądź aplikacji o małej interakcji z użytkownikiem, której głównym zadaniem jest wykonanie określonego zadania po uruchomieniu bez zbytniej ingerencji użytkownika w jego wykonanie, aplikacja konsolowa będzie odpowiednim rozwiązaniem. Jako prosty przykład aplikacji konsolowej, przedstawiony zostanie program zapisujący dane do pliku. Aplikacje konsolowe bardzo dobrze nadają się do demonstracji działania na plikach, dlatego też podstawowe operacje na nich przeprowadzane zostaną również tutaj omówione.

Przykład

Tworzymy program, który po otrzymaniu w parametrach nazwy pliku i ciągu znaków, zapisuje ciąg znaków z drugiego parametru do pliku o nazwie w pierwszym parametrze.

W pierwszym kroku określamy typ instrukcji procesora, definiujemy model pamięci jako jeden segment o maksymalnej wielkości 4GB - .Model FLAT i określamy sposób wywołania procedur jako STADCALL. Dołączamy także plik masm32rt.inc:


.586 ;typ instrukcji procesora

.MODEL FLAT,STDCALL

include \masm32\include\masm32rt.inc

Następnie definiujemy wszystkie stałe potrzebne do poprawnego działania programu:


STD_OUTPUT_HANDLE          EQU -11

INVALID_HANDLE_VALUE       EQU -1

GENERIC_WRITE              EQU 40000000h

OPEN_ALWAYS                EQU 4

FILE_ATTRIBUTE_ARCHIVE     EQU 20h

STD_OUTPUT_HANDLE oznacza standardowe wyjście konsoli. INVALID_HANDLE_VALUE definiuje stałą zwracaną w wyniku nieprawidłowego utworzenia pliku. GENERIC_WRITE to stała określająca plik do zapisu. OPEN_ALWAYS oznacza stałą, która wykorzystywana jest również przy tworzeniu pliku. Jeżeli nie istnieje plik o podanej nazwie tworzony jest on na nowo, jeśli jednak istnieje otwierany jest istniejący. FILE_ATTRIBUTE_ARCHIVE określa standardowe atrybuty pliku. Wszystkie stałe wykorzystywane w programie można znaleźć w pliku windows.inc, w katalogu include. Niezbędne w tworzeniu programów jest też skorzystanie z MSDN Library dostępnego tutaj, gdzie znajduje się opis wszystkich wykorzystywanych stałych i funkcji.

Funkcje API, jakie użyjemy w naszym programie:


EXTERN AllocConsole@0:NEAR

EXTERN FreeConsole@0:NEAR

EXTERN GetStdHandle@4:NEAR

EXTERN ExitProcess@4:NEAR

EXTERN GetCommandLineW@0:NEAR

EXTERN CommandLineToArgvW@8:NEAR

EXTERN WriteConsoleA@20:NEAR

EXTERN lstrlenA@4:NEAR

EXTERN lstrlenW@4:NEAR

EXTERN CreateFileW@28:NEAR

EXTERN CloseHandle@4:NEAR

EXTERN WriteFile@20:NEAR

EXTERN CharToOemA@8:NEAR

Funkcja AllocConsole@0 przydziela programowi okno konsoli. Analogicznie funkcja FreeConsole@0 zwalnia okno konsoli. GetStdHandle@4 powoduje pobranie uchwytu w zależności od podanego parametru. W naszym wypadku będzie to standardowe wyjście na konsolę - STD_OUTPUT_HANDLE. ExitProcess@4 wywołuje zakończenie procesu aplikacji i oddanie sterowania do systemu. GetCommandLineW@0 zwraca wskaźnik na linię poleceń. Następnie funkcja CommandLineToArgvW@8 wykorzystuje ten wskaźnik aby zwrócić parametry w postaci zmiennych: ARGV i ARGC znanych z języka C. Zmienna ARGV zawiera odpowiednie parametry a ARGC określa ich ilość. WriteConsoleA@20 powoduje wypisanie w konsoli ciągu znaków pobranych w kodowaniu ASCII, świadczy o tym litera A dodana na końcu funkcji. Natomiast funkcje z dodaną literą W operują na ciągach znaków w kodowaniu UNICODE. lstrlenA@4 i lstrlenW@4 zwracają długość podanego ciągu znaków. Pierwsza podaje długość w bajtach, druga w jednostce kodowania UNICODE (2 bajty). Kolejne 3 funkcje związane są z obsługą plików. Pierwsza - CreateFileW@28 tworzy bądź otwiera plik o podanych parametrach w postaci argumentów. Dokładniejszy opis przestawiony jest poniżej. Nazwa pliku przekazywana do funkcji CreateFileW@28 jest kodowana w UNICODE. CloseHandle@4 zamyka plik o uchwycie utworzonym podczas wywołania funkcji CreateFileW@28.WriteFile@20 powoduje zapisanie bajtów do pliku. Jej dokładniejszy opis znajduje się również w dalszej części przykładu. Ostatnia funkcja CharToOemA@8 służy zmianie kodowania ciągu znaków na kodowanie OEM charakterystycznego dla MS-DOS.

Niezainicjalizowane dane w naszym programie wyglądają następująco:


.DATA?
WriteBytes     DD   ? ;ilość zapisanych bajtów przez funkcję WriteFile
HANDL          DWORD   ? ;uchwyt konsoli
HFILEZ         DD   ? ;uchwyt pliku do zapisu
ARGC           DD   ? ;ilość parametrów podanych przy uruchomieniu
ARGV           DD   ? ;paramtery podane podczas uruchomienia programu
LENS           DD   ? ;parametr dla funkcji WriteConsole-ilość wyświetlonych znaków

Dane jakie użyjemy w programie:


.DATA
NazwaPliku    DW    300  DUP (0);bufor na nazwę pliku(1 znak to 2 bajty-UNICODE)
STR1          DB   "Nieprawidłowa ilość parametrów. ",10,13,0 ;komunikaty
STR2          DB   "Błąd tworzenia pliku.",10,13,0

Po określeniu danych rozpoczynamy sekcję kodu naszego programu. Na początku przydzielamy programowi konsolę, pobieramy uchwyt konsoli, ustawionej na standardowe wyjście i zapisujemy nasz uchwyt w zmiennej HANDL:


.CODE

START:

CALL  AllocConsole@0
PUSH  STD_OUTPUT_HANDLE
CALL  GetStdHandle@4
MOV   HANDL,EAX

Następnie pobieramy wskaźnik na linię poleceń funkcją GetCommandLineW@0 i przekazujemy go jako parametr funkcji CommandLineToArgvW@8, który tworzy nam zmienne ARGC i ARGV. Jako parametr funkcji CommandLineToArgvW@8 podajemy również OFFSET ARGC. Parametry otrzymane w rejestrze EAX kopiujemy do zmiennej ARGV:


CALL  GetCommandLineW@0

PUSH  OFFSET ARGC
PUSH  EAX
CALL  CommandLineToArgvW@8

MOV  ARGV,EAX

Po pobraniu parametrów, sprawdzamy czy użytkownik podał poprawną ich ilość - kopiując do rejestru EBX wartość 3 i porównując ją ze zmienną ARGC. Wartość ARGC powinna również zawierać wartość 3, gdyż funkcja CommandLineToArgvW@8 traktuje nazwę programu jako parametr, stąd dwa parametry i nazwa programu dają w sumie trzy. W przypadku nieprawidłowej ilości podanych parametrów informujemy o tym użytkownika, wykonując skok do odpowiedniej sekcji z komunikatem:


MOV  EBX,3
CMP  ARGC,EBX
JNE  ZLEPARAMETRY

Wyodrębniamy parametr nr.1 naszego programu, przechodząc o 4 bajty do przodu w strukturze ARGV, następnie tworzymy plik o nazwie z pobranego parametru, wykorzystując w tym celu funkcję CreateFileW. Uchwyt pliku kopiujemy do zmiennej HFILEZ:


MOV  EDI,ARGV
ADD  EDI,4
MOV  EAX,[EDI]

PUSH  0                        ;dodatkowe flagi,
PUSH  FILE_ATTRIBUTE_ARCHIVE   ;atrybuty
PUSH  OPEN_ALWAYS              ;tryb tworzenia pliku
PUSH  0                        ;atrybuty bezpieczeństwa
PUSH  0                        ;sposób współdzielenia pliku
PUSH  GENERIC_WRITE            ;tryb otwarcia
PUSH  EAX                      ;nazwa pliku
CALL  CreateFileW@28

MOV  HFILEZ,EAX

Porównujemy rejestr EAX z wartością INVALID_HANDLE_VALUE. Jeżeli rejestr EAX zawiera taką samą wartość, oznacza to iż nie udało się utworzyć pliku i generowany jest komunikat o nieudanym otwarciu pliku. Program kończy działanie poprzez skok JMP KONCZ:


CMP EAX,INVALID_HANDLE_VALUE
JNE PLIKOK

PUSH  OFFSET STR2
PUSH  OFFSET STR2
CALL CharToOemA@8

MOV EBX,OFFSET STR2
PUSH EBX
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH EBX
PUSH HANDL
CALL WriteConsoleA@20
JMP KONCZ

Funkcja CharToOemA@8 pozwala na odpowiednią konwersję łańcucha tekstowego tak aby zawierał polskie litery. Po konwersji podajemy OFFSET łańcucha tekstowego jako parametr funkcji zliczającej ilość znaków w napisie - lstrlenA@4. Następnie zarówno długość jak i sam napis przekazywany jest do funkcji WriteConsoleA@20 w celu wyświetlenia go w oknie konsoli. Kolejne parametry funkcji WriteConsoleA to: uchwyt okna konsoli, wskaźnik na zmienną z tekstem, długość tekstu, wskaźnik na zmienną do której funkcja zapisuje ilość wyświetlonych znaków (LENS). Ostatni parametr wynosi zero, gdyż jest zarezerwowany.

W przypadku gdy plik został jednak poprawnie utworzony, powyższa sekcja jest pomijana i wykonywane są instrukcje:


PLIKOK:
MOV  EDI,ARGV
ADD  EDI,8
MOV  EBX,[EDI]

PUSH EBX
CALL lstrlenW@4

XOR  ESI,ESI
MOV  ESI,2
MUL ESI

PUSH 0
PUSH OFFSET WriteBytes
PUSH EAX
PUSH EBX
PUSH HFILEZ
CALL WriteFile@20

Tym razem wyodrębniamy z łańcucha ARGV, wskaźnik na parametr nr 2, przesuwając się o 8 bajtów do przodu biorąc pod uwagę początek łańcucha - ADD EDI,8. Wskaźnik na parametr kopiujemy do rejestru EBX. Następnie korzystamy z funkcji zliczającej ilość znaków w napisie - lstrlenW@. Funkcja lstrlenW@ zwraca długość napisu w ilości pojedynczych znaków w kodowaniu UNICODE. Do kolejnej funkcji - WriteFile@20, chcemy jednak przekazać wielkość naszego napisu w bajtach, stąd mnożymy wartość otrzymaną w rejestrze EAX przez rejestr EDI. Następnie długość napisu jak i wskaźnik na napis przekazywany jest do funkcji WriteFile@20, która zapisuje nam przekazany łańcuch tekstowy do pliku. Kolejne parametry funkcji WriteFile@20 to uchwyt okna konsoli, wskaźnik na zmienną z danymi do zapisania w pliku, ilość bajtów jakie zajmuje zapisywany tekst i wskaźnik na zmienną do której funkcja zapisuje ilość zapisanych bajtów (WriteBytes).

Po zapisaniu określonej ilości bajtów do pliku, zamykamy go funkcją CloseHandle@4:


PUSH HFILEZ
CALL CloseHandle@4

JMP KONCZ

Sekcja w której wyświetlamy informację o nieprawidłowej ilości parametrów, przedstawia się następująco:


PUSH HFILEZ
CALL CloseHandle@4

JMP KONCZ

ZLEPARAMETRY:

PUSH  OFFSET STR1
PUSH  OFFSET STR1
CALL CharToOemA@8

MOV EBX,OFFSET STR1
PUSH EBX
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH EBX
PUSH HANDL
CALL WriteConsoleA@20

Jest to zwykłe wyświetlenie komunikatu. Wygląda to podobnie jak w przypadku wyświetlenia komunikatu o nieprawidłowym otwarciu pliku.
W końcowej części kodu programu zwalniamy konsolę funkcją FreeConsole@0 i kończymy proces aplikacji funkcją ExitProcess@4:


KONCZ:

CALL FreeConsole@0

PUSH 0

CALL ExitProcess@4

RET

END START


Przykładowe wywołanie gotowego programu wygląda następująco:


Nie można wyświetlić obrazu
Rys.1.Przykładowe wywołanie programu zapisującego tekst do pliku.

Całość programu przedstawiona jest na poniższym listingu:


.586 ;typ instrukcji procesora

.MODEL FLAT,STDCALL

include \masm32\include\masm32rt.inc




STD_OUTPUT_HANDLE          EQU -11

INVALID_HANDLE_VALUE       EQU -1

GENERIC_WRITE              EQU 40000000h

OPEN_ALWAYS                EQU 4

FILE_ATTRIBUTE_ARCHIVE     EQU 20h




EXTERN AllocConsole@0:NEAR

EXTERN FreeConsole@0:NEAR

EXTERN GetStdHandle@4:NEAR

EXTERN ExitProcess@4:NEAR

EXTERN GetCommandLineW@0:NEAR

EXTERN CommandLineToArgvW@8:NEAR

EXTERN WriteConsoleA@20:NEAR

EXTERN lstrlenA@4:NEAR

EXTERN lstrlenW@4:NEAR

EXTERN CreateFileW@28:NEAR

EXTERN CloseHandle@4:NEAR

EXTERN WriteFile@20:NEAR

EXTERN CharToOemA@8:NEAR


.DATA?
WriteBytes     DD   ? ;ilość zapisanych bajtów przez funkcję WriteFile
HANDL          DWORD   ? ;uchwyt konsoli
HFILEZ         DD   ? ;uchwyt pliku do zapisu
ARGC           DD   ? ;ilość parametrów podanych przy uruchomieniu
ARGV           DD   ? ;parametry podane podczas uruchomienia programu
LENS           DD   ? ;parametr dla funkcji WriteConsole-ilość wyświetlonych znaków

.DATA
NazwaPliku    DW    300  DUP (0);bufor na nazwę pliku(1 znak to 2 bajty-UNICODE)
STR1          DB   "Nieprawidłowa ilość parametrów. ",10,13,0 ;komunikaty
STR2          DB   "Błąd tworzenia pliku.",10,13,0

.CODE

START:

CALL  AllocConsole@0
PUSH  STD_OUTPUT_HANDLE
CALL  GetStdHandle@4
MOV   HANDL,EAX
CALL  GetCommandLineW@0

PUSH  OFFSET ARGC
PUSH  EAX
CALL  CommandLineToArgvW@8

MOV  ARGV,EAX

MOV  EBX,3
CMP  ARGC,EBX
JNE  ZLEPARAMETRY

MOV  EDI,ARGV
ADD  EDI,4
MOV  EAX,[EDI]

PUSH  0                        ;dodatkowe flagi,
PUSH  FILE_ATTRIBUTE_ARCHIVE   ;atrybuty
PUSH  OPEN_ALWAYS              ;tryb tworzenia pliku
PUSH  0                        ;atrybuty bezpieczeństwa
PUSH  0                        ;sposób współdzielenia pliku
PUSH  GENERIC_WRITE            ;tryb otwarcia
PUSH  EAX                      ;nazwa pliku
CALL  CreateFileW@28

MOV  HFILEZ,EAX

CMP EAX,INVALID_HANDLE_VALUE
JNE PLIKOK

PUSH  OFFSET STR2
PUSH  OFFSET STR2
CALL CharToOemA@8

MOV EBX,OFFSET STR2
PUSH EBX
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH EBX
PUSH HANDL
CALL WriteConsoleA@20
JMP KONCZ

PLIKOK: MOV  EDI,ARGV
ADD  EDI,8
MOV  EBX,[EDI]

PUSH EBX
CALL lstrlenW@4

XOR  ESI,ESI
MOV  ESI,2
MUL ESI

PUSH 0
PUSH OFFSET WriteBytes
PUSH EAX
PUSH EBX
PUSH HFILEZ
CALL WriteFile@20

JMP KONCZ

ZLEPARAMETRY:

PUSH  OFFSET STR1
PUSH  OFFSET STR1
CALL CharToOemA@8

MOV EBX,OFFSET STR1
PUSH EBX
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH EBX
PUSH HANDL
CALL WriteConsoleA@20

KONCZ:

CALL FreeConsole@0

PUSH 0

CALL ExitProcess@4

RET

END START

Pobierz kod

Ćwiczenie 1

Napisać program kopiujący podany plik n razy, gdzie ścieżka do pliku podawana jest jako pierwszy parametr przy uruchomieniu programu w konsoli, a ilość kopii to drugi parametr. Przykładowe uruchomienie programu przedstawiono na rysunku poniżej- Rys.2. Nazwy kolejnych kopii pliku podanego w parametrze pierwszym mają mieć dołączoną do nazwy liczbę porządkową określającą kolejny numer, np. wywołanie programu w podany sposób:

ćwiczenie1.exe   plik1.txt   2

,spowoduje skopiowanie pliku plik.txt 2 razy pod nazwami: plik1.txt i plik2.txt . Program ma umożliwiać maksymalnie stokrotne skopiowanie danego pliku. W przypadku podania zbyt dużej liczby w drugim parametrze programu, powinien on wygenerować stosowny komunikat. Program ma także informować o niepoprawnym otwarciu pliku i nieprawidłowo podanych parametrach.

Pobierz szablon
Nie można wyświetlić obrazu
Rys.2.Przykładowe wywołanie programu.

<Poprzednia lekcjaKolejna lekcja>