sexta-feira, janeiro 23, 2009

Simulando Um Computador - 4/4

Nesta última parte vamos dar uma examinada no código fonte do simulador, que pode ser baixado daqui.

Estrutura Básica

O 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:
  1. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst,  
  2.                  LPSTR lpCmdLine, int nCmdShow)  
  3. {  
  4.  HACCEL hAccel;  
  5.  MSG msg;  
  6.   
  7.  // Iniciacoes  
  8.  hInst = hInstance;  
  9.  InitComp ();  
  10.  nModoAtual = MODO_CMD;  
  11.  hevBreak = CreateEvent (NULL, FALSE, FALSE, NULL);  
  12.  hevInput = CreateEvent (NULL, FALSE, FALSE, NULL);  
  13.   
  14.  // Cria a janela principal  
  15.  if (! CriaJanela())  
  16.      return 1;  
  17.   
  18.  // Apresenta a janela  
  19.  ShowWindow (hMainWnd, nCmdShow == SW_SHOWMAXIMIZED ? SW_SHOW :  
  20.                  nCmdShow);  
  21.  UpdateWindow (hMainWnd);  
  22.   
  23.  // Trata as mensagens  
  24.  hAccel = LoadAccelerators (hInst, MAKEINTRESOURCE (IDR_ACCEL));  
  25.  while (GetMessage (&msg, NULL, 0, 0))  
  26.  {  
  27.      // Usa accelerator para converter Enter em Exec  
  28.      if (!TranslateAccelerator (hMainWnd, hAccel, &msg))  
  29.          TranslateMessage (&msg);  
  30.      DispatchMessage (&msg);  
  31.  }  
  32.   
  33.  // Limpeza final  
  34.  CloseHandle (hevBreak);  
  35.  CloseHandle (hevInput);  
  36.  return 0;  
  37. }  
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:
  1. LRESULT CALLBACK MainWinProc (HWND hwnd, UINT message,  
  2.                            WPARAM wParam, LPARAM lParam)  
  3. {  
  4. char szCmd [40];                // comando  
  5. PAINTSTRUCT ps;  
  6. HDC  hdc;  
  7.   
  8.   
  9. switch (message)  
  10. {  
  11.  case WM_CREATE:  
  12.      // salva o handle, cria o fonte de letra e acerta o tamanho  
  13.      hMainWnd = hwnd;  
  14.      CriaFonte ();  
  15.      MoveWindow (hwnd, 0, 0, 90*nCxCar, 32*nCyCar, FALSE);  
  16.   
  17.      // cria controles auxiliares e os inicia  
  18.      hEdit = CreateWindow ("edit""",  
  19.                      WS_VISIBLE | WS_CHILD | WS_BORDER | ES_UPPERCASE,  
  20.                      2*nCxCar, 28*nCyCar, 72*nCxCar, nCyCar+2,  
  21.                      hwnd, (HMENU) IDC_INPUT, hInst, 0);  
  22.      hExec = CreateWindow ("button""Exec",  
  23.                      WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  
  24.                      76*nCxCar, 28*nCyCar-3, 7*nCxCar, nCyCar+6,  
  25.                      hwnd, (HMENU) IDC_EXEC, hInst, 0);  
  26.      hBreak = CreateWindow ("button""Break",  
  27.                      WS_CHILD | BS_PUSHBUTTON,  
  28.                      76*nCxCar, nCyCar-3, 7*nCxCar, nCyCar+6,  
  29.                      hwnd, (HMENU) IDC_BREAK, hInst, 0);  
  30.      SendMessage (hEdit,  EM_SETLIMITTEXT, sizeof(szCmd)-1, 0);  
  31.      SendMessage (hEdit,  WM_SETFONT, (WPARAM) hFont, 0);  
  32.      SendMessage (hExec,  WM_SETFONT, (WPARAM) hFont, 0);  
  33.      SendMessage (hBreak, WM_SETFONT, (WPARAM) hFont, 0);  
  34.      return 0;  
  35.   
  36.  case WM_SETFOCUS:  
  37.    // Deixar foco sempre no editbox  
  38.    if (hEdit != NULL)  
  39.        SetFocus (hEdit);  
  40.    return 0;  
  41.   
  42.  case WM_PAINT:  
  43.    if (! IsIconic (hwnd))  
  44.    {  
  45.      // Refaz a tela  
  46.      hdc = BeginPaint (hwnd, &ps);  
  47.      AtlReg (hdc);  
  48.      AtlMem (hdc);  
  49.      AtlDisplay (hdc);  
  50.      Copyright (hdc);  
  51.      EndPaint (hwnd, &ps);  
  52.    }  
  53.    return 0;  
  54.   
  55.  case WM_COMMAND:        // Botoes  
  56.    switch (LOWORD(wParam))  
  57.    {  
  58.      case IDC_EXEC:        // Trata texto digitado  
  59.          SendMessage (hEdit, WM_GETTEXT, sizeof (szCmd), (LPARAM) szCmd);  
  60.          TrataDigitacao (szCmd);  
  61.          SendMessage (hEdit, WM_SETTEXT, 0, (LPARAM"");  
  62.          SetFocus (hEdit);  
  63.          return 0;  
  64.   
  65.      case IDC_BREAK:        // Interrompe o programa  
  66.          SetEvent (hevBreak);  
  67.          SetFocus (hEdit);  
  68.          return 0;  
  69.    }  
  70.    break;  
  71.   
  72.  case WM_CLOSE:  
  73.    // Destroi controles e a janela  
  74.    if (hEdit != NULL)  
  75.        DestroyWindow (hEdit);  
  76.    if (hExec != NULL)  
  77.        DestroyWindow (hExec);  
  78.    if (hBreak != NULL)  
  79.        DestroyWindow (hBreak);  
  80.    DestroyWindow (hwnd);  
  81.    DeleteObject (hFont);  
  82.    return 0;  
  83.   
  84.  case WM_DESTROY:  
  85.    // Encerra o programa  
  86.    PostQuitMessage (0);  
  87.    return 0;  
  88. }  
  89.   
  90. // Demais mensagens vao para a rotina padrao  
  91. return DefWindowProc (hwnd, message, wParam, lParam);  
  92. }  
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 Computador

Como o COMP é muito simples, a sua representação também é:
  1. static int ac;            // acumulador  
  2. static int cy;            // "sobrecarga" (carry)  
  3. static int cp;            // contador de programa  
  4. static int mem [100];    // memoria  
  5.   
  6. #define    NLIN_DISP    11  
  7. #define NCOL_DISP    80  
  8.   
  9. static char szDisplay [NLIN_DISP] [NCOL_DISP+1];    // display do computador  
  10. static int  nLinDisp;                                // linha do cursor do display  
  11. static int  nColDisp;                                // coluna do cursor do display  
  12.   
  13. // Modo atual do computador  
  14. static enum  
  15. {  
  16.  MODO_CMD,            // aguardando um comando  
  17.  MODO_ASM,            // executando comando Asm  
  18.  MODO_EDIT,            // executando comando Edit  
  19.  MODO_RUN,            // executando comando Run  
  20.  MODO_INPUT            // aguradando INPUT no comando Run  
  21. } 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 Monitor

Inicialmente 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.
  1. static void TrataCmd (char *szCmd)  
  2. {  
  3.  unsigned tid;  
  4.  HANDLE hThread;  
  5.  char szAux [10];  
  6.  int i;  
  7.   
  8.  // pula espacos iniciais  
  9.  for (i = 0; *szCmd == ' '; i++)  
  10.      ;  
  11.  if (szCmd[i] == 0)    // ignora linha vazia  
  12.      return;  
  13.   
  14.  // coloca a linha digitada no display  
  15.  Display (szCmd, TRUE, TRUE);  
  16.   
  17.  // primeira letra deve ser comando  
  18.  switch (szCmd[i++])  
  19.  {  
  20.      case 'A':        // Asm  
  21.          nPosAtl = PegaValor (szCmd, &i, 100);  
  22.          wsprintf (szAux, "%02d: ", nPosAtl);  
  23.          Display (szAux, FALSE, TRUE);  
  24.          nModoAtual = MODO_ASM;  
  25.          break;  
  26.   
  27.      case 'C':        // Clear  
  28.          InitComp ();  
  29.          RefazTela ();  
  30.          break;  
  31.   
  32.      case 'E':        // Edit  
  33.          nPosAtl = PegaValor (szCmd, &i, 100);  
  34.          wsprintf (szAux, "%02d: ", nPosAtl);  
  35.          Display (szAux, FALSE, TRUE);  
  36.          nModoAtual = MODO_EDIT;  
  37.          break;  
  38.   
  39.      case 'L':        // Load  
  40.          Load ();  
  41.          Display (">", TRUE, TRUE);  
  42.          break;  
  43.   
  44.      case 'R':        // Run  
  45.          cp = PegaValor (szCmd, &i, 100);  
  46.          nVeloc = PegaValor (szCmd, &i, 100) * 100;  
  47.          nModoAtual = MODO_RUN;  
  48.          ShowWindow (hBreak, SW_SHOW);  
  49.          SetFocus (hBreak);  
  50.          ShowWindow (hEdit, SW_HIDE);  
  51.          ShowWindow (hExec, SW_HIDE);  
  52.          ResetEvent (hevBreak);  
  53.          hThread = (HANDLE) _beginthreadex (NULL, 0, Exec, NULL, 0, &tid);  
  54.          CloseHandle (hThread);  
  55.          break;  
  56.   
  57.      case 'S':        // Save  
  58.          Save ();  
  59.          Display (">", TRUE, TRUE);  
  60.          break;  
  61.   
  62.      default:  
  63.          Display ("Comando Invalido!", TRUE, TRUE);  
  64.          break;  
  65.  }  
  66. }  
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 Janela

Conforme 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:
  1. static void Print (HDC hdc, int lin, int col, COLORREF cor, char *szTexto)  
  2. {  
  3.  HFONT hOldFont;  
  4.   
  5.  hOldFont = (HFONT) SelectObject (hdc, hFont);  
  6.  SetTextColor (hdc, cor);  
  7.  SetBkColor (hdc, RGB(255,255,255));  
  8.  SetBkMode (hdc, OPAQUE);  
  9.  TextOut (hdc, col*nCxCar, lin*nCyCar, szTexto, strlen (szTexto));  
  10.  SelectObject (hdc, hOldFont);  
  11. }  
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:
  1. static void Copyright (HDC hdc)  
  2. {  
  3.  static char szTexto[] = "(C) 2004, Daniel Quadros";  
  4.  HFONT hOldFont;  
  5.   
  6.  hOldFont = (HFONT) SelectObject (hdc, hFont);  
  7.  SetBkMode (hdc, TRANSPARENT);  
  8.  SetTextColor (hdc, RGB (255, 255, 255));  
  9.  TextOut (hdc, 59*nCxCar+1, 26*nCyCar+6, szTexto, strlen (szTexto));  
  10.  SetTextColor (hdc, RGB (0, 0, 0));  
  11.  TextOut (hdc, 59*nCxCar, 26*nCyCar+5, szTexto, strlen (szTexto));  
  12.  SelectObject (hdc, hOldFont);  
  13. }  
Executando um programa

A 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:
  1. case 'R':        // Run  
  2.  cp = PegaValor (szCmd, &i, 100);  
  3.  nVeloc = PegaValor (szCmd, &i, 100) * 100;  
  4.  nModoAtual = MODO_RUN;  
  5.  ShowWindow (hBreak, SW_SHOW);  
  6.  SetFocus (hBreak);  
  7.  ShowWindow (hEdit, SW_HIDE);  
  8.  ShowWindow (hExec, SW_HIDE);  
  9.  ResetEvent (hevBreak);  
  10.  hThread = (HANDLE) _beginthreadex (NULL, 0, Exec, NULL, 0, &tid);  
  11.  CloseHandle (hThread);  
  12.  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.
  1. static unsigned __stdcall Exec (void *pParam)  
  2. {  
  3.  int instr, op, ind, addr, val;  
  4.  BOOL fCont, fErro;  
  5.  HANDLE tbEvt [2];  
  6.   
  7.  fCont = TRUE;  
  8.  fErro = FALSE;  
  9.  while (fCont && !fErro)  
  10.  {  
  11.      // Pega a instrucao e avanca contador ("fetch")  
  12.      instr = mem [cp++];  
  13.      if (cp > 99)  
  14.          cp = 0;  
  15.   
  16.      // Decodifica a instrução ("decode")  
  17.      ind = instr / 1000;  
  18.      op = (instr / 100) % 10;  
  19.      addr = instr % 100;  
  20.   
  21.      // Decodifica o endereçamento  
  22.      switch (ind)  
  23.      {  
  24.          case 0:        // direto  
  25.              val = mem [addr];  
  26.              break;  
  27.          case 1:        // indireto  
  28.              addr = mem [addr] % 100;  
  29.              val = mem [addr];  
  30.              break;  
  31.          case 2:        // imediato  
  32.          default:  
  33.              val = addr;  
  34.              break;  
  35.      }  
  36.      if ((ind == 2) && (cTipoEnder [op] != '2'))  
  37.      {  
  38.          fErro = TRUE;  
  39.          break;  
  40.      }  
  41.   
  42.      // Executa a instrucao  
  43.      switch (op)  
  44.      {  
  45.          case 0:        // LDA  
  46.              ac = val;  
  47.              break;  
  48.          case 1:        // STA  
  49.              mem [addr] = ac;  
  50.              nPosMem = addr;  
  51.              break;  
  52.   
  53.          // etc  
  54.      }  
  55.   
  56.      if (fCont)  
  57.      {  
  58.          // testa break e controla a velociadade  
  59.          fCont = WaitForSingleObject (hevBreak, nVeloc) == WAIT_TIMEOUT;  
  60.      }  
  61.  }  
  62.   
  63.  nModoAtual = MODO_CMD;  
  64.  return fErro;  
  65. }  
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.

Um comentário:

Anônimo disse...

O link do simulador esta fora do ar.