Search

terça-feira, 15 de abril de 2014

Unicode vs ANSI Char

Ansi char e unicode

Hoje em dia a localização é uma obrigação para o sucesso de um aplicativo.

Um problema real na localização é a manipulação de uma variedade de entradas diferentes do usuário.
Antigamente era natural a manipulação de strings ser realizada por uma array do tipo char terminadas com zero. Acontece que alguns países possuem um sistema de escrita com milhares de caracteres( china e japão por exemplo ) e o padrão Ansi suporta apenas 256 caracteres, e cada algarismo ocupa 1 byte.

O padrão UNICODE

Nas versões mais novas do windows, todo algarismo é representado no formato UNICODE. Cada algarismo no formato UNICODE possui o formato UTF-16, nesse padrão cada algarismo possui 2 bytes.

O UTF-8 encoda alguns algarismos com 1 byte, alguns algarismos com 2 bytes, alguns com 3 bytes e alguns com 4 bytes. Algarismos com o valor abaixo de 0x0080 possuem 1 byte e tem seu uso em textos escritos em inglês, algarismos com o valor entre 0x0080 e 0x07FF possuem 2 bytes, são usualmente textos europeus, algarismos com o valor maior que 0x0800 usam 3 bytes normalmente textos asiáticos.


UTF-32 Encoda todos os algarismos com 4 bytes cada um. tornando a programação para esse tipo de text-encode mais simples.

Exemplo de um Texto ANSI usando 1 byte por algarismo
char alg = 'a';
Exemplo de um array de caracteres formando uma string;
char szTexto[10] = "um Texto.";

O visual Studio considera que o tipo wchar_t representa o UTF-16, como esse tipo não existia em alguns compiladores antigos, para usar ele em código legado devemos delara-lo:

typedef unsigned short wchar_t;
Mas devemos fazer isso apenas em códigos muito antigos.

Aqui o exemplo de um Texto escrito com UTF-16
wchar_t alg = L'a'; // wchar siginifica wide char

Um simples array:
wchar_t sztexto[10] = L"Um texto.";

A letra L antes do texto indica al compilador o valor literal da variável, indicando que ela deve ser compilada como UNICODE.

No visual Studio existe uma opção nas propriedades do projeto para informar ao compilador qual o padrão será adotado.

Podemos observar no código abaixo como o Visual Studio trata essa diferença em tempo de execução:

#ifdef UNICODE

typedef WCHAR TCHAR, *PTCHAR, PTSTR;
typedef CONST WCHAR *PCTSTR;
#define __TEXT(quote) quote          // r_winnt

#define __TEXT(quote) L##quote

#else

typedef CHAR TCHAR, *PTCHAR, PTSTR;
typedef CONST CHAR *PCTSTR;
#define __TEXT(quote) quote

#endif

Bom sempre utilizaremos o padrão UNICODE em todos os exemplos deste blog. Pois assim teremos a certeza de que o projeto vai funcionar em qualquer cultura escolhida pelo usuário. Falaremos mais tarde sobre o padrão stl::string

segunda-feira, 14 de abril de 2014

Win32 Hello World!!

Imaginando que o leitor já conheça uma aplicação console escrita em c ou c++, vamos estudar um pouco uma aplicação C++ Win32 básica e compara-la a uma aplicação Console.

Aplicação Console: método main()

#include "stdio.h"
int main ()
{
   printf ("hello, world\n") ;
   return 0 ;
}
Ou então
#include "stdafx.h"
#include "iostream.h"
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
 cout << "Hello World";
 return 0;
}





Para criar uma aplicação Console, abra o Visual Studio, no menu file-> New Project -> Na Aba Visual C++ escolha Win32 Console Application







Esse será o resultado da execução do código.






O equivalente em Win32

Segue abaixo um código bem básico utilizando Win32

#include "windows.h"
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    MessageBox (NULL, TEXT ("Hello world"), TEXT ("Hello Program"), 0) ;
    return 0 ;
}

A função WinMain é a função de entrada para um programa Win32.

Podemos notar algumas diferenças entre um programa utilizando a WinAPI e uma aplicação console;


 A função WinMain sempre terá sempre 4 parametros:
 int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance,LPWSTR cmdLine, int cmdShow );

HINSTANCE hInstance -> É o handle(*) para a instancia da aplicação;
HINSTANCE prevInstance -> É o handle da instancia anterior da aplicação, segundo MSDN deverá ser sempre NULL. Nas versões mais antigas do windows, quando você rodava o mesmo programa, ao mesmo tempo, você poderia compartilhar recursos da memória.
LPWSTR cmdLine(LPSTR se for ANSICHAR) -> Linhas de comando para a aplicação, podemos passar parametros de como a janela será iniciada, ou qualquer outra coisa que quisermos informar.

(*)Um handle é um número usando pelo windows para identificar alguma coisa, pode ser um componente, um processo até mesmo uma janela.

No caso de hInstance esse número representa um programa.


A aplicação padrão Win32

Criando a instancia de uma janela(windows).


Quando criamos um projeto do windows no Visual Studio, ele nos fornece o código abaixo para a criação de uma janela padrão:
// Comment
#include "stdafx.h"
#include "FirstWindow.h"

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;        // current instance
TCHAR szTitle[MAX_LOADSTRING];     // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];   // the main window class name

// Forward declarations of functions included in this code module:
ATOM    MyRegisterClass(HINSTANCE hInstance);
BOOL    InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);

Podemos notar que ele cria três variáveis(hInst,szTitle e szWindowClass ).
hInst -> Variável global para armazenar o handle da instância da nossa aplicação.
szTitle -> Array de caracteres wchar_t(UNICODE) que dará nome a nossa aplicação.
szWindowClass -> Arra de wchar_t que dará nome a nossa primeira janela.

E quatro funções(MyRegisterClass,InitInstance,WndProc e About).

MyRegisterClass-> É usada para instanciar a minha classe WNDCLASSEX, usada para descrever algumas propriedades da minha janela.

InitInstance-> Aqui é feita a chamada para a função CreateWindow, onde minha janela é criada.

WndProc-> Este ponto é muito importante, pois, é onde processamos todas as mensagens passadas pelo sistema operacional,mouse, teclado etc ... para o handle da nossa janela.

About-> É o callback de uma janela secundária.

Vamos falar um pouco sobre essa abordagem da MS para a criação deste projeto genérico de win32 e porque ele não é o mais apropriado para uma game engine.

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
 UNREFERENCED_PARAMETER(hPrevInstance);
 UNREFERENCED_PARAMETER(lpCmdLine);//Previne algumas Warnings Messages do compilador.
//Isso acontece quando algum parâmetro não é usado no corpo da função.
//Apenas uma boa prática de programação, pois assim podemos compilar nosso código sem warnings por parte do VS.


  // TODO: Place code here.
 MSG msg;
 HACCEL hAccelTable;

 // Initialize global strings
 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
 LoadString(hInstance, IDC_FIRSTWINDOW, szWindowClass, MAX_LOADSTRING);
 MyRegisterClass(hInstance);
//Dá nome a nossa aplicação e a nossa primeira janla


 // Perform application initialization:
 if (!InitInstance (hInstance, nCmdShow))
 {
  return FALSE;
 }

 hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FIRSTWINDOW));

 // Main message loop:
 while (GetMessage(&msg, NULL, 0, 0))
 {
  if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
  {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
 }

 return (int) msg.wParam;
}
Acima podemos verificar nosso método main( wWinMain ) com alguns comentários, mas minha principal observação é na função GetMessage.

Uma alternativa ao GetMessage é a função PeekMessage, muitas aplicações Win32 usam GetMessage, o problema é que ao usar GetMessage ficamos bloqueados dentro laço esperando por uma mensagem.

while (GetMessage(...) > 0)
{
 TranslateMessage();
 DispatchMessage();
}

Enquanto a função PeekMessage não, essa função consulta a queue de mensagens, ela retorna a primeira mensagem da fila, se não houver nenhuma mensagem ela retorna NULL.

while (WM_QUIT != uMsg.message)
{
     while (PeekMessage (&uMsg, NULL, 0, 0, PM_REMOVE) > 0) //Or use an if statement
     {
          TranslateMessage (&uMsg);
          DispatchMessage (&uMsg);
     }
}

O processamento de mensagens é uma parte crítica da programação Win32, através dela nos comunicamos com o Sistema Operacional, mouse, teclado etc... quando uma aplicação não responde as mensagens ao sistema operacional, o sistema informa ao usuário que a aplicação não está respondendo. O usuário por sua vez acredita que a aplicação travou, ou que algum erro ocorreu. Fazemos isso através do PeekMessage, TranslateMessage e DispatchMessage;

ATOM MyRegisterClass(HINSTANCE hInstance)
{
 WNDCLASSEX wcex;

 wcex.cbSize = sizeof(WNDCLASSEX);

 wcex.style   = CS_HREDRAW | CS_VREDRAW;
 wcex.lpfnWndProc = WndProc;
 wcex.cbClsExtra  = 0;
 wcex.cbWndExtra  = 0;
 wcex.hInstance  = hInstance;
 wcex.hIcon   = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FIRSTWINDOW));
 wcex.hCursor  = LoadCursor(NULL, IDC_ARROW);
 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
 wcex.lpszMenuName = MAKEINTRESOURCE(IDC_FIRSTWINDOW);
 wcex.lpszClassName = szWindowClass;
 wcex.hIconSm  = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

 return RegisterClassEx(&wcex);
}

WNDCLASSEX é a classe que descreve algumas propriedades de uma janela, está ligada a instância da nossa aplicação. Na propriedade lpfnWndProc é onde definimos qual será nossa função callback descrita mais abaixo.

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

    Aqui criamos a nossa janela, uma das coisas que eu adicionaria a este sample da microsoft seria mudar a variável local hwnd para global, assim eu declararia ela no início do programa juntamente com as outras três variáveis comentadas no início do artigo.

// Global Variables:
g_hwnd = NULL;  // global windows handle

HINSTANCE hInst; // current instance TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 int wmId, wmEvent;
 PAINTSTRUCT ps;
 HDC hdc;

 switch (message)
 {
 case WM_COMMAND:
  wmId    = LOWORD(wParam);
  wmEvent = HIWORD(wParam);
  // Parse the menu selections:
  switch (wmId)
  {
  case IDM_ABOUT:
   DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
   break;
  case IDM_EXIT:
   DestroyWindow(hWnd);
   break;
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
  }
  break;
 case WM_PAINT:
  hdc = BeginPaint(hWnd, &ps);
  // TODO: Add any drawing code here...
  EndPaint(hWnd, &ps);
  break;
 case WM_DESTROY:
  PostQuitMessage(0);
  break;
 default:
  return DefWindowProc(hWnd, message, wParam, lParam);
 }
 return 0;
}

A função WinProc( windows procedure ) trata e processa as mensagens recebidas pela nossa janela,
Podemos observar que ela possui os mesmos parametros de uma mensagem, O primeiro é o handle da janela retornado na função CreateWindow, o segundo parametro identifica a mensagem, os ultimos dois são so parametros da mensagem.

Vamos imaginar que o usuário mexa o mouse, nossa janela vai receber uma mensagem do sistema operacional identificada como WM_MOUSEMOVE, para sabermos onde o mouse se encontra no eixo x e y devemos então obter os parametros da mensagem:
xPos = GET_X_LPARAM(lParam); 
yPos = GET_Y_LPARAM(lParam); 



Uma defProc bem básica abaixo demonstra por si só uma esqueleto bem básico:
switch (iMsg)
{
case WM_CREATE :
return 0 ;
case WM_PAINT :
return 0 ;
case WM_DESTROY :
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

Podemos criar nossas próprias mensagens e processa-las em nossas janelas abaixo um exemplo:
#define MY_MSG_NOVO_DOCUMENTO (WM_USER+1)
mas falaremos disso em uma postagem mais adiante.

INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
 UNREFERENCED_PARAMETER(lParam);
 switch (message)
 {
 case WM_INITDIALOG:
  return (INT_PTR)TRUE;

 case WM_COMMAND:
  if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
  {
   EndDialog(hDlg, LOWORD(wParam));
   return (INT_PTR)TRUE;
  }
  break;
 }
 return (INT_PTR)FALSE;
}
Quando compilamos e rodamos o código padrão do Visual Studio para uma aplicação Windows, podemos observar no defproc da nossa janela que ela trata um clique no menu, para chamar uma janela about:
  case IDM_ABOUT:
   DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
   break;
Neste momento criamos uma janela secundária, ela foi confeccionada anteriormente e ficou armazenada nos recursos do programa. Note que passamos o hWnd Owner, ou seja a janela pai, e o método About, que se trata do defproc para essa janela. que é tratado separadamente.