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.
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:
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
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:
,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.