<< 6. Praktikum | NASM Lehele

NASM – Praktikum7: Switch, Libprax 3: FileIO, OpenGL

Viimases praktikumis vaatame kuidas töötavad SWITCH laused ning lõpetame Libprax-i FileIO funktsioonid. Eelmistest praktikumidest on nüüdseks alusfailides programmeeritud ka aftoa funktsioon. Antud praktikumi alusfailid on palju mahukamad ning selleks, et ekraanile ilmuks soovitud väljund peame implementeerima kõik FileIO.asm funktsioonid.


1. Switch

Switch lause on C-s tuntud eriline mitmeharuline loogikalülitus. Switch-i rakendatakse juhtudel, mil sisendiks on palju erinevaid, kuid järjestikuseid väärtusi. If/else lausetega läheks selliste konstruktsioonide kirjutamine tülikaks ja ebaefektiivseks:

	enum Commands {
		CMD_NOP = 0,
		CMD_AUTH_INIT,
		CMD_AUTH_OK,
		CMD_AUTH_INV,
		CMD_MSG_GLOBAL,
		CMD_MSG_ROOM,
		CMD_MSG_PRIVATE,
		CMD_WHOIS,
		CMD_PING,
		CMD_LAST, // last entry
	};
	// get a string representation of a command
	const char* GetCommandStr(unsigned cmd)
	{
		if (cmd == CMD_NOP)
			return "CMD_NOP";
		else if (cmd == CMD_AUTH_INIT)
			return "CMD_AUTH_INIT";
		else if (cmd == CMD_AUTH_OK)
			return "CMD_AUTH_OK";
		else if (cmd == CMD_AUTH_INV)
			return "CMD_AUTH_INV";
		else if (cmd == CMD_MSG_GLOBAL)
			return "CMD_MSG_GLOBAL";
		else if (cmd == CMD_MSG_ROOM)
			return "CMD_MSG_ROOM";
		else if (cmd == CMD_MSG_PRIVATE)
			return "CMD_MSG_PRIVATE";
		else if (cmd == CMD_WHOIS)
			return "CMD_WHOIS";
		else if (cmd == CMD_PING)
			return "CMD_PING";
		else
			return "INVALID_COMMAND";
	}

Nagu näha võib väga lihtne programm muutuda väga ebamugavaks if/else rägastikuks. Veelgi hullem on olukord kui käsk on CMD_PING, mille puhul peab programm läbima kõik eelnevad if/else laused! Mida pikemaks rägastik muutub, seda aeglasemaks muutub ka ExecCommand funktsioon.

Switch lause teeb kirjutamise märksa mugavamaks:

	// get a string representation of a command
	const char* GetCommandStr(unsigned cmd)
	{
		switch (cmd) {
			case CMD_NOP:         return "CMD_NOP";
			case CMD_AUTH_INIT:   return "CMD_AUTH_INIT";
			case CMD_AUTH_OK:     return "CMD_AUTH_OK";
			case CMD_AUTH_INV:    return "CMD_AUTH_INV";
			case CMD_MSG_GLOBAL:  return "CMD_MSG_GLOBAL";
			case CMD_MSG_ROOM:    return "CMD_MSG_ROOM";
			case CMD_MSG_PRIVATE: return "CMD_MSG_PRIVATE";
			case CMD_WHOIS:       return "CMD_WHOIS";
			case CMD_PING:        return "CMD_PING";
			default:              return "INVALID_COMMAND";
		}
	}

Alternatiivselt võime switch lause ka asendada tabeliga, kuid see variant töötab ainult kindlatel juhtudel. Tabeli võib teha ka näiteks funktsiooniviitadest, kus iga funktsioon vastab mingile kindlale "actionile". Sellist meetodi kutsutakse Command Pattern-iks ja on kasutuses enamikes suuremates programmides mitmel erineval tasemel.

	// get a string representation of a command
	const char* GetCommandStr(unsigned cmd)
	{
		static const char* strings[] = {
			"CMD_NOP", 
			"CMD_AUTH_INIT", 
			"CMD_AUTH_OK", 
			"CMD_AUTH_INV",
			"CMD_MSG_GLOBAL", 
			"CMD_MSG_ROOM", 
			"CMD_MSG_PRIVATE",
			"CMD_WHOIS", 
			"CMD_PING"
		};
		if (cmd >= CMD_LAST) // check for invalid case
			return "INVALID_COMMAND";
		return strings[cmd]; // return value from table
	}
	// execute a series of actions based on the command
	int ExecuteCommand(void* context, unsigned cmd)
	{
		typedef int (*ActionFunc)(void* ctx);
		static ActionFunc actions[] = {
			&Action_NOP,
			&Action_AUTH_INIT,
			&Action_AUTH_OK,
			&Action_AUTH_INV,
			&Action_MSG_GLOBAL,
			&Action_MSG_ROOM,
			&Action_MSG_PRIVATE,
			&Action_WHOIS,
			&Action_PING,
		};
		if (cmd >= CMD_LAST) // check for invalid case
			return 0;
		return actions[cmd](ctx); // execute the action
	}

2. Switch + ASM

Switch lauseid on Makroassembleris palju keerulisem kirjutada kui C-s. Kompilaatoril on eelis rakendada mitmeid erinevaid switch võtteid, mis hüppavad väga täpselt konkreetse sümbolini.

Prax7-switch.zip - Alusfailid Windowsile ja Linuxile

Üldine switch lause koosneb 3 osast:
1) Kontrollblokk - Sisendväärtuse teisendamine indeksiks ja välistamine
2) Hüppetabel - Switch CASE aadressite tabel (jump-table)
3) Kood - CASE labelitega seotud kood

Näide kolmeosalisest switchist:

; const char* __cdecl switch_jumptable(int cmd);
globalfunc switch_jumptable, cmd:dword
	prologue
	mov eax, .cmd  ;; EAX: int cmd
	
	;; 1) switch (cmd)
	cmp    eax, 2               ;; 2 max case value
	mov    ecx, 3
	cmova  eax, ecx             ;; conditional move, if(cmd > 2) mov eax, ecx
	mov	   eax, [.@jumptable + eax*4] ;; load case label pointer
	jmp    eax 						  ;; jump to case label
	
	;; 2) create a jumptable with all the case label pointers:
	.@jumptable:
		dd .@case0, .@case1, .@case2, .@cdefault
		
	;; 3) implemented case labels
	.@case0:
		mov  eax, cmd0 ;; return "LOGIN";
		jmp  .@eswitch
	.@case1:
		mov  eax, cmd1 ;; return "WHOIS";
		jmp  .@eswitch
	.@case2:
		mov  eax, cmd2 ;; return "CHAT";
		jmp  .@eswitch
	.@cdefault:
		mov  eax, cmdi ;; return "INVALID";
	.@eswitch:
	epilogue

Efektiivsem switch lause koosneb ainult kahest osast:
1) Kontrollblokk - Sisendväärtuse teisendamine indeksiks ja välistamine
2) Joondatud kood - Joondatud CASE labelid

Joondatud (aligned) CASE labelid vahetavad mälu kiiruse vastu ning võimaldavad väga efektiivset CASE blokkide väljakutsumist:

; const char* __cdecl switch_aligned(int cmd);
globalfunc switch_aligned, cmd:dword
	prologue
	mov  eax, .cmd ;; EAX: int cmd 
	
	;; 1) switch (cmd)
	cmp    eax, 2               ;; > 2, then jump default
	mov    ecx, 3
	cmova  eax, ecx             ;; conditional move, if(cmd > 2) mov eax, ecx
	shl    eax, 4				;; eax *= 16
	add    eax, .@case0			;; add address of ..@case0
	jmp	   eax 					;; jump to case label
	
	;; 2) aligned case labels (16 bytes for each case)
	align 16
	.@case0:
		mov  eax, cmd0 ;; return "LOGIN";
		jmp  .@eswitch
	align 16
	.@case1:
		mov  eax, cmd1 ;; return "WHOIS";
		jmp  .@eswitch
	align 16
	.@case2:
		mov  eax, cmd2 ;; return "CHAT";
		jmp  .@eswitch
	align 16
	.@cdefault:
		mov  eax, cmdi ;; return "INVALID";
	.@eswitch:
	epilogue

Makrod

Switchimise loogika võib olla päris tüütu, seega on variant kasutada erilist switch makrot, mis teeb meile tabelipõhise switch-i:

; const char* __cdecl switch_macro(int cmd);
globalfunc switch_macro, cmd:dword
	prologue
	.switch .cmd
		.case CMD_LOGIN
			.return cmd0 ;; return "LOGIN"
		.case CMD_WHOIS
			.return cmd1 ;; return "WHOIS"
		.case CMD_CHAT
			.return cmd2 ;; return "CHAT"
		.default
			.return cmdi ;; return "INVALID"
	.endswitch
	epilogue

Alternatiiv - Indekseeritud tabel

Switche on väga lihtne kirjutada, kuid võimaluse korral on lihtsalt tabeli kasutamine ikkagi kõige parem:

; const char* __cdecl noswitch(int cmd);
globalfunc noswitch, cmd:dword
	prologue
	mov    eax, .cmd ;; EAX: int cmd
	cmp    eax, 2   ;; 
	mov    ecx, 3   
	cmova  eax, ecx ;; conditional move, if(cmd > 2) mov eax, ecx
	
	data32 commands, cmd0, cmd1, cmd2, cmdi
	mov eax, [commands + eax*4] ;; return commands[cmd];
	epilogue

3. Libprax: FileIO

Selleks, et 7. praktikumi programm korrektselt töötaks, on vaja implementeerida FileIO.

Alusfailid:
Prax7.zip
NB! MinGW GCC versioon peab olema 4.8, vaata gcc -v käsuga GCC versiooni. 64-bitised Linux keskkonnad võivad lisaks vajada GCC 32/64-bit teeke: sudo apt-get install gcc-multilib
NB! Linuxi puhul tuleb esmalt käivitada make install käsk.

:/NASM/Prax7/
  `- Makefile
  `- libprax.h
  `- main.c
  `- main.h
  `- util.c
  `- util.h
  `- bitmap.c          <= Little-Endian Windows BMP loader
  `- bitmap.h
  `- utf8.c            <= UTF8 to WCHAR that isn't completely broken
  `- utf8.h  
  ./libprax/
    `- fileio.asm      <= Praktikumi FileIO (implementeerimata!)
    `- conio.asm
    `- lib.asm
    `- string.asm
    `- macros.inc
  ./data/
    `- model_mage.bmd    <= Little-Endian
    `- model_mage.bmp    <= Little-Endian
	`- dark_fighter_6.bmd
    `- dark_fighter_6.bmp
	`- ARC_170.bmd
    `- ARC_170.bmp
  ./GL/

Implementeeritavad funktsioonid:

Ülesanne - Implementeerida FileIO funktsioonid.

Abiks Windows File API-st: Win32 File API
Abiks Linux Syscall API-st: Linux Syscall Reference

Kokkupakitud lahendusfailid:
Prax7-switch-complete.zip
Prax7-complete.zip

BMD faili genereerimine (C++ lähtekood):
BMDGen.zip


Lõppülesanne - Täiendada OpenGL stseeni funktsionaalsust

  • Lisa mudelid - Lisa nupuvajutus mis vahetab mudelite vahel
  • Lisa Zoom - Korralik Zoom funktsionaalsus hiire scrolliga
  • Lisa Hiirevaade - Kui vasak hiirenupp on all, siis saab hiirega ringi vaadata
  • Täienda infot - Täienda infot ekraanil, et kasutajal oleks lihtsam

4. Kokkuvõte

Selle praktikumiga õppisime SWITCH lause kasulikkusest ning implementeerisime oma algelise FileIO teegi. Saime ka näha kuidas näeb välja väga lihtne OpenGL programm C-s.
Ühtlasi oli see ka viimane praktikum - arvestustööd palun esitada 4. jaanuriks!

<< 6. Praktikum | NASM Lehele