Często w programach zachodzi potrzeba konwersji zmiennych tekstowych na liczby. Dlatego też odpowiednie stosowanie procedur pozwalających na zamianę ciągu znaków na liczbę i na odwrót jest ważnym aspektem w programowaniu w Windows.
Tworzymy program sumujący 2 liczby podane w 2 kontrolkach typu EDIT i wyświetlający wynik w 3 kontrolce. Na początku definiujemy standardowo typ instrukcji procesora, określamy model pamięci jako płaski z wywołaniem procedur typu STDCALL. Dołączamy także odpowiednie biblioteki: user32.lib i kernel32.lib:
.586 ;typ instrukcji procesora
.MODEL
FLAT,STDCALL
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
Następnie definiujemy stałe określające kontrolki wykorzystywane w programie:
IDC_EDT1 EQU 100
IDC_EDT2 EQU 101
IDC_EDT3 EQU 103
IDC_BTN1 EQU 106
Następnie definiujemy wszystkie stałe potrzebne do poprawnego funkcjonowania programu:
WM_SETICON EQU 80h
WM_COMMAND EQU 111h
WM_SETTEXT EQU 0Ch
WM_GETTEXT EQU 0Dh
WM_CLOSE EQU 10h
WM_INITDIALOG EQU 110h
Funkcje Api, z których zamierzamy skorzystać:
EXTERN lstrlenA@4:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN SendDlgItemMessageA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN LoadIconA@8:NEAR
Sekcja danych programu wygląda następująco:
.DATA
HINST DD ?
BUFF DB 100 DUP (0)
BUFF2 DB 100 DUP (0)
PA DB 'IDD_DLG1', 0 ;nazwa dialogu
DLG DD 0
LICZBA1 DD 0
BUFF to bufor przechowujący cyfry wprowadzonej liczby w postaci kolejnych znaków. BUFF2 to cyfry liczby którą otrzymamy w wyniku. DLG to długość liczby, biorąc pod uwagę ilość znaków. LICZBA1 przechowuje odczytaną liczbę z postaci znakowej. Początek sekcji kodu to standardowe uruchomienie programu korzystającego z okna dialogowego i zdefiniowanie procedury obsługi okna:
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
CMP EAX,-1
JNE KOL
KOL:
PUSH 0
CALL ExitProcess@4
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
CMP DWORD PTR [EBP + 0CH],WM_CLOSE;komunikat związany ze zniszczeniem okna
JNE OMIN
PUSH 0
PUSH DWORD PTR [EBP+ 08H]
CALL EndDialog@8
JMP FINISH
OMIN:
CMP DWORD PTR [EBP + 0CH],WM_COMMAND;komunikat związany z kontrolkami
JNE OMINCOMMAND
W sekcji obsługi zdarzeń kontrolek definiujemy reakcje na przycisk wynik, umieszczony w edytorze zasobów:
CMP WORD PTR [EBP + 10H],IDC_BTN1
JNE OMINCOMMAND
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH IDC_EDT1
PUSH DWORD PTR [EBP + 08H]
CALL SendDlgItemMessageA@20
PUSH OFFSET BUFF
CALL lstrlenA@4
MOV DLG,EAX
XOR EAX,EAX
CALL KONWERSJA
Funkcją SetDlgItemMessageA@12 pobieramy tekst z pierwszej kontrolki. Za pomocą funkcji lstrlenA@4 odczytujemy ilość znaków wprowadzonej liczby. Następnie ilość tą kopiujemy do zmiennej DLG, czyścimy rejestr EAX i wywołujemy procedurę KONWERSJA, która konwertuje podaną przez nas liczbę z postaci znakowej i dodaje do zmiennej LICZBA1 . Procedura KONWERSJA wygląda następująco:
KONWERSJA PROC
PUSHAD;wrzucamy wszystkie rejestry 32-bitowe na stos
MOV ECX,DLG ;ilość cyfr kopiujemy do ECX
MOV EBX,10; rejestr potrzebny do zamiany liczby na postać dziesiętną
MOV ESI,ECX
MOV EDI,0; rejestr informujący o liczbie mnożeń przez 10
CMP ESI,0; czy mamy jakieś cyfry jak nie to koniec
JE NIEOMIJAJ
DEC ESI
PETLADOD:
MOV EAX,DWORD PTR [BUFF + ESI] ; cyfra pobierana z bufora
AND EAX,000000FFh; zakładamy maskę na 8 najmłodszych bitów
SUB EAX,30h; z postaci znakowej przechodzimy do konkretnej cyfry odejmując 30 h
PUSH ECX ; pętla zagnieżdżona -ECX wędruje na stos
CMP EDI,0 ; jeżeli to pierwsza cyfra liczby to nie mnożymy przez 10
JE NIEMNOZ
MOV ECX,EDI
; mnożymy odpowiednią ilość razy 10
PETLAMNOZ:
MUL EBX
LOOP PETLAMNOZ
NIEMNOZ:
INC EDI
POP ECX
ADD LICZBA1,EAX; otrzymaną liczbę dodajemy do LICZBA1
JC OMINKONWERSJE
XOR EAX,EAX
DEC ESI
LOOP PETLADOD
OMINKONWERSJE:
NIEOMIJAJ:
POPAD;ściągamy wszystkie rejestry 32-bitowe ze stosu
RET
KONWERSJA ENDP
Następnie czyścimy bufor na drugą liczbę funkcją CZYSC :
CALL CZYSC
Definicja funkcji CZYSC wygląda następująco:
CZYSC PROC
PUSHAD;wrzucamy wszystkie rejestry 32-bitowe na stos
MOV ECX,100; nasz bufor zawiera 100 elementów, więc tyle trzeba wyczyścić w pętli
MOV DLG,0 ;ilość cyfr będzie na początku 0
MOV [BUFF],0 ;0 do pierwszej cyfry bufora na liczbę
MOV [BUFF2],0 ;0 do pierwszej cyfry bufora wynikowego
PETLA:
MOV [BUFF + ECX],0
MOV [BUFF2 + ECX],0
LOOP   PETLA
POPAD;ściągamy wszystkie rejestry 32-bitowe ze stosu
RET
CZYSC ENDP
Po wyczyszczeniu bufora można sczytać kolejną liczbę:
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH IDC_EDT2
PUSH DWORD PTR [EBP + 08H]
CALL SendDlgItemMessageA@20
PUSH OFFSET BUFF
CALL lstrlenA@4
MOV DLG,EAX
XOR EAX,EAX
CALL KONWERSJA
CALL CZYSC
Następnie otrzymaną liczbę wynikową znajdująca się w zmiennej LICZBA1 kopiujemy do rejestru EAX i dekonwertujmy na postać znakową funkcją DEKONWERSJA, tak aby można było ją wyświetlić w 3 kontrolce typu EDIT:
MOV EAX,LICZBA1
CALL DEKONWERSJA
Kod funkcji DEKONWERSJA wygląda następująco:
DEKONWERSJA PROC
PUSHAD;wrzucamy wszystkie rejestry 32-bitowe na stos
XOR ESI,ESI
XOR EBX,EBX
XOR EDX,EDX
MOV EBX,10;tym razem będziemy dzielić przez 10 aby otrzymać odpowiednie cyfry
DZIEL:
DIV EBX
PUSH EDX;resztę wrzucamy na stos jest to odpowiednia cyfra liczby
XOR EDX,EDX
INC ESI
CMP EAX,0;jeżeli EAX nie zawiera zera tzn. że należy dalej dzielić przez 10
JNE DZIEL
MOV ECX,ESI
XOR ESI,ESI
DOBUFORA:
POP EDX;ściągamy resztę w postaci cyfr do bufora
ADD EDX,30h; przechodzimy do postaci znakowej poprzez dodanie 30h
MOV DWORD PTR [BUFF2 + ESI],EDX; cyfra ładowana do bufora wynikowego
INC ESI
LOOP DOBUFORA
POPAD;ściągamy wszystkie rejestry 32-bitowe ze stosu
RET
DEKONWERSJA ENDP
Po dekonwersji możemy ostatecznie wyświetlić nasz wynik oraz wyczyścić zmienną LICZBA1 i bufory dla nowych wartości:
PUSH OFFSET BUFF2
PUSH 100
PUSH WM_SETTEXT
PUSH IDC_EDT3
PUSH DWORD PTR [EBP + 08H]
CALL SendDlgItemMessageA@20
CALL CZYSC
MOV LICZBA1, 0
OMINCOMMAND:
FINISH:
MOV EAX,0
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
Przykładowe wywołanie programu na Rys.1:
Całość programu przedstawiona jest na poniższym listingu:
.586 ;typ instrukcji procesora
.MODEL
FLAT,STDCALL
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
IDC_EDT1 EQU 101
IDC_EDT2 EQU 102
IDC_EDT3 EQU 103
IDC_BTN1 EQU 106
WM_SETICON EQU 80h
WM_COMMAND EQU 111h
WM_SETTEXT EQU 0Ch
WM_GETTEXT EQU 0Dh
WM_CLOSE EQU 10h
WM_INITDIALOG EQU 110h
EXTERN lstrlenA@4:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN SendDlgItemMessageA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN LoadIconA@8:NEAR
.DATA
HINST DD ?
BUFF DB 100 DUP (0)
BUFF2 DB 100 DUP (0)
PA DB 'IDD_DLG1', 0 ;nazwa dialogu
DLG DD 0
LICZBA1 DD 0
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
CMP EAX,-1
JNE KOL
KOL:
PUSH 0
CALL ExitProcess@4
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
CMP DWORD PTR [EBP + 0CH],WM_CLOSE;komunikat związany ze zniszczeniem okna
JNE OMIN
PUSH 0
PUSH DWORD PTR [EBP+ 08H]
CALL EndDialog@8
JMP FINISH
OMIN:
CMP DWORD PTR [EBP + 0CH],WM_COMMAND;komunikat związany z kontrolkami
JNE OMINCOMMAND
CMP WORD PTR [EBP + 10H],IDC_BTN1
JNE OMINCOMMAND
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH IDC_EDT1
PUSH DWORD PTR [EBP + 08H]
CALL SendDlgItemMessageA@20
PUSH OFFSET BUFF
CALL lstrlenA@4
MOV DLG,EAX
XOR EAX,EAX
CALL KONWERSJA
CALL CZYSC
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH IDC_EDT2
PUSH DWORD PTR [EBP + 08H]
CALL SendDlgItemMessageA@20
PUSH OFFSET BUFF
CALL lstrlenA@4
MOV DLG,EAX
XOR EAX,EAX
CALL KONWERSJA
CALL CZYSC
MOV EAX,LICZBA1
CALL DEKONWERSJA
PUSH OFFSET BUFF2
PUSH 100
PUSH WM_SETTEXT
PUSH IDC_EDT3
PUSH DWORD PTR [EBP + 08H]
CALL SendDlgItemMessageA@20
CALL CZYSC
MOV LICZBA1, 0
OMINCOMMAND:
FINISH:
MOV EAX,0
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
CZYSC PROC
PUSHAD;wrzucamy wszystkie rejestry 32-bitowe na stos
MOV ECX,100; nasz bufor zawiera 100 elementów, więc tyle trzeba wyczyścić w pętli
MOV DLG,0 ;ilość cyfr będzie na początku 0
MOV [BUFF],0 ;0 do pierwszej cyfry bufora na liczbę
MOV [BUFF2],0 ;0 do pierwszej cyfry bufora wynikowego
PETLA:
MOV [BUFF + ECX],0
MOV [BUFF2 + ECX],0
LOOP   PETLA
POPAD;ściągamy wszystkie rejestry 32-bitowe ze stosu
RET
CZYSC ENDP
KONWERSJA PROC
PUSHAD;wrzucamy wszystkie rejestry 32-bitowe na stos
MOV ECX,DLG ;ilość cyfr kopiujemy do ECX
MOV EBX,10; rejestr potrzebny do zamiany liczby na postać dziesiętną
MOV ESI,ECX
MOV EDI,0; rejestr informujący o liczbie mnożeń przez 10
CMP ESI,0; czy mamy jakieś cyfry jak nie to koniec
JE NIEOMIJAJ
DEC ESI
PETLADOD:
MOV EAX,DWORD PTR [BUFF + ESI]; cyfra pobierana z bufora
AND EAX,000000FFh; zakładamy maskę na 8 najmłodszych bitów
SUB EAX,30h; z postaci znakowej przechodzimy do konkretnej cyfry odejmując 30 h
PUSH ECX; pętla zagnieżdżona -ECX wędruje na stos
CMP EDI,0; jeżeli to pierwsza cyfra liczby to nie mnożymy przez 10
JE NIEMNOZ
MOV ECX,EDI
; mnożymy odpowiednią ilość razy 10
PETLAMNOZ:
MUL EBX
LOOP PETLAMNOZ
NIEMNOZ:
INC EDI
POP ECX
ADD LICZBA1,EAX; otrzymaną liczbę dodajemy do LICZBA1
JC OMINKONWERSJE
XOR EAX,EAX
DEC ESI
LOOP PETLADOD
OMINKONWERSJE:
NIEOMIJAJ:
POPAD;ściągamy wszystkie rejestry 32-bitowe ze stosu
RET
KONWERSJA ENDP
DEKONWERSJA PROC
PUSHAD;wrzucamy wszystkie rejestry 32-bitowe na stos
XOR ESI,ESI
XOR EBX,EBX
XOR EDX,EDX
MOV EBX,10;tym razem będziemy dzielić przez 10 aby otrzymać odpowiednie cyfry
DZIEL:
DIV EBX
PUSH EDX;resztę wrzucamy na stos jest to odpowiednia cyfra liczby
XOR EDX,EDX
INC ESI
CMP EAX,0;jeżeli EAX nie zawiera zera tzn. że należy dalej dzielić przez 10
JNE DZIEL
MOV ECX,ESI
XOR ESI,ESI
DOBUFORA:
POP EDX;ściągamy resztę w postaci cyfr do bufora
ADD EDX,30h; przechodzimy do postaci znakowej poprzez dodanie 30h
MOV DWORD PTR [BUFF2 + ESI],EDX; cyfra ładowana do bufora wynikowego
INC ESI
LOOP DOBUFORA
POPAD;ściągamy wszystkie rejestry 32-bitowe ze stosu
RET
DEKONWERSJA ENDP
END Start
Plik zasobów programu wygląda następująco:
#include "..\include\resource.h"
#define IDC_EDT1 101
#define IDC_EDT2 102
#define IDC_EDT3 103
#define IDC_STC1 104
#define IDC_STC2 105
#define IDC_BTN1 106
IDD_DLG1 DIALOGEX 10,10,249,166
CAPTION "Dodawanie"
FONT 8,"MS Sans Serif", 0, 0, 0
STYLE WS_VISIBLE|WS_OVERLAPPEDWINDOW
BEGIN
CONTROL "",IDC_EDT1,"Edit",WS_CHILD|WS_VISIBLE|WS_TABSTOP,30,51,54,15,WS_EX_CLIENTEDGE
CONTROL "",IDC_EDT2,"Edit",WS_CHILD|WS_VISIBLE|WS_TABSTOP,108,51,54,15,WS_EX_CLIENTEDGE
CONTROL "",IDC_EDT3,"Edit",WS_CHILD|WS_VISIBLE|WS_TABSTOP,183,51,54,15,WS_EX_CLIENTEDGE
CONTROL "=",IDC_STC1,"Static",WS_CHILD|WS_VISIBLE,168,54,12,9
CONTROL "+",IDC_STC2,"Static",WS_CHILD|WS_VISIBLE,93,54,12,9
CONTROL "Wynik",IDC_BTN1,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP,183,69,54,15
END
Należy zaprogramować kalkulator, o wyglądzie przedstawionym na rysunku poniżej - Rys.2. Kalkulator ma realizować podstawowe działania arytmetyczne: dodawanie, odejmowanie, mnożenie i dzielenie na liczbach dziesiętnych, naturalnych mieszczących się na 32-bitach. W przypadku liczby większej niż 32 bitowa, bądź mniejszej od zera kalkulator ma wyświetlić w polu edycyjnym informacje o zbyt dużej bądź zbyt małej podanej/otrzymanej liczbie. Kalkulator ma mieć możliwość realizacji sekwencji kilku działań pod rząd np. wciśnięcie sekwencji klawiszy 2, +, 5, +, 6 ,*, 7,= ma dać w wyniku liczbę 49. Pole edycyjne powinno być wypełniane od prawej strony tak jak przedstawione jest to na rysunku poniżej i zabezpieczone przed wpisywaniem przez użytkownika czegokolwiek. Pole służy jedynie w celu wyświetlania odpowiednich operacji związanych z przyciskami kalkulatora. Po wciśnięciu przycisku C "pamięć" kalkulatora jest kasowana. Należy także zabezpieczyć kalkulator przed dzieleniem przez 0.