Pendahuluan
Bab ini membahas program dan teknik pemrograman dengan menggunakan inline assembler dari Visual C++ Express. Inline assembler Visual C++ sebenarnya sudah dijelaskan pada bab-bab sebelumnya, tetapi masih ada fitur-fitur lain yang bisa dipelajari di sini. Beberapa teknik pemrograman yang dibahas dalam bab ini antara lain:
-
Modul bahasa rakitan (assembly language modules)
-
Manipulasi keyboard dan tampilan layar
-
Modul program dan library files
-
Penggunaan mouse
-
Penggunaan timer
-
Teknik pemrograman penting lainnya
Bab ini juga memberikan dasar pemrograman berharga agar program bisa dikembangkan dengan mudah di komputer pribadi menggunakan inline assembler, yang berfungsi sebagai landasan untuk aplikasi Visual C++ Express di Windows.
8–1 MODULAR PROGRAMMING[kembali]
Pemrograman modular merupakan teknik yang digunakan ketika sebuah program terlalu besar untuk dikerjakan oleh satu orang. Dalam pendekatan ini, program dibagi ke dalam beberapa modul yang lebih kecil sehingga dapat dikembangkan secara terpisah dan kemudian digabungkan. Proses penggabungan modul-modul tersebut dilakukan oleh sebuah program bernama linker, yang sudah tersedia dalam Visual Studio. Linker berfungsi menyatukan berbagai object file hasil kompilasi atau assembly menjadi satu program yang lengkap. Selain melalui Visual Studio, proses linking juga dapat dijalankan lewat perintah pada command prompt Windows. Dalam praktiknya, konsep pemrograman modular juga melibatkan penggunaan file library serta deklarasi EXTRN
dan PUBLIC
untuk mengatur bagaimana suatu modul dapat diakses oleh modul lain.
The Assembler and Linker
Assembler sendiri berperan mengubah kode sumber yang ditulis dalam bahasa assembly (biasanya berekstensi .ASM
) menjadi sebuah object file berbentuk heksadesimal. Assembler ini merupakan bagian dari Visual Studio, misalnya dalam folder instalasi C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin
. Sebagai contoh, sebuah file bernama NEW.ASM
akan diproses oleh assembler dan menghasilkan file object. Namun perlu diperhatikan bahwa assembler bawaan Visual C++ tidak mendukung program 16-bit DOS. Untuk kebutuhan tersebut, digunakan assembler dan linker khusus yang bisa diperoleh dari Windows Driver Development Kit (DDK). Penulisan file sumber dapat dilakukan dengan editor teks sederhana seperti Notepad, selama file tersebut disimpan sebagai ASCII text.
EXAMPLE 8–1
C:\masm611\BIN\ml new.asm
Microsoft (R) Macro Assembler Version 6.11
Copyright (C) Microsoft Corp 1981-1993. All rights reserved.
Assembling: new.asm
Microsoft (R) Segmented Executable Linker Version 5.60.220 Sep 9 1994
Copyright (C) Microsoft Corp 1984-1993. All rights reserved.
Object Modules [.obj]: new.obj
Run File [new.exe]: new.exe
List File [nul.map]: nul
Libraries [.lib]:
Definitions File [nul.def]:
Assembler (ML) digunakan untuk membaca file sumber.asm
dan menghasilkan file objek.obj
. Pada tahap ini dapat pula dibuat file listing.lst
untuk membantu analisis dan pemecahan masalah. Setelah itu, linker membaca file objek yang dihasilkan assembler dan menggabungkannya menjadi file eksekusi.exe
, yang kemudian dapat dijalankan langsung melalui command prompt. Jika ukuran program cukup kecil (kurang dari 64K), file eksekusi dapat dikonversi menjadi file perintah.com
, yang lebih sederhana dan memerlukan ruang disk lebih sedikit.
EXAMPLE 8–2
C:\masm611\BIN\ml new.asm what.asm donut.asm
Microsoft (R) Macro Assembler Version 6.11
Copyright (C) Microsoft Corp 1981-1993. All rights reserved.
Assembling: new.asm
Assembling: what.asm
Assembling: donut.asm
Microsoft (R) Segmented Executable Linker Version 5.60.220 Sep 9 1994
Copyright (C) Microsoft Corp 1984-1993. All rights reserved.
Object Modules [.obj]: new.obj+
Object Modules [.obj]: what.obj+
Object Modules [.obj]: donut.obj
Run File [new.com]: new.com
List File [nul.map]: NUL
Libraries [.lib]:
Definitions File [nul.def]:
Pada contoh ini, assembler memproses tiga file sumber yaitu NEW.ASM, WHAT.ASM, dan DONUT.ASM, lalu menghasilkan file objek masing-masing. Linker kemudian menggabungkan ketiga file objek tersebut menjadi satu file eksekusi dengan nama NEW.COM. Jika terdapat lebih dari satu file objek, file utama (misalnya NEW) harus diletakkan pertama, kemudian diikuti file pendukung lainnya. Selain itu, library tambahan dapat disertakan dengan perintah /LINK
setelah nama program utama.
EXAMPLE 8–3
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin>ml /c /Cx /coff new.asm
Microsoft (R) Macro Assembler Version 7.10.3077
Copyright (C) Microsoft Corporation. All rights reserved.
Assembling: new.asm
Contoh ini memperlihatkan cara meng-assemble file NEW.ASM tanpa langsung melakukan linking. Perintah ml /c /Cx /coff
digunakan agar assembler hanya menghasilkan file objek. Opsi /c berarti compile saja tanpa link, /Cx menjaga penulisan huruf besar/kecil pada fungsi dan variabel, sedangkan /coff menghasilkan format objek COFF (Common Object File Format) yang umum dipakai dalam lingkungan 32-bit dan kompatibel dengan Visual C++.
PUBLIC and EXTRN
Dalam pemrograman assembly yang bersifat modular, komunikasi antar modul sangat penting agar program dapat diorganisasikan dengan baik. Dua direktif yang berperan utama dalam hal ini adalah PUBLIC dan EXTRN.
-
PUBLIC digunakan untuk mendeklarasikan label-label (baik berupa kode, data, maupun segmen) agar dapat diakses oleh modul lain. Dengan kata lain, label yang didefinisikan sebagai public dapat dilihat dan digunakan oleh program eksternal.
-
EXTRN (external) berfungsi sebaliknya, yaitu menyatakan bahwa label tertentu bersumber dari luar modul saat ini. Hal ini memungkinkan satu modul untuk memanggil fungsi atau menggunakan data dari modul lain yang telah mendeklarasikan labelnya sebagai PUBLIC.
Tanpa kedua direktif ini, modul-modul tidak akan dapat saling berhubungan meskipun sudah dilink menjadi satu program. Dengan PUBLIC dan EXTRN, programmer dapat membagi program besar menjadi potongan lebih kecil (modular programming), membuatnya lebih terstruktur, mudah dikelola, dan dapat digunakan kembali.
EXAMPLE 8–4
.model flat, c
.data
public Data1 ; declare Data1 public
public Data2 ; declare Data2 public
Data1 db 100 dup(?)
Data2 db 100 dup(?)
.code
.startup
public Read ; declare Read public
Read proc far
mov ah, 6
Contoh ini menunjukkan bagaimana direktif PUBLIC digunakan untuk membuat label dapat diakses modul lain. Variabel Data1 dan Data2 dideklarasikan sebagai public pada segmen data, sementara prosedur Read dideklarasikan sebagai public pada segmen kode. Dengan cara ini, modul eksternal yang terhubung dapat menggunakan data dan prosedur tersebut.
EXAMPLE 8–5
.model flat, c
.data
extrn Data1:byte
extrn Data2:byte
extrn Data3:word
extrn Data4:dword
.code
extrn Read:far
.startup
mov dx, offset Data1
mov cx, 10
Start:
call Read
stosb
loop Start
.exit
End
Contoh ini memperlihatkan bagaimana direktif EXTRN digunakan untuk menyatakan bahwa label tertentu berasal dari luar modul. Pada bagian .data
, variabel Data1, Data2, Data3, dan Data4 didefinisikan sebagai eksternal dengan ukuran berbeda (BYTE, WORD, DWORD). Pada bagian .code
, prosedur Read juga dideklarasikan sebagai eksternal dengan tipe FAR.
Instruksi program kemudian menggunakan data eksternal tersebut, seperti memindahkan alamat Data1 ke dalam register DX, melakukan loop, dan memanggil prosedur eksternal Read. Dengan begitu, modul ini hanya bisa berjalan bila dihubungkan dengan modul lain yang sudah mendeklarasikan label-label tersebut sebagai PUBLIC (contohnya pada Example 8–4).
LIBRARIES
Library files adalah kumpulan prosedur yang digunakan oleh berbagai program. Prosedur-prosedur ini pertama kali ditulis dan kemudian dikompilasi ke dalam bentuk library file menggunakan program LIB.EXE yang menyertai MASM assembler. Library memungkinkan prosedur umum dikumpulkan dalam satu tempat sehingga dapat dipakai ulang oleh berbagai aplikasi, tanpa perlu ditulis ulang dari awal.
Dalam praktiknya, library sering digunakan saat membangun modul bahasa assembly di Visual C++ (misalnya pada pembahasan Chapter 7 sebelumnya). Banyak library bawaan sudah tersedia dan dapat langsung dipanggil oleh linker menggunakan ekstensi .LIB. File library ini akan dipanggil secara otomatis ketika program melakukan proses linking dengan perintah linker.
Alasan utama menggunakan library file adalah karena sifatnya yang menghemat waktu dan usaha. Alih-alih menyalin ulang kode prosedur ke setiap program, programmer cukup menyimpan fungsi-fungsi penting dalam sebuah library. Ketika program membutuhkan fungsi tertentu, linker hanya mengambil prosedur yang diperlukan dari library dan menambahkannya ke dalam hasil akhir program. Hal ini membuat pemrograman assembly menjadi lebih efisien, terstruktur, dan mudah dipelihara.
MEMBUAT LIBRARY ATAU PUSTAKA
Sebuah library file dapat dibuat dengan perintah LIB, yang secara internal mengeksekusi program LIB.EXE dari Visual Studio. Library file terdiri atas kumpulan file .OBJ hasil kompilasi assembler (atau bahasa lain) yang berisi prosedur atau fungsi. Dengan cara ini, sebuah fungsi dapat ditulis satu kali, dikompilasi menjadi object file, lalu dimasukkan ke dalam library agar dapat digunakan kembali oleh banyak program.
Contoh sederhana ditunjukkan dalam Example 8–6, di mana terdapat dua fungsi (UpperCase
dan LowerCase
) yang ditulis dalam sebuah modul assembly untuk kemudian dimasukkan ke library. Perlu diperhatikan bahwa setiap prosedur yang akan dimasukkan ke library harus diberi deklarasi PUBLIC, meskipun nama prosedurnya tidak harus sama dengan nama library. Selain itu, jika fungsi atau variabel eksternal dipanggil, tetap diperlukan deklarasi EXTRN pada masing-masing file.
Dengan cara ini, library file menjadi sarana yang sangat berguna untuk mengorganisasi kode, memudahkan pemanggilan ulang, serta memungkinkan integrasi langsung dengan program C++ atau bahasa tingkat tinggi lainnya.
EXAMPLE 8–6
.586
.model flat, c
.code
public UpperCase
public LowerCase
UpperCase proc ,\
Data1:byte
mov al, Data1
.if al >= 'a' && al <= 'z'
sub al, 20h
.endif
ret
UpperCase endp
LowerCase proc ,\
Data2:byte
mov al, Data2
.if al >= 'A' && al <= 'Z'
add al, 20h
.endif
ret
LowerCase endp
End
Pada contoh ini dibuat dua prosedur, yaitu UpperCase dan LowerCase, yang berfungsi untuk mengubah karakter huruf kecil menjadi huruf besar, serta huruf besar menjadi huruf kecil.
Prosedur UpperCase menerima sebuah karakter (Data1
), kemudian dicek apakah termasuk huruf kecil ('a'
sampai 'z'
). Jika ya, maka nilai ASCII dikurangi 20h
sehingga berubah menjadi huruf besar.
Prosedur LowerCase menerima karakter (Data2
) dan memeriksa apakah termasuk huruf besar ('A'
sampai 'Z'
). Jika benar, nilainya ditambah 20h
untuk menjadikannya huruf kecil.
Kedua prosedur ini dideklarasikan PUBLIC, sehingga bisa dimasukkan ke dalam library dan dipanggil oleh program lain.
EXAMPLE 8–7
protokol C++ (extern "C"
) yang diperlukan agar fungsi dalam file library bisa dipanggil dari program C++. Syaratnya: library tersebut harus di-link ke dalam program C++ tersebut.
extern "C" char UpperCase(char);
extern "C" char LowerCase(char);
Kode ini adalah deklarasi fungsi eksternal dalam C/C++.
extern "C"
digunakan agar nama fungsi tidak diubah oleh compiler C++ (name mangling), sehingga bisa dipanggil dari assembly atau library eksternal.
UpperCase(char)
akan mengubah karakter huruf kecil menjadi huruf besar.
LowerCase(char)
akan mengubah karakter huruf besar menjadi huruf kecil.
EXAMPLE 8–8
Program LIB dimulai dengan menampilkan pesan copyright dari Microsoft, lalu dilanjutkan dengan prompt Library name.
-
Nama library yang dipilih pada contoh adalah CASE.LIB.
-
Karena ini adalah file library baru, program LIB meminta nama file objek (
.OBJ
) yang akan dimasukkan ke dalam library. -
Sebelum menjalankan LIB, file assembly CASE.ASM harus terlebih dahulu di-assemble menggunakan ML (Microsoft Macro Assembler).
-
Perintah LIB yang sebenarnya dapat dilihat pada Example 8–8.
-
Perhatikan bahwa pada saat menjalankan LIB, nama object file harus dituliskan setelah nama perintah di command line.
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin>lib case.obj
Microsoft (R) Library Manager Version 7.10.3077
Copyright (C) Microsoft Corporation. All rights reserved.
Penjelasan singkat:
Perintah lib case.obj
digunakan untuk membuat file library (CASE.LIB
) dari file objek case.obj
yang sebelumnya dihasilkan oleh assembler (ml case.asm
).
Library Manager
dari Microsoft Visual Studio bertugas menggabungkan object file menjadi library yang nantinya bisa dipanggil oleh program lain.
EXAMPLE 8–9
Macros
Macro adalah sekumpulan instruksi yang menjalankan satu tugas, mirip seperti procedure yang juga hanya menjalankan satu tugas. Perbedaannya adalah procedure dipanggil menggunakan instruksi CALL
, sedangkan macro (beserta semua instruksi di dalamnya) langsung dimasukkan ke dalam program pada titik di mana macro tersebut dipanggil.
Membuat macro mirip dengan membuat opcode baru, yaitu serangkaian instruksi yang bisa digunakan kembali di program. Kamu cukup menuliskan nama macro beserta parameter-parameter yang diperlukan, lalu assembler akan menggantikannya dengan instruksi yang sesuai di dalam program.
Karena tidak ada instruksi CALL
atau RET
, eksekusi macro biasanya lebih cepat dibanding procedure. Instruksi-instruksi dalam macro akan ditempatkan langsung di dalam program oleh assembler pada lokasi di mana macro dipanggil.
Namun perlu diperhatikan: macro tidak bisa dipakai di external assembly modules, hanya bisa berfungsi di dalam inline assembler.
Macro didefinisikan dengan arahan MACRO
untuk membuka dan ENDM
untuk menutup. Baris pertama macro berisi nama macro dan parameter-parameternya
EXAMPLE 8–10
Contoh 8–10 menunjukkan bagaimana sebuah macro dibuat dan digunakan dalam sebuah program. Enam baris pertama kode mendefinisikan macro. Macro ini memindahkan isi word (16-bit) dari lokasi memori B ke lokasi memori A. Setelah macro didefinisikan dalam contoh, macro tersebut dipanggil dua kali. Macro akan diperluas (expanded) oleh assembler sehingga Anda dapat melihat bagaimana macro diubah menjadi instruksi nyata untuk menghasilkan operasi pemindahan data.
Setiap pernyataan bahasa mesin heksadesimal yang diikuti oleh angka (1, dalam contoh ini) adalah hasil ekspansi macro. Pernyataan ekspansi tidak diketik dalam program sumber; ekspansi tersebut dihasilkan oleh assembler (jika .LSTALL dimasukkan dalam program) untuk menunjukkan bahwa assembler telah menambahkan instruksi tersebut ke dalam program.
Perhatikan bahwa komentar dalam macro diawali dengan ;;
bukan ;
seperti biasanya. Macro sequence selalu harus didefinisikan sebelum digunakan dalam program, sehingga biasanya diletakkan di bagian atas segmen kode.
;===========================
; Definisi Macro
;===========================
MOVE MACRO A,B
PUSH AX
MOV AX,B
MOV A,AX
POP AX
ENDM
;===========================
; Ekspansi Macro oleh Assembler
;===========================
0000 50 PUSH AX
0001 A1 0002 R MOV AX,VAR2
0004 A3 0000 R MOV VAR1,AX
0007 58 POP AX
0008 50 PUSH AX
0009 A1 0006 R MOV AX,VAR4
000C A3 0004 R MOV VAR3,AX
000F 58 POP AX
EXAMPLE 8–11
;====================================
; Example 8-11: Local Variable in a Macro
;====================================
FILL MACRO WHERE, HOW_MANY ;; fill memory
LOCAL FILL1
PUSH SI
PUSH CX
MOV SI,OFFSET WHERE
MOV CX,HOW_MANY
MOV AL,0
FILL1: MOV [SI],AL
INC SI
LOOP FILL1
POP CX
POP SI
ENDM
;====================================
; Pemanggilan Macro
;====================================
FILL MES1,5
FILL MES2,10
;====================================
; Ekspansi Assembler
;====================================
; Ekspansi untuk FILL MES1,5
0014 56 PUSH SI
0015 51 PUSH CX
0016 BE 0000 R MOV SI,OFFSET MES1
0019 B9 0005 MOV CX,5
001C B0 00 MOV AL,0
0020 88 04 ??0000: MOV [SI],AL
0022 46 INC SI
0023 E2 FB LOOP ??0000
0025 59 POP CX
0026 5E POP SI
; Ekspansi untuk FILL MES2,10
0030 56 PUSH SI
0031 51 PUSH CX
0032 BE 0014 R MOV SI,OFFSET MES2
0035 B9 000A MOV CX,10
0038 B0 00 MOV AL,0
003A 88 04 ??0001: MOV [SI],AL
003C 46 INC SI
003D E2 FB LOOP ??0001
003F 59 POP CX
0040 5E POP SI
8–2 USING THE KEYBOARD AND VIDEO DISPLAY[kembali]
Reading the Keyboard
TABLE 8–1 The keyboard scanning and extended ASCII codes as returned from the keyboard.
FIGURE 8–1 Using the textbox with filtering.
EXAMPLE 8–12
private: System::Void Form1_Load(System::Objectˆ sender,
System::EventArgsˆ e)
{
textBox1->Focus(); // set Focus to textbox1
}
bool keyHandled;
private: System::Void textBox1_KeyDown(System::Objectˆ sender,
System::Windows::Forms::KeyEventArgsˆ e)
{ // this is called first
keyHandled = true;
if (e->KeyCode >= Keys::NumPad0 && e->KeyCode <= Keys::NumPad9 ||
e->KeyCode >= Keys::D0 && e->KeyCode <= Keys::D9 &&
e->Shift == false ||
e->KeyCode >= Keys::A && e->KeyCode <= Keys::F ||
e->KeyCode == Keys::Back)
{
keyHandled = false;
}
}
private: System::Void textBox1_KeyPress(System::Objectˆ sender,
System::Windows::Forms::KeyPressEventArgsˆ e)
{ // this is called second
if (e->KeyChar >= ‘a’ && e->KeyChar <= ‘f’)
{
e->KeyChar -= 32; // make uppercase
}
e->Handled = keyHandled;
}
EXAMPLE 8–13
// Placed at the top of the program following the uses statements
int Filter(char key)
{
int retval; // 0 = false, 1 = true
_asm
{
mov eax,1
cmp key,8 ; backspace
jb good
cmp key,30h
jb bad
cmp key,39h
jbe good
cmp key,41h
jb bad
cmp key,46h
jbe good
cmp key,61h
jb bad
cmp key,66h
jbe good
good: dec eax
bad: mov retval,eax
}
return retval;
}
private: System::Void Form1_Load(System::Objectˆ sender,
System::EventArgsˆ e)
{
textBox1->Focus();
}
bool keyHandled;
// new version of textbox1_KeyDown
private: System::Void textBox1_KeyDown(System::Objectˆ sender,
System::Windows::Forms::KeyEventArgsˆ e)
{
keyHandled = Filter(e->KeyValue);
}
private: System::Void textBox1_KeyPress(System::Objectˆ sender,
System::Windows::Forms::KeyPressEventArgsˆ e)
{
if (e->KeyChar >= ‘a’ && e->KeyChar <= ‘f’)
{
e->KeyChar -= 32;
}
e->Handled = keyHandled;
}
FIGURE 8–2 Hexadecimal to decimal conversion.
Using the Video Display
EXAMPLE 8–14
private: System::Void textBox1_KeyPress(System::Objectˆ sender,
System::Windows::Forms::KeyPressEventArgsˆ e)
{
if (e->KeyChar >= ‘a’ && e->KeyChar <= ‘f’)
{
e->KeyChar -= 32;
}
else if (e->KeyChar == 13)
{
// software to display the decimal version in textBox2
keyHandled = true;
}
e->Handled = keyHandled;
}
EXAMPLE 8–15
// placed after the using statements at the top of the program
int Filter(char key)
{
int retval;
_asm
{
mov eax,1
cmp key,8 ; backspace
je good
cmp key,30h
jb bad
cmp key,39h
jbe good
cmp key,41h
jb bad
cmp key,46h
jbe good
cmp key,61h
jb bad
cmp key,66h
jbe good
good: dec a;
bad: mov retval,eax
}
return retval;
}
int Converts(int number, short digit)
{
_asm
{
mov eax,number
shl eax,4
mov dx,digit
sub dx,30h
cmp dx,9
jbe later
sub dx,7
later: or al,dl
mov number,eax
}
return number;
}
private: System::Void Form1_Load(System::Objectˆ sender,
System::EventArgsˆ e)
{
textBox1->Focus();
}
bool keyHandled;
private: System::Void textBox1_KeyDown(System::Objectˆ sender,
System::Windows::Forms::KeyEventArgsˆ e)
{
keyHandled = Filter(e->KeyValue);
}
private: System::Void textBox1_KeyPress(System::Objectˆ sender,
System::Windows::Forms::KeyPressEventArgsˆ e)
{
if (e->KeyChar >= ‘a’ && e->KeyChar <= ‘f’)
{
e->KeyChar -= 32;
}
else if (e->KeyChar == 13)
{
int number = 0;
for (int a = 0; a < textBox1->Text->Length; a++)
{
number = Converts(number, textBox1->Text[a]);
}
textBox2->Text = Convert::ToString(number);
keyHandled = true;
}
e->Handled = keyHandled;
}
Using a Timer in a Program
FIGURE 8–3 The Shift/ Rotate application design screen.
EXAMPLE 8–16
bool shift;
int count;
private: System::Void button1_Click(System::Objectˆ sender,
System::EventArgsˆ e)
{
label1->Text = “Shifted”;
label2->Text = “00011001”;
shift = true;
count = 8;
timer1->Start();
}
private: System::Void button2_Click(System::Objectˆ sender,
System::EventArgsˆ e)
{
label1->Text = “Rotated”;
label2->Text = “00011001”;
shift = false;
count = 8;
timer1->Start();
}
private: System::Void timer1_Tick(System::Objectˆ sender,
System::EventArgsˆ e)
{
Stringˆ digit = “0”;
if (shift == false && label2->Text[0] == ‘1’)
{
digit = “1”;
}
label2->Text = label2->Text->Remove(0,1) + digit;
if (—count == 0)
{
timer1->Enabled = false;
}
}
The Mouse
TABLE 8–2 Mouse message handlers.
FIGURE 8–4 Displaying the mouse coordinates.
Aplikasi ini menampilkan koordinat mouse
pada dua label (label1 dan label2) dengan memanfaatkan event handler MouseMove
yang membaca nilai X dan Y dari properti Location, lalu mengubahnya
menjadi string menggunakan kelas Convert. Agar koordinat tetap terlacak
ketika pointer berada di atas label, ditambahkan handler MouseMove khusus untuk
label dengan penyesuaian (bias) pada nilai X dan Y. Bias ini diperoleh dari
properti Location masing-masing label sesuai posisi label di form.
EXAMPLE 8–17
private: System::Void Form1_MouseMove(System::Objectˆ sender,
System::Windows::Forms::MouseEventArgsˆ e)
{
label1->Text = “X-coordinate = ” + Convert::ToString(e->Location.X);
label2->Text = “Y-coordinate = ” + Convert::ToString(e->Location.Y);
}
EXAMPLE 8–18
private: System::Void Form1_MouseMove(System::Objectˆ sender,
System::Windows::Forms::MouseEventArgsˆ ’e)
{
label1->Text = “X-coordinate = ” + Convert::ToString(e->Location.X);
label2->Text = “Y-coordinate = ” + Convert::ToString(e->Location.Y);
}
private: System::Void label1_MouseMove(System::Objectˆ sender,
System::Windows::Forms::MouseEventArgsˆ e)
{
label1->Text = “X-coordinate = ” + Convert::ToString(e->Location.X+159);
label2->Text = “Y-coordinate = ” + Convert::ToString(e->Location.Y+232);
}
private: System::Void label2_MouseMove(System::Objectˆ sender,
System::Windows::Forms::MouseEventArgsˆ e)
{
label1->Text = “X-coordinate = ” + Convert::ToString(e->Location.X+159);
label2->Text = “Y-coordinate = ” + Convert::ToString(e->Location.Y+246);
}
EXAMPLE 8–19
private: System::Void Form1_MouseDown(System::Objectˆ sender,
System::Windows::Forms::MouseEventArgsˆ e)
{
if (e->Button == ::mouses::MouseButtons::Left)
{
label1->ForeColor = Color::Red;
label2->ForeColor = Color::Red;
}
else if (e->Button == ::mouses::MouseButtons::Right)
{
label1->ForeColor = Color::Blue;
label2->ForeColor = Color::Blue;
}
}
8–3 DATA CONVERSIONS [kembali]
Converting from Binary to ASCII
EXAMPLE 8–20
// place at top of program
// will not function in 64-bit mode
void ConvertAam(char number, char* data)
{
_asm
{
mov ebx, data ; pointer to ebx
mov al, number ; get test data
mov ah, 0 ; clear AH
aam ; convert to BCD
add ah, 20h
cmp al, 20h ; test for leading zero
je D1 ; if leading zero
add ah, 10h ; convert to ASCII
D1:
mov [ebx], ah
add al, 30h ; convert to ASCII
mov [ebx+1], al
}
}
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
char temp[2]; // place for result
ConvertAam(74, temp);
Char a = temp[0];
Char b = temp[1];
label1->Text = Convert::ToString(a) + Convert::ToString(b);
}
EXAMPLE 8–21
void Converts(int number, int radix, char* data)
{
_asm
{
mov ebx, data ; initialize pointer
push radix
mov eax, number ; get test data
L1:
mov edx, 0 ; clear edx
div radix ; divide by base
push edx ; save remainder
cmp eax, 0
jnz L1 ; repeat until 0
L2:
pop edx ; get remainder
cmp edx, radix
je L4 ; if finished
add dl, 30h ; convert to ASCII
cmp dl, 39h
jbe L3
add dl, 7
L3:
mov [ebx], dl ; save digit
inc ebx ; point to next
jmp L2 ; repeat until done
L4:
mov byte ptr [ebx], 0 ; save null in string
}
}
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
char temp[32]; // place for result
Converts(7423, 10, temp);
String^ a = "";
int count = 0;
while (temp[count] != 0) // convert to string
{
Char b = temp[count++];
a += b;
}
label1->Text = a;
}
Converting from ASCII to Binary
EXAMPLE 8–22
int ConvertAscii(char* data)
{
int number = 0;
_asm
{
mov ebx, data ; initialize pointer
mov ecx, 0
B1:
mov cl, [ebx] ; get digit
inc ebx ; address next digit
cmp cl, 0 ; if null found
je B2
sub cl, 30h ; convert from ASCII to BCD
mov eax, 10 ; multiply by 10
mul number
add eax, ecx ; add digit
mov number, eax ; save result
jmp B1
B2:
}
return number;
}
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
char temp[] = "2316"; // string
int number = ConvertAscii(temp);
label1->Text = Convert::ToString(number);
}
Displaying and Reading Hexadecimal Data
EXAMPLE 8–23
unsigned char Conv(unsigned char temp)
{
_asm
{
cmp temp, '9'
jbe Conv2 ; if 0–9
cmp temp, 'a'
jb Conv1 ; if A–F
sub temp, 20h ; to uppercase
Conv1:
sub temp, 7
Conv2:
sub temp, 30h
}
return temp;
}
private: System::UInt32 ReadH(String^ temp)
{
unsigned int numb = 0;
for (int a = 0; a < temp->Length; a++)
{
numb <<= 4; // shift 4 bits left (hex base)
numb += Conv(temp[a]); // add converted digit
}
return numb;
}
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
unsigned int temp = ReadH("2AB4");
label1->Text = Convert::ToString(temp); // display in decimal
}
EXAMPLE 8–24
void Disph(unsigned int number, unsigned int size, char* temp)
{
int a;
number < i <<= (8 - size) * 4; // adjust position
for (a = 0; a < size; a++)
{
char temp1;
_asm
{
rol number, 4
mov al, byte ptr number
and al, 0fh ; make 0 – f
add al, 30h ; convert to ASCII
cmp al, 39h
jbe Disph1
add al, 7
Disph1:
mov temp1, al
}
temp[a] = temp1; // add digit to string
}
temp[a] = 0; // null terminator
}
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
char temp[9];
Disph(1000, 4, temp);
String^ a = "";
int count = 0;
while (temp[count] != 0) // convert to string
{
Char b = temp[count++];
a += b;
}
label1->Text = a;
}
Using Lookup Tables for Data Conversions
EXAMPLE 8–25
Lookup
table sering dipakai
untuk konversi data, yaitu dengan menyimpan daftar nilai di memori yang bisa
diakses prosedur saat melakukan konversi. Instruksi XLAT biasanya
digunakan untuk pencarian tabel jika data selebar 8-bit dan tabel tidak lebih
dari 256 byte. Salah satu contoh penerapan adalah konversi BCD ke kode
seven-segment. Contoh 8–25 menunjukkan tabel lookup berisi kode
seven-segment untuk angka 0–9. Pada display seven-segment aktif-high (logika 1
menyalakan segmen), susunan bit dalam tabel diatur sehingga segmen a
berada di posisi bit 0 dan segmen g di bit 6. Bit ke-7 bernilai 0,
tetapi bisa digunakan untuk titik desimal jika diperlukan.
unsigned char LookUp(unsigned char temp)
{
unsigned char temp1[] = {
0x3f, 0x06, 0x5b, 0x4f, 0x66,
0x6d, 0x7d, 0x07, 0x7f, 0x6f
};
_asm
{
lea ebx, temp1
mov al, temp
xlat
mov temp, al
}
return temp;
}
FIGURE 8–5 The sevensegment display.
EXAMPLE 8–26
private: System::String^ GetDay(unsigned char day)
{
array^ temp =
{
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
};
return temp[day];
}
FIGURE 8–6 A sevensegment display.
An Example Program Using a Lookup Table
Program contoh
pada Gambar 8–6 menampilkan karakter angka bergaya seven-segment dengan
memanfaatkan lookup table. Input diperoleh dari keyboard menggunakan
handler KeyDown dan KeyPress, lalu difilter agar hanya menerima
angka 0–9. Setiap angka diubah menjadi kode seven-segment dan ditampilkan
menggunakan panel control objects: panel horizontal berukuran 120×25 dan
panel vertikal 25×75, dinamai panel1–panel7 sesuai urutan segmen. Latar
belakang panel dibuat hitam, sedangkan fungsi Clear (Contoh 8–27)
digunakan untuk menghapus tampilan angka dengan mengatur properti Visible
dari panel, atau alternatifnya mengubah warna panel.
EXAMPLE 8–27
private: System::Void Clear()
{
panel1->Visible = false;
panel2->Visible = false;
panel3->Visible = false;
panel4->Visible = false;
panel5->Visible = false;
panel6->Visible = false;
panel7->Visible = false;
}
EXAMPLE 8–28
private: System::Void Clear()
{
panel1->Visible = false;
panel2->Visible = false;
panel3->Visible = false;
panel4->Visible = false;
panel5->Visible = false;
panel6->Visible = false;
panel7->Visible = false;
}
private: System::Void Form1_KeyDown(System::Object^ sender,
System::Windows::Forms::KeyEventArgs^ e)
{
char lookup[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66,
0x6d, 0x7d, 0x07, 0x7f, 0x6f};
if (e->KeyCode >= Keys::D0 && e->KeyCode <= Keys::D9)
{
ShowDigit(lookup[e->KeyValue - 0x30]); // display the digit
}
}
private: System::Void ShowDigit(unsigned char code)
{
Clear();
if ((code & 1) == 1) panel1->Visible = true; // segment a
if ((code & 2) == 2) panel4->Visible = true; // segment b
if ((code & 4) == 4) panel5->Visible = true; // segment c
if ((code & 8) == 8) panel3->Visible = true; // segment d
if ((code & 16) == 16) panel6->Visible = true; // segment e
if ((code & 32) == 32) panel7->Visible = true; // segment f
if ((code & 64) == 64) panel2->Visible = true; // segment g
}
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
Clear();
}
8–4 DISK FILES [kembali]
Disk Organization
FIGURE 8–7 Structure of the disk.
FIGURE 8–8 Main data storage areas on a disk.
File Names
File dan program disimpan di dalam disk dan diakses menggunakan nama file serta ekstensi dari nama file tersebut.
Pada sistem operasi DOS, nama file hanya boleh terdiri dari 1 hingga 8 karakter. Nama file dapat berisi hampir semua karakter ASCII, kecuali spasi atau karakter berikut:
Selain nama file, file juga dapat memiliki ekstensi opsional yang panjangnya 1 sampai 3 karakter. Nama file dan ekstensi selalu dipisahkan dengan tanda titik (.).
Jika menggunakan Windows 95 hingga Windows XP, nama file bisa memiliki panjang hingga 255 karakter dan bahkan dapat berisi spasi. Ini merupakan peningkatan dibandingkan batasan 8 karakter pada DOS. Selain itu, file pada Windows juga bisa memiliki lebih dari satu ekstensi.
Nama Direktori dan Subdirektori
Sistem manajemen file pada DOS mengatur data dan program di dalam disk ke dalam direktori dan subdirektori.
Pada Windows, direktori dan subdirektori disebut juga dengan file folder. Aturan yang berlaku pada nama file juga berlaku pada nama folder.
Struktur disk dibuat sedemikian rupa sehingga ketika pertama kali diformat, disk sudah memiliki root directory. Root directory atau folder utama pada hard disk drive C dituliskan sebagai C:\. Folder lain dapat ditempatkan di dalam root directory.
FIGURE 8–9 Format of any FAT directory or subdirectory entry.
Sequential Access Files
Data pada disk disimpan dalam
bentuk file, dengan struktur utama: boot sector, FAT (File
Allocation Table) atau MFT (Master File Table), root directory, dan area
data. Boot sector (512
byte) berisi bootstrap loader yang memuat OS ke RAM saat komputer dinyalakan.
FAT/MFT menyimpan lokasi dan atribut file, sementara root directory (pada FAT)
berisi daftar file dan subdirektori. Pada NTFS, informasi file disimpan dalam
MFT record (1.024 byte) yang memuat nama, ukuran, atribut, tanggal, keamanan,
hingga lokasi data (file run).
Nama file di DOS terbatas 1–8 karakter + ekstensi 1–3 karakter tanpa spasi, sedangkan di Windows bisa hingga 255 karakter dan boleh berisi spasi. Direktori diatur secara hierarkis (C:, C:\DATA, C:\DATA\AREA1, dst.). Semua file diakses secara sekuensial, dibaca/ditulis dari awal ke akhir.
FIGURE 8–10 A record in the Master File Table in the NTFS system.
EXAMPLE 8–29
Dalam C++/CLI, file dikelola dengan File class di namespace System::IO. File dibuat menggunakan metode Create, tetapi program harus mengecek apakah file sudah ada. Jika gagal dibuat (misalnya folder tidak ditemukan atau disk penuh), program menampilkan pesan error lewat message box. Contoh 8–29 menunjukkan implementasi pembuatan file dengan penanganan kesalahan ini.
String^ fileName = "C:\\Test.txt";
if (File::Exists(fileName) == false)
{
// don’t forget using namespace System::IO;
try
{
File::Create(fileName);
}
catch (...)
{
MessageBox::Show("Cannot create " + fileName);
Application::Exit();
}
}
// Test.txt now exists with a length of 0 bytes
EXAMPLE 8–30
Setelah sebuah
file dibuat, langkah berikutnya adalah menulis data ke file. Penulisan
dilakukan satu byte demi satu byte menggunakan FileStream class, yang
selalu menulis mulai dari byte pertama file. Contoh 8–30 menunjukkan program
yang membuat file Test1.txt di root directory dan mengisinya dengan 256
huruf A. Jika dibuka di Notepad, file akan penuh dengan huruf A. Setelah
proses selesai, file stream wajib ditutup dengan fungsi Close(). Pada contoh ini, array byte dibuat dengan garbage collection
class di C++ agar menjadi managed array, yang penting untuk
pengelolaan memori otomatis.
String^ fileName = "C:\\Test1.txt";
array^ buffer = gcnew array(256);
try
{
FileStream^ fs = File::OpenWrite(fileName);
for (int a = 0; a < 256; a++)
{
buffer[a] = 'A';
}
fs->Write(buffer, 0, buffer->Length);
fs->Close();
}
catch (...)
{
MessageBox::Show("Disk error");
Application::Exit();
}
EXAMPLE 8–31
int number = 0x20000;
array^ buf = gcnew array(4);
// C++ conversion
buf[0] = number;
buf[1] = number >> 8;
buf[2] = number >> 16;
buf[3] = number >> 24;
// Assembly language conversion
_asm
{
mov eax, number
mov buf[0], al
mov buf[1], ah
bswap eax ; little endian to big endian
mov buf[2], ah
mov buf[3], al
}
EXAMPLE 8–32
String^ fileName = "C:\\Test1.txt";
array^ buffer1 = gcnew array(256);
try
{
FileStream^ fs = File::OpenRead(fileName);
fs->Read(buffer1, 0, 256);
fs->Close();
}
catch (...)
{
MessageBox::Show("Disk error");
Application::Exit();
}
EXAMPLE 8–33
Stringˆ fileName = “C:\\Test1.txt”;
FileInfoˆ fi = gcnew FileInfo(fileName);
int fileLength = fi->Length;
FIGURE 8–11 The HexDump program.
EXAMPLE 8–34
EXAMPLE 8–35
String^ fileName = "C:\\Test1.txt";
array^ buffer = gcnew array(256);
try
{
FileStream^ fs = File::OpenWrite(fileName);
for (int a = 0; a < 256; a++)
{
buffer[a] = 'S';
}
fs->Seek(0, SeekOrigin::End);
fs->Write(buffer, 0, buffer->Length);
fs->Close();
}
catch (...)
{
MessageBox::Show("Disk error");
Application::Exit();
}
// or the same operation is performed using the offset number
// in the Write function as follows:
String^ fileName = "C:\\Test1.txt";
array^ buffer = gcnew array(256);
try
{
FileStream^ fs = File::OpenWrite(fileName);
for (int a = 0; a < 256; a++)
{
buffer[a] = 'S';
}
fs->Write(buffer, 256, buffer->Length);
fs->Close();
}
catch (...)
{
MessageBox::Show("Disk error");
Application::Exit();
}
FIGURE 8–12 Inserting new data within an old file.
EXAMPLE 8–36
Contoh 8–36 menunjukkan sebuah program yang menyisipkan data baru ke dalam file lama.
Program ini menyalin file Data.new ke dalam file Data.old pada posisi setelah 256 byte pertama dari file Data.old.
Data baru dari buffer2 kemudian ditambahkan ke file, dan setelah itu diikuti oleh sisa isi file lama.
Fungsi-fungsi anggota baru dari File digunakan untuk menghapus file lama dan mengganti nama file baru agar menjadi nama file lama.
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
String^ fileName1 = "C:\\Data.old";
String^ fileName2 = "C:\\Data.new";
int fileLength;
array^ buffer1 = gcnew array(256);
array^ buffer2 = gcnew array(6);
try
{
FileStream^ fs1 = File::OpenWrite(fileName1);
FileStream^ fs2 = File::OpenWrite(fileName2);
FileInfo^ fi = gcnew FileInfo(fileName1);
fileLength = fi->Length;
fs1->Read(buffer1, 0, 256);
fs2->Write(buffer1, 0, 256);
fs2->Write(buffer2, 0, 6);
fileLength -= 256;
while (fileLength > 0)
{
fs1->Read(buffer1, 0, 256);
fs2->Write(buffer1, 0, 256);
fileLength -= 256;
}
fs1->Close();
fs2->Close();
}
catch (...)
{
MessageBox::Show("Disk error");
Application::Exit();
}
}
Random Access Files
File akses acak (random access files) dikembangkan melalui perangkat lunak dengan menggunakan file akses berurutan (sequential access files).
Sebuah file akses acak diakses menggunakan nomor record, bukan dengan menelusuri file untuk mencari data.
Fungsi Seek menjadi sangat penting ketika file akses acak dibuat.
File akses acak jauh lebih mudah digunakan untuk volume data yang besar, yang sering disebut sebagai basis data (database).
Menyisipkan data di tengah file tidak bisa dilakukan langsung, sehingga diperlukan cara dengan membuat file baru. Caranya adalah menyalin bagian awal file lama sampai titik sisipan ke file baru, kemudian menambahkan data baru, lalu menyalin sisa isi file lama setelah titik sisipan. Setelah semua selesai, file lama dihapus dan file baru diubah namanya agar sama dengan file lama. Dengan metode ini, data baru dapat dimasukkan tanpa merusak struktur file yang sudah ada, seperti diperlihatkan pada contoh program yang menyalin isi setelah 256 byte, menyisipkan buffer baru, lalu menambahkan sisa isi file lama.
EXAMPLE 8–37
Contoh 8–37 menggambarkan sebuah program singkat yang membuat file bernama CUST.FIL dan memasukkan 5000 record kosong yang masing-masing berukuran 512 byte.
Sebuah record kosong berisi nilai 00H pada setiap byte.
File ini tampak cukup besar, tetapi sebenarnya masih muat di hard disk dengan kapasitas paling kecil sekalipun.
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
String^ fileName = "C:\\Cust.fil";
array^ buffer = gcnew array(512);
// fill buffer
for (int a = 0; a < 512; a++)
{
buffer[a] = 0;
}
try
{
FileStream^ fs = File::OpenWrite(fileName);
for (int a = 0; a < 5000; a++)
{
fs->Write(buffer, 0, 512);
}
fs->Close();
}
catch (...)
{
MessageBox::Show("Disk error");
Application::Exit();
}
}
EXAMPLE 8–38
Membaca dan
menulis record pada random access file dilakukan dengan memanfaatkan
fungsi Seek untuk langsung menuju lokasi record yang diinginkan. Karena
setiap record memiliki ukuran tetap, misalnya 512 byte, maka nomor
record dikalikan dengan 512 untuk mendapatkan posisi byte awal record tersebut.
Contoh pada program (8–38) menunjukkan bahwa file CUST.FIL tetap terbuka
sepanjang operasi, dan pointer file dipindahkan dari awal file ke lokasi record
target. Dengan cara ini, proses membaca maupun menulis data bisa dilakukan
secara cepat tanpa harus menelusuri isi file dari awal hingga posisi record.
void CCusDatabaseDlg::FindRecord(unsigned int RecordNumber)
{
File.Seek(RecordNumber * 512, CFile::begin);
}
EXAMPLE 8–39
Fungsi-fungsi
tambahan seperti WriteRecord, ReadRecord, FindLastNameRecord, dan FindBlankRecord
digunakan untuk mempermudah pengelolaan database pelanggan dalam random access
file. Fungsi WriteRecord bertugas menulis data
baru ke dalam record tertentu, sedangkan ReadRecord membaca isi record
berdasarkan nomor record. Fungsi FindLastNameRecord digunakan untuk
mencari data pelanggan melalui pencocokan nama belakang, sementara FindBlankRecord
berfungsi menemukan record kosong (berisi 00H) yang bisa dipakai untuk
menambahkan data baru. Semua fungsi ini bekerja dengan memanfaatkan struktur
data yang telah ditentukan untuk setiap record, sehingga akses, penyimpanan,
maupun pencarian data dapat dilakukan secara teratur dan efisien.
^ FirstName = gcnew array(32);
static array^ Mi = gcnew array(1);
static array^ LastName = gcnew array(32);
static array^ Street1 = gcnew array(64);
static array^ Street2 = gcnew array(64);
static array^ City = gcnew array(32);
static array^ State = gcnew array(2);
static array^ ZipCode = gcnew array(9);
static array^ Other = gcnew array(276);
};
// functions placed at the end of the form1 class
static array^ buffer = gcnew array(512);
static String^ fileName = "C:\\Cust.fil";
static FileStream^ fs;
static Customer Record;
private: System::Void Form1_Load(System::Object^ sender,
System::EventArgs^ e)
{
// open the file when the application starts
Customer Record;
try
{
fs = File::OpenWrite(fileName);
for (int a = 0; a < 5000; a++)
{
fs->Write(buffer, 0, 512);
}
fs->Close();
}
catch (...)
{
MessageBox::Show("Disk error");
Application::Exit();
}
}
private: System::Void FindRecord(unsigned int RecordNumber)
{
fs->Seek(RecordNumber * 512, SeekOrigin::Begin);
}
private: System::Void WriteRecord(unsigned int RecordNumber)
{
FindRecord(RecordNumber);
fs->Write(Record.FirstName, 0, 32);
fs->Write(Record.Mi, 0, 1);
fs->Write(Record.LastName, 0, 32);
fs->Write(Record.Street1, 0, 64);
fs->Write(Record.Street2, 0, 64);
fs->Write(Record.City, 0, 32);
fs->Write(Record.State, 0, 2);
fs->Write(Record.ZipCode, 0, 9);
}
private: System::Void ReadRecord(unsigned int RecordNumber)
{
FindRecord(RecordNumber);
fs->Read(Record.FirstName, 0, 32);
fs->Read(Record.Mi, 0, 1);
fs->Read(Record.LastName, 0, 32);
fs->Read(Record.Street1, 0, 64);
fs->Read(Record.Street2, 0, 64);
fs->Read(Record.City, 0, 32);
fs->Read(Record.State, 0, 2);
fs->Read(Record.ZipCode, 0, 9);
}
private: System::UInt32 FindFirstName(array^ FirstName)
{
for (int a = 0; a < 5000; a++)
{
ReadRecord(a);
if (Record.FirstName == FirstName)
{
return a; // if found return record number
}
}
return 5001; // if not found return 5001
}
private: System::UInt32 FindBlankRecord()
{
for (int a = 0; a < 5000; a++)
{
ReadRecord(a);
if (Record.LastName[0] == 0)
{
return a;
}
}
return 0;
}
FIGURE 8–13 The DataTime application.