Nesta última parte vamos dar uma examinada no código fonte do simulador, que pode ser baixado
daqui.
Estrutura BásicaO SimComp é um programa C que segue a estrutura típica de uma aplicação "Windows API", nos moldes popularizados pelo
Petzold.
O programa inicia pela rotina
WinMain, que comanda a criação e apresentação da janela e depois entra no loop de tratamento de mensagens:
- int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst,
- LPSTR lpCmdLine, int nCmdShow)
- {
- HACCEL hAccel;
- MSG msg;
-
-
- hInst = hInstance;
- InitComp ();
- nModoAtual = MODO_CMD;
- hevBreak = CreateEvent (NULL, FALSE, FALSE, NULL);
- hevInput = CreateEvent (NULL, FALSE, FALSE, NULL);
-
-
- if (! CriaJanela())
- return 1;
-
-
- ShowWindow (hMainWnd, nCmdShow == SW_SHOWMAXIMIZED ? SW_SHOW :
- nCmdShow);
- UpdateWindow (hMainWnd);
-
-
- hAccel = LoadAccelerators (hInst, MAKEINTRESOURCE (IDR_ACCEL));
- while (GetMessage (&msg, NULL, 0, 0))
- {
-
- if (!TranslateAccelerator (hMainWnd, hAccel, &msg))
- TranslateMessage (&msg);
- DispatchMessage (&msg);
- }
-
-
- CloseHandle (hevBreak);
- CloseHandle (hevInput);
- return 0;
- }
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst,
LPSTR lpCmdLine, int nCmdShow)
{
HACCEL hAccel;
MSG msg;
// Iniciacoes
hInst = hInstance;
InitComp ();
nModoAtual = MODO_CMD;
hevBreak = CreateEvent (NULL, FALSE, FALSE, NULL);
hevInput = CreateEvent (NULL, FALSE, FALSE, NULL);
// Cria a janela principal
if (! CriaJanela())
return 1;
// Apresenta a janela
ShowWindow (hMainWnd, nCmdShow == SW_SHOWMAXIMIZED ? SW_SHOW :
nCmdShow);
UpdateWindow (hMainWnd);
// Trata as mensagens
hAccel = LoadAccelerators (hInst, MAKEINTRESOURCE (IDR_ACCEL));
while (GetMessage (&msg, NULL, 0, 0))
{
// Usa accelerator para converter Enter em Exec
if (!TranslateAccelerator (hMainWnd, hAccel, &msg))
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Limpeza final
CloseHandle (hevBreak);
CloseHandle (hevInput);
return 0;
}
A criação da janela não tem nada de especial, uma classe de janela é registrada e a janela é criada (vide rotina
CriaJanela).
A rotina de janela cuida da interface com o operador:
- LRESULT CALLBACK MainWinProc (HWND hwnd, UINT message,
- WPARAM wParam, LPARAM lParam)
- {
- char szCmd [40];
- PAINTSTRUCT ps;
- HDC hdc;
-
-
- switch (message)
- {
- case WM_CREATE:
-
- hMainWnd = hwnd;
- CriaFonte ();
- MoveWindow (hwnd, 0, 0, 90*nCxCar, 32*nCyCar, FALSE);
-
-
- hEdit = CreateWindow ("edit", "",
- WS_VISIBLE | WS_CHILD | WS_BORDER | ES_UPPERCASE,
- 2*nCxCar, 28*nCyCar, 72*nCxCar, nCyCar+2,
- hwnd, (HMENU) IDC_INPUT, hInst, 0);
- hExec = CreateWindow ("button", "Exec",
- WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
- 76*nCxCar, 28*nCyCar-3, 7*nCxCar, nCyCar+6,
- hwnd, (HMENU) IDC_EXEC, hInst, 0);
- hBreak = CreateWindow ("button", "Break",
- WS_CHILD | BS_PUSHBUTTON,
- 76*nCxCar, nCyCar-3, 7*nCxCar, nCyCar+6,
- hwnd, (HMENU) IDC_BREAK, hInst, 0);
- SendMessage (hEdit, EM_SETLIMITTEXT, sizeof(szCmd)-1, 0);
- SendMessage (hEdit, WM_SETFONT, (WPARAM) hFont, 0);
- SendMessage (hExec, WM_SETFONT, (WPARAM) hFont, 0);
- SendMessage (hBreak, WM_SETFONT, (WPARAM) hFont, 0);
- return 0;
-
- case WM_SETFOCUS:
-
- if (hEdit != NULL)
- SetFocus (hEdit);
- return 0;
-
- case WM_PAINT:
- if (! IsIconic (hwnd))
- {
-
- hdc = BeginPaint (hwnd, &ps);
- AtlReg (hdc);
- AtlMem (hdc);
- AtlDisplay (hdc);
- Copyright (hdc);
- EndPaint (hwnd, &ps);
- }
- return 0;
-
- case WM_COMMAND:
- switch (LOWORD(wParam))
- {
- case IDC_EXEC:
- SendMessage (hEdit, WM_GETTEXT, sizeof (szCmd), (LPARAM) szCmd);
- TrataDigitacao (szCmd);
- SendMessage (hEdit, WM_SETTEXT, 0, (LPARAM) "");
- SetFocus (hEdit);
- return 0;
-
- case IDC_BREAK:
- SetEvent (hevBreak);
- SetFocus (hEdit);
- return 0;
- }
- break;
-
- case WM_CLOSE:
-
- if (hEdit != NULL)
- DestroyWindow (hEdit);
- if (hExec != NULL)
- DestroyWindow (hExec);
- if (hBreak != NULL)
- DestroyWindow (hBreak);
- DestroyWindow (hwnd);
- DeleteObject (hFont);
- return 0;
-
- case WM_DESTROY:
-
- PostQuitMessage (0);
- return 0;
- }
-
-
- return DefWindowProc (hwnd, message, wParam, lParam);
- }
LRESULT CALLBACK MainWinProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
char szCmd [40]; // comando
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_CREATE:
// salva o handle, cria o fonte de letra e acerta o tamanho
hMainWnd = hwnd;
CriaFonte ();
MoveWindow (hwnd, 0, 0, 90*nCxCar, 32*nCyCar, FALSE);
// cria controles auxiliares e os inicia
hEdit = CreateWindow ("edit", "",
WS_VISIBLE | WS_CHILD | WS_BORDER | ES_UPPERCASE,
2*nCxCar, 28*nCyCar, 72*nCxCar, nCyCar+2,
hwnd, (HMENU) IDC_INPUT, hInst, 0);
hExec = CreateWindow ("button", "Exec",
WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
76*nCxCar, 28*nCyCar-3, 7*nCxCar, nCyCar+6,
hwnd, (HMENU) IDC_EXEC, hInst, 0);
hBreak = CreateWindow ("button", "Break",
WS_CHILD | BS_PUSHBUTTON,
76*nCxCar, nCyCar-3, 7*nCxCar, nCyCar+6,
hwnd, (HMENU) IDC_BREAK, hInst, 0);
SendMessage (hEdit, EM_SETLIMITTEXT, sizeof(szCmd)-1, 0);
SendMessage (hEdit, WM_SETFONT, (WPARAM) hFont, 0);
SendMessage (hExec, WM_SETFONT, (WPARAM) hFont, 0);
SendMessage (hBreak, WM_SETFONT, (WPARAM) hFont, 0);
return 0;
case WM_SETFOCUS:
// Deixar foco sempre no editbox
if (hEdit != NULL)
SetFocus (hEdit);
return 0;
case WM_PAINT:
if (! IsIconic (hwnd))
{
// Refaz a tela
hdc = BeginPaint (hwnd, &ps);
AtlReg (hdc);
AtlMem (hdc);
AtlDisplay (hdc);
Copyright (hdc);
EndPaint (hwnd, &ps);
}
return 0;
case WM_COMMAND: // Botoes
switch (LOWORD(wParam))
{
case IDC_EXEC: // Trata texto digitado
SendMessage (hEdit, WM_GETTEXT, sizeof (szCmd), (LPARAM) szCmd);
TrataDigitacao (szCmd);
SendMessage (hEdit, WM_SETTEXT, 0, (LPARAM) "");
SetFocus (hEdit);
return 0;
case IDC_BREAK: // Interrompe o programa
SetEvent (hevBreak);
SetFocus (hEdit);
return 0;
}
break;
case WM_CLOSE:
// Destroi controles e a janela
if (hEdit != NULL)
DestroyWindow (hEdit);
if (hExec != NULL)
DestroyWindow (hExec);
if (hBreak != NULL)
DestroyWindow (hBreak);
DestroyWindow (hwnd);
DeleteObject (hFont);
return 0;
case WM_DESTROY:
// Encerra o programa
PostQuitMessage (0);
return 0;
}
// Demais mensagens vao para a rotina padrao
return DefWindowProc (hwnd, message, wParam, lParam);
}
Os detalhes mais importantes desta rotina, como indicado nos comentários, são o tratamento das seguintes mensagens:
- WM_CREATE: acerta o tamanho da janela, cria e configura os controles auxiliares (editbox de digitação os botões Exec e Break) na janela principal.
- WM_SETFOCURS: obriga o foco a estar sempre no editbox.
- WM_PAINT: redesenha a tela, mais detalhes adiante.
- WM_COMMAND: trata os botões.
- WM_CLOSE: faz a limpeza dos controles e do fonte criado.
- WM_DESTROY: encerra o programa.
A Representação do ComputadorComo o COMP é muito simples, a sua representação também é:
- static int ac;
- static int cy;
- static int cp;
- static int mem [100];
-
- #define NLIN_DISP 11
- #define NCOL_DISP 80
-
- static char szDisplay [NLIN_DISP] [NCOL_DISP+1];
- static int nLinDisp;
- static int nColDisp;
-
-
- static enum
- {
- MODO_CMD,
- MODO_ASM,
- MODO_EDIT,
- MODO_RUN,
- MODO_INPUT
- } nModoAtual;
static int ac; // acumulador
static int cy; // "sobrecarga" (carry)
static int cp; // contador de programa
static int mem [100]; // memoria
#define NLIN_DISP 11
#define NCOL_DISP 80
static char szDisplay [NLIN_DISP] [NCOL_DISP+1]; // display do computador
static int nLinDisp; // linha do cursor do display
static int nColDisp; // coluna do cursor do display
// Modo atual do computador
static enum
{
MODO_CMD, // aguardando um comando
MODO_ASM, // executando comando Asm
MODO_EDIT, // executando comando Edit
MODO_RUN, // executando comando Run
MODO_INPUT // aguradando INPUT no comando Run
} nModoAtual;
Por simplificação usei int para o acumulador e memória, na verdade eles estão limitados a valores entre 0 e 9999. Da mesma forma, carry pode ser somente 0 ou 1 e cp pode variar de 0 a 99 mas também são int. O display do COMP foi organizado em 11 linhas de 80 colunas.
Por último, e mais importante, temos o "modo de operação" do COMP, que indica o que ele está fazendo. Isto é usado para definir o tratamento a ser dado ao texto digitado no textbox quando o botão Exec é pressionado.
O MonitorInicialmente o COMP está em MODO_CMD, no qual o texto digitado é tratado como um comando do monitor. A rotina
TrataCmd examina a primeira letra do comando e executa a ação correspondente.
- static void TrataCmd (char *szCmd)
- {
- unsigned tid;
- HANDLE hThread;
- char szAux [10];
- int i;
-
-
- for (i = 0; *szCmd == ' '; i++)
- ;
- if (szCmd[i] == 0)
- return;
-
-
- Display (szCmd, TRUE, TRUE);
-
-
- switch (szCmd[i++])
- {
- case 'A':
- nPosAtl = PegaValor (szCmd, &i, 100);
- wsprintf (szAux, "%02d: ", nPosAtl);
- Display (szAux, FALSE, TRUE);
- nModoAtual = MODO_ASM;
- break;
-
- case 'C':
- InitComp ();
- RefazTela ();
- break;
-
- case 'E':
- nPosAtl = PegaValor (szCmd, &i, 100);
- wsprintf (szAux, "%02d: ", nPosAtl);
- Display (szAux, FALSE, TRUE);
- nModoAtual = MODO_EDIT;
- break;
-
- case 'L':
- Load ();
- Display (">", TRUE, TRUE);
- break;
-
- case 'R':
- cp = PegaValor (szCmd, &i, 100);
- nVeloc = PegaValor (szCmd, &i, 100) * 100;
- nModoAtual = MODO_RUN;
- ShowWindow (hBreak, SW_SHOW);
- SetFocus (hBreak);
- ShowWindow (hEdit, SW_HIDE);
- ShowWindow (hExec, SW_HIDE);
- ResetEvent (hevBreak);
- hThread = (HANDLE) _beginthreadex (NULL, 0, Exec, NULL, 0, &tid);
- CloseHandle (hThread);
- break;
-
- case 'S':
- Save ();
- Display (">", TRUE, TRUE);
- break;
-
- default:
- Display ("Comando Invalido!", TRUE, TRUE);
- break;
- }
- }
static void TrataCmd (char *szCmd)
{
unsigned tid;
HANDLE hThread;
char szAux [10];
int i;
// pula espacos iniciais
for (i = 0; *szCmd == ' '; i++)
;
if (szCmd[i] == 0) // ignora linha vazia
return;
// coloca a linha digitada no display
Display (szCmd, TRUE, TRUE);
// primeira letra deve ser comando
switch (szCmd[i++])
{
case 'A': // Asm
nPosAtl = PegaValor (szCmd, &i, 100);
wsprintf (szAux, "%02d: ", nPosAtl);
Display (szAux, FALSE, TRUE);
nModoAtual = MODO_ASM;
break;
case 'C': // Clear
InitComp ();
RefazTela ();
break;
case 'E': // Edit
nPosAtl = PegaValor (szCmd, &i, 100);
wsprintf (szAux, "%02d: ", nPosAtl);
Display (szAux, FALSE, TRUE);
nModoAtual = MODO_EDIT;
break;
case 'L': // Load
Load ();
Display (">", TRUE, TRUE);
break;
case 'R': // Run
cp = PegaValor (szCmd, &i, 100);
nVeloc = PegaValor (szCmd, &i, 100) * 100;
nModoAtual = MODO_RUN;
ShowWindow (hBreak, SW_SHOW);
SetFocus (hBreak);
ShowWindow (hEdit, SW_HIDE);
ShowWindow (hExec, SW_HIDE);
ResetEvent (hevBreak);
hThread = (HANDLE) _beginthreadex (NULL, 0, Exec, NULL, 0, &tid);
CloseHandle (hThread);
break;
case 'S': // Save
Save ();
Display (">", TRUE, TRUE);
break;
default:
Display ("Comando Invalido!", TRUE, TRUE);
break;
}
}
No caso do comando Asm a rotina
Assembla é quem faz o
parse da instrução assembly e coloca o código de máquina correspondente na memória.
Atualizando a JanelaConforme pode ser visto no tratamento de WM_PAINT, as seguintes rotinas são responsáveis por atualizar a janela do programa:
AtlReg,
AtlMem,
AtlDisplay e
Copyright. Estas rotinas usam as funções do GDI do Windows.
A rotina
Print encapsula a escrita de textos na janela:
- static void Print (HDC hdc, int lin, int col, COLORREF cor, char *szTexto)
- {
- HFONT hOldFont;
-
- hOldFont = (HFONT) SelectObject (hdc, hFont);
- SetTextColor (hdc, cor);
- SetBkColor (hdc, RGB(255,255,255));
- SetBkMode (hdc, OPAQUE);
- TextOut (hdc, col*nCxCar, lin*nCyCar, szTexto, strlen (szTexto));
- SelectObject (hdc, hOldFont);
- }
static void Print (HDC hdc, int lin, int col, COLORREF cor, char *szTexto)
{
HFONT hOldFont;
hOldFont = (HFONT) SelectObject (hdc, hFont);
SetTextColor (hdc, cor);
SetBkColor (hdc, RGB(255,255,255));
SetBkMode (hdc, OPAQUE);
TextOut (hdc, col*nCxCar, lin*nCyCar, szTexto, strlen (szTexto));
SelectObject (hdc, hOldFont);
}
No caso da rotina
Copyright, o texto é escrito duas vezes, uma em preto e outra em branco, com a defasagem de 1 pixel para produzir um efeito de baixo relevo:
- static void Copyright (HDC hdc)
- {
- static char szTexto[] = "(C) 2004, Daniel Quadros";
- HFONT hOldFont;
-
- hOldFont = (HFONT) SelectObject (hdc, hFont);
- SetBkMode (hdc, TRANSPARENT);
- SetTextColor (hdc, RGB (255, 255, 255));
- TextOut (hdc, 59*nCxCar+1, 26*nCyCar+6, szTexto, strlen (szTexto));
- SetTextColor (hdc, RGB (0, 0, 0));
- TextOut (hdc, 59*nCxCar, 26*nCyCar+5, szTexto, strlen (szTexto));
- SelectObject (hdc, hOldFont);
- }
static void Copyright (HDC hdc)
{
static char szTexto[] = "(C) 2004, Daniel Quadros";
HFONT hOldFont;
hOldFont = (HFONT) SelectObject (hdc, hFont);
SetBkMode (hdc, TRANSPARENT);
SetTextColor (hdc, RGB (255, 255, 255));
TextOut (hdc, 59*nCxCar+1, 26*nCyCar+6, szTexto, strlen (szTexto));
SetTextColor (hdc, RGB (0, 0, 0));
TextOut (hdc, 59*nCxCar, 26*nCyCar+5, szTexto, strlen (szTexto));
SelectObject (hdc, hOldFont);
}
Executando um programaA execução de um programa no COMP é feita em uma thread separada, para não interferir no funcionamento da UI. A thread é criada quando o comando Run é interpretado:
- case 'R':
- cp = PegaValor (szCmd, &i, 100);
- nVeloc = PegaValor (szCmd, &i, 100) * 100;
- nModoAtual = MODO_RUN;
- ShowWindow (hBreak, SW_SHOW);
- SetFocus (hBreak);
- ShowWindow (hEdit, SW_HIDE);
- ShowWindow (hExec, SW_HIDE);
- ResetEvent (hevBreak);
- hThread = (HANDLE) _beginthreadex (NULL, 0, Exec, NULL, 0, &tid);
- CloseHandle (hThread);
- break;
case 'R': // Run
cp = PegaValor (szCmd, &i, 100);
nVeloc = PegaValor (szCmd, &i, 100) * 100;
nModoAtual = MODO_RUN;
ShowWindow (hBreak, SW_SHOW);
SetFocus (hBreak);
ShowWindow (hEdit, SW_HIDE);
ShowWindow (hExec, SW_HIDE);
ResetEvent (hevBreak);
hThread = (HANDLE) _beginthreadex (NULL, 0, Exec, NULL, 0, &tid);
CloseHandle (hThread);
break;
A execução em si é feita pela rotina
Exec. De forma resumida, ela pega na memória a instrução, separa as várias partes e usa switches para decodificar as várias combinações. O trecho abaixo está simplificado, veja no fonte a rotina completa.
- static unsigned __stdcall Exec (void *pParam)
- {
- int instr, op, ind, addr, val;
- BOOL fCont, fErro;
- HANDLE tbEvt [2];
-
- fCont = TRUE;
- fErro = FALSE;
- while (fCont && !fErro)
- {
-
- instr = mem [cp++];
- if (cp > 99)
- cp = 0;
-
-
- ind = instr / 1000;
- op = (instr / 100) % 10;
- addr = instr % 100;
-
-
- switch (ind)
- {
- case 0:
- val = mem [addr];
- break;
- case 1:
- addr = mem [addr] % 100;
- val = mem [addr];
- break;
- case 2:
- default:
- val = addr;
- break;
- }
- if ((ind == 2) && (cTipoEnder [op] != '2'))
- {
- fErro = TRUE;
- break;
- }
-
-
- switch (op)
- {
- case 0:
- ac = val;
- break;
- case 1:
- mem [addr] = ac;
- nPosMem = addr;
- break;
-
-
- }
-
- if (fCont)
- {
-
- fCont = WaitForSingleObject (hevBreak, nVeloc) == WAIT_TIMEOUT;
- }
- }
-
- nModoAtual = MODO_CMD;
- return fErro;
- }
static unsigned __stdcall Exec (void *pParam)
{
int instr, op, ind, addr, val;
BOOL fCont, fErro;
HANDLE tbEvt [2];
fCont = TRUE;
fErro = FALSE;
while (fCont && !fErro)
{
// Pega a instrucao e avanca contador ("fetch")
instr = mem [cp++];
if (cp > 99)
cp = 0;
// Decodifica a instrução ("decode")
ind = instr / 1000;
op = (instr / 100) % 10;
addr = instr % 100;
// Decodifica o endereçamento
switch (ind)
{
case 0: // direto
val = mem [addr];
break;
case 1: // indireto
addr = mem [addr] % 100;
val = mem [addr];
break;
case 2: // imediato
default:
val = addr;
break;
}
if ((ind == 2) && (cTipoEnder [op] != '2'))
{
fErro = TRUE;
break;
}
// Executa a instrucao
switch (op)
{
case 0: // LDA
ac = val;
break;
case 1: // STA
mem [addr] = ac;
nPosMem = addr;
break;
// etc
}
if (fCont)
{
// testa break e controla a velociadade
fCont = WaitForSingleObject (hevBreak, nVeloc) == WAIT_TIMEOUT;
}
}
nModoAtual = MODO_CMD;
return fErro;
}
Dois eventos, cujos
handles são mantidos em hevBreak e hevInput são usados para sincronizar a execução com a UI.
Quem quiser ver um exemplo bem sofisticado de simulação de computador pode examinar os fontes do POSE (Palm OS Emulator), que nomento podem ser baixados
daqui.