INTRODUÇÃO

Na ciranda das linguagens de programação, a linguagem C permanece firme e forte. Não é uma linguagem orientada a objeto, mas você pode pode fazer uma programação orientada a objeto se assim o desejar. Não é uma linguagem funcional, mas você pode programar funcionalmente se quiser. Como dá para perceber, a C não impõe qualquer ponto de vista. Além disso, possui todas as características de uma linguagem de programação de uso geral, como recursividade, procedimentos como tipos de dados de primeira classe e muito mais.

Muitos acham que a C não tem a simplicidade da Java ou a sofisticação da C++. É verdade. C é uma linguagem simples, sem muitas frescuras. Mas é justamente esta simplicidade que faz com que se possa controlar o que os programas realizam sem ter que se preocupar com qualquer característica escondida. O compilador não fará nada mais do que se tenha determinado. A linguagem permanece transparente, mesmo quando se implementa características mais sofisticadas, do tipo da "garbage collection" típica da Java.

As linguagens surgem e desaparecem, mas a C permanece. Foi o coração do desenvolvimento do sistema operacional UNIX nos anos 70, foi o centro da revolução dos microcomputadores nos anos 80 e, quando surgiram a C++, Delphi, Java e muitas outras, a linguagem C continuou fiel à sua própria natureza.

Minha experiência pessoal talvez sirva de exemplo: sabendo C, aprender qualquer outra linguagem fica fácil, ou seja, quando se domina uma língua, aprender um dialeto é bico. Mesmo se você não tiver a mínima noção de como programar, ainda assim vale a pena seguir este tutorial. Criança também aprende a falar ;)))))

A organização de programas C

Um programa C é composto por funções, isto é, porções menores de código que realizam determinadas tarefas, e por dados, ou seja, variáveis ou tabelas que são inicializadas antes do início do programa. Existe uma função especial denominada main, onde a execução do programa se inicia. As funções são organizadas em módulos agrupados nos arquivos fonte. Em C, a organização do código em arquivos tem um significado semântico. O arquivo fonte principal, passado como argumento para o compilador, define uma unidade de compilação.

Uma unidade pode importar definições usando a diretiva #include ou apenas declarando algum identificador como externo.

C permite um modelo de compilação separado, isto é, você pode dividir o programa em várias unidades independentes que são compiladas separadamente e depois são encadeadas com o link editor para construir o programa final. Normalmente, cada módulo é escrito num arquivo texto separado que contém funções ou declarações de dados. As interfaces entre os módulos são escritas em "header files" (arquivos de cabeçalho) que descrevem tipos ou funções visíveis a vários módulos do programa. Estes arquivos possuem a extensão ".h" e são de dois tipos: privados, específicos da aplicação que está sendo elaborada, e para o sistema.

Toda função possui uma lista de parâmetros, um corpo e, eventualmente, um valor de retorno. O corpo pode conter declarações de variáveis locais, ou seja, variáveis que são ativadas quando a execução alcançar o corpo da função.

Exemplo de programa

Ficando na mesmice de sempre, o primeiro exemplo se refere ao código de um programa que colocará na tela "Informática NumaBoa".

#include <stdio.h>                                   (1)
int main(void)                                (2)
{                                             (3)
        printf("Informática NumaBoa\n");              (4)
        return 0;                                     (5)
}                                             (6)
  1. Usando a característica do compilador chamada de "pre-processamento", incluímos textualmente um arquivo completo de fonte C com a diretiva "#include". Neste exemplo incluímos o arquivo cabeçalho "stdio.h", que faz parte das inclusões padrão do compilador.
  2. Definimos uma função chamada "main" que retorna o inteiro 0 (zero) e que não recebe argumentos (void).
  3. O corpo da função é composto por declarações delimitadas pelos colchetes { }
  4. Chamamos a função padrão "printf" que formata seus argumentos numa string de caracteres que é mostrada na tela. Uma chamada de função em C é escrita da seguinte maneira: nome-da-função ( lista-de-argumentos ). Neste exemplo, o nome da função é "printf" e sua lista de argumentos é a string "Informática NumaBoa\n". Strings são delimitadas por aspas duplas. Em C, são representadas como um array de caracteres terminados por um byte sero.
  5. A declaração "return" indica que o controle deve ser devolvido à função chamadora. Opcionalmente é possível especificar uma valor de retorno que, neste caso, é 0.
  6. O colchete terminador "}" indica o fim da área da função.
Programas em C são definidos em arquivos texto que normalmente possuem a extensão ".c".

Proposta de trabalho

Como em todos os outros tutoriais que escrevi, a proposta é aprender com exemplos práticos - e este tutorial não poderia ser diferente. Escolhi como ferramenta de trabalho um software gratuito, ao alcance de todos e de excelente qualidade.

Há muito tempo atrás esbarrei no lcc-win32 e, para minha surpresa, esta excelente plataforma de desenvolvimento continua sendo atualizada. Os autores do lcc são Chris Fraser e Dave Hanson. Jacob Navia escreveu o sistema de compilação lcc-win32 a partir do compilador lcc. O lcc-win32 não é um software de domínio público ou shareware e tampouco é protegido por copyleft, mas é livre para fins de instrução e para pesquisa pessoal.

Você encontra o software para download em www.cs.virginia.edu/~lcc-win32 e na Q software solutions. Faça o download nos endereços indicados para garantir que seja a versão mais atualizada.

O pacote contém instalador e desinstalador, além de alguns aplicativos interessantes. O mais importante deles é o Wedit, que será a ferramenta principal deste tutorial. Tenho certeza de que você ficará admirado com os resultados que pode obter sem muito esforço e em tempo recorde!

O PRIMEIRO PROJETO

Após fazer o download do lcc-win32, a instalação deste sistema de compilação é muito tranquila - basta indicar o diretório desejado, o resto fica por conta do instalador. Como todo software de qualidade, possibilita inserir uma entrada no menu de programas e possui um desinstalador.

Com o lcc-win32 à disposição, clique no item lcc-win32 do menu de programas ou chame diretamente o Wedit que se encontra no subdiretório bin. Em todos os módulos do tutorial, os itens de menu serão indicadas entre barras - por exemplo |File|. Quando a opção for um item de submenu, a indicação será |File/New|. Botões são indicados por chaves, por exemplo [Browse].

Criando um projeto

Para criar um novo projeto clique em |File/New/Project|. A caixa de diálogo "Definition of a new project" é apresentada e algumas indicações precisam ser feitas:

  1. Clique no campo "Name of the project" e digite o nome do projeto, por exemplo TESTE.
  2. Clique em [Browse] para abrir a janela de navegação de diretórios.
  3. Escolha ou crie com [Make new folder] o diretório onde queira guardar o projeto TESTE (sugiro que você crie um subdiretório TESTE no diretório /lcc/projects) e clique em [OK].
  4. O campo "Sources working directory" indica sua escolha (digamos, C:\lcc\projects\TESTE) e o campo seguinte, "Objects and executables", é preenchido automaticamente (C:\lcc\projects\TESTE\lcc).
  5. Em "Options" assinale apenas "Single user".
  6. Em "Type of project" assinale "Console Application".
  7. Clique em [Create] para obter o diálogo "Do you want to use the wizard to generate the application skeleton?" (Quer usar o wizard para gerar o esqueleto do aplicativo?). Clique em [No].
  8. Na janela "Add source files to project" digite teste1.c no campo "File name" e clique em [Open].
  9. Na janela "Source files for project" selecione teste1.c e clique em [OK].
  10. É apresentada a janela "Compiler settings" - clique em [Next]. A seguir vem a janela "Linker settings" - clique também em [Next]. Segue-se a janela "Debugger settings" - clique em [Finish].
Finalmente é apresentada a janela de edição onde é possível escrever o código de teste1.c

Um programa de console

O primeiro exemplo se refere ao código de um programa que colocará na tela "Informática NumaBoa", conforme já explicado na Introdução. Como optamos por um programa de console, o mesmo rodará num shell do DOS (aquela telinha preta). Digite na janela de edição o exemplo abaixo:

#include <stdio.h>
int main(void)
{
        printf("Informática NumaBoa\n");
        return 0;
}

Note o texto destacado em cores, a indentação automática quando se digita os colchetes e a indicação de sintaxe quando se digita a função printf. NÃO SE ESQUEÇA de terminar as linhas de comando com ponto e vírgula - este é o caracter terminador!

  1. O asterisco no título da janela de edição (TESTE.c*) indica que o projeto TESTE.c foi alterado. Clique em |File/Save| ou digite Ctrl+S para salvar as alterações.
  2. Clique em |Compiler/Compile TESTE.c|. Se o programa não contiver erros, o rodapé do aplicativo apresentará "teste1.exe built successfully.(0.3 sec)."
  3. Clique em |Compiler/Execute teste1.exe| ou digite Ctrl+F5 para executar o programa. Se tudo correu bem, observe abaixo o resultado obtido.

Observações da vovó Vicki

Viu só, não foi tão difícil assim. Se você é da turma que não entende chongas de linguagem de programação, foi a glória ver esta telinha preta - diz que não foi :))) Se você faz parte dos iniciados e está tentando virar poliglota, tenho certeza de que também foi um bom começo. Aproveitando o entusiasmo inicial, só mais umas coisinhas:

O arquivo stdio.h incluído no início do programa contém um caminhão de informações para o pre-processador. Ao invés de escrever todas estas informações no código fonte do nosso arquivo teste1.c, pedimos simplesmente para que ele fosse incluído (daí #include - êta mordomia!). O nome do arquivo vem de standard input/output, ou seja, entrada/saída padrão. Como precisamos de uma saída para a tela, precisamos do stdio.h. Aliás, você vai cansar de usar este #include.

Se você tiver curiosidade, o arquivo stdio.h se encontra no diretório /lcc/include/. É só abrí-lo, por exemplo com o próprio Wedit, para poder dar uma olhada. Procure pela referência à função printf que, claro, está neste arquivo. Afinal, esta função não apareceu do nada, né não? Tome cuidado para não alterá-lo, senão é caca na certa quando for usá-lo na próxima vez.

 

A PRIMEIRA JANELA

O primeiro projeto tem gosto de "quero mais" e, geralmente, o quero mais se refere a um programa Windows. Acontece que estamos apenas no terceiro módulo do tutorial e não é aconselhável queimar etapas. Então, o jeito é partir para a janelinha mais simples que existe: uma caixa de diálogo.

Apesar de simples, muitos conceitos serão abordados. Analise-os com atenção, pois são essenciais para o sistema operacional Windows e vamos precisar deles daqui para frente. É importante entender todas as etapas e o mecanismo usado pelo sistema operacional.

Um programa para o Windows

Hoje em dia, a maioria dos aplicativos são controlados por uma GUI (Graphical User Interface). Através do uso de janelas, que contém controles como campos de edição, botões, menus, etc, a interatividade do usuário torna-se mais fácil e intuitiva.

Você viu na Introdução que a organização de um programa C exige SEMPRE uma função principal, geralmente denominada de main, por onde o programa é iniciado. No caso de um programa para o Windows, esta função é a WinMain (poderia ser diferente?). As funções utilizadas num programa Windows podem ser de dois tipos: aquelas que você programar (suas funções) e aquelas que fazem parte do sistema operacional Windows e estão prontinhas para serem usadas.

Todas as funções "usáveis" do Windows estão agrupadas por tipo em arquivos com a extensão .DLL (dynamic-linked libraries). O conjunto destas DLLs é chamado de API (Application Programming Interface). As principais DLLs são kernel32.dll, user32.dll e gdi32.dll. A kernel32.dll contém funções API que lidam com a memória e com a administração de processos, a user32.dll possui funções que controlam a aparência da interface com o usuário e a gdi32.dll tem funções responsáveis por operações gráficas. São milhares de funções que, quando chamadas com os parâmetros corretos, criam as janelas e os controles da GUI (Ainda bem, já pensou ter que programar cada risquinho???).

O primeiro programa GUI para Windows

Crie um novo projeto clicando em |Project/Create|. Dê-lhe um nome (eu o chamei de testedlg) e indique o diretório onde o projeto deve ser colocado. Em "Options" assinale "Single user". Até aqui, tudo igual ao módulo anterior. Mas, para um programa Windows, siga os passos a seguir:

  1. Em "Type of project" assinale "Windows Application".
  2. Clique em [Create] e no diálogo "Do you want to use the wizard to generate the application skeleton?" (Quer usar o wizard para gerar o esqueleto do aplicativo?) clique em [Yes].
  3. Na caixa de diálogo "Application characteristics", em "Type of apllication", assinale a opção "Dialog based" e depois clique em [Ok].
  4. Clique em [OK] na caixa de mensagem avisando que o projeto foi criado.
  5. Na janela "Compiler settings", clique em [Next]. Na janela "Linker settings", clique em [Next]. Na janela "Debugger settings", clique em [Finish].

Surpresa! A janela de edição mostra nosso programa, gentilmente preparado pelo lcc-win32. Mordomia pura, pois o programa está pronto. Basta compilá-lo com |Compiler/Compile testedlg.c| e rodá-lo com |Compiler/Execute testedlg.exe| para ver o resultado.

O programa montado pelo lcc

Não se assuste com todo este código, mesmo porque vamos cansar de vê-lo e, pode ter certeza, a gente acaba se acostumando. Observe que o programa possui três funções: WinMain, InitializaApp e a DialogFunc, destacadas em negrito. A InitializaApp não faz nada além de retornar 1 - serve apenas de gancho se quisermos configurar alguma coisa antes da caixa de diálogo ser mostrada. Vou tentar explicar em detalhes as outras duas, a WinMain e a DialogFunc.

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <string.h>
#include "testedlgres.h"
 
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
 
int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
        WNDCLASS wc;
        INITCOMMONCONTROLSEX cc;
 
        memset(&wc,0,sizeof(wc));
        wc.lpfnWndProc = DefDlgProc;
        wc.cbWndExtra = DLGWINDOWEXTRA;
        wc.hInstance = hinst;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
        wc.lpszClassName = "testedlg";
        RegisterClass(&wc);
        memset(&cc,0,sizeof(cc));
        cc.dwSize = sizeof(cc);
        cc.dwICC = 0xffffffff;
        InitCommonControlsEx(&cc);
 
        return DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC) DialogFunc);
}
 
static int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
        return 1;
}
 
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
        switch (msg) {
        case WM_INITDIALOG:
               InitializeApp(hwndDlg,wParam,lParam);
               return TRUE;
        case WM_COMMAND:
               switch (LOWORD(wParam)) {
                       case IDOK:
                              EndDialog(hwndDlg,1);
                               return 1;
                       case IDCANCEL:
                               EndDialog(hwndDlg,0);
                               return 1;
               }
               break;
        case WM_CLOSE:
               EndDialog(hwndDlg,0);
               return TRUE;
        }
        return FALSE;
}

A função WinMain

A função WinMain é o ponto de entrada do programa. É uma função da API do Windows que pede quatro parâmetros, usando a convenção de chamada stdcall (APIENTRY especifica este tipo de chamada), e que retorna um inteiro (int):

               int APIENTRY WinMain(HINSTANCE hinst,
                               HINSTANCE hinstPrev, 
                               LPSTR lpCmdLine, 
                               int nCmdShow);
  1. HINSTANCE hinst é o manipulador (handle) da instância do programa. Seu valor é sempre 0x400000 em hexadecimal e não é usado para nada... coisas do Windows.
  2. HINSTANCE hinstPrev refere-se a uma instância anterior do programa: outro manipulador misterioso. Contém sempre zero e também nunca é usado.
  3. LPSTR lpCmdLine é importante! É um ponteiro para uma string de caracteres que contém os argumentos da linha de comando.
  4. int nCmdShow contém um inteiro que indica se o programa foi chamado com a instrução de ficar oculto, ou aparecer normalmente, ou outras instruções que podem ser usadas quando a janela principal for criada.
int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
        WNDCLASS wc;
        INITCOMMONCONTROLSEX cc;
 
        memset(&wc,0,sizeof(wc));
        wc.lpfnWndProc = DefDlgProc;
        wc.cbWndExtra = DLGWINDOWEXTRA;
        wc.hInstance = hinst;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
        wc.lpszClassName = "testedlg";
        RegisterClass(&wc);
        memset(&cc,0,sizeof(cc));
        cc.dwSize = sizeof(cc);
        cc.dwICC = 0xffffffff;
        InitCommonControlsEx(&cc);
 
        return DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC) DialogFunc);
}

A tarefa principal desta função é criar uma estrutura WNDCLASS, preenchê-la com dados e depois chamar a função da API DialogBox. WNDCLASS wc; cria a estrutura e todas as linhas com wc.qualquer coisa atribuem valores aos campos desta estrutura. Depois de pronta, esta estrutura precisa ser registrada como uma classe do sistema operacional Windows, o que é feito com RegisterClass(&wc);.

Uma classe, no Windows, é um conjunto de objetos de janela que compartilham um procedimento comum. Quando alguma mensagem ou evento referente a esta janela é detectado pelo sistema, é enviada uma mensagem à esta janela. Por exemplo, quando movemos o cursor do mouse sobre esta janela, o sistema envia uma mensagem do tipo WM_MOUSEMOVE para o procedimento da janela, informando-o do evento. Este sistema de troca de mensagens pode ser comparado ao um serviço SAC (serviço de atendimento ao consumidor). Não se confunda: SAC não é um acrônimo usado em informática - serve apenas como comparação.

Quando um programa está rodando, existe um caminhão de mensagens sendo constantemente enviadas e recebidas. Seria impossível gerenciar todas. Para nossa sorte, apenas tratamos as mensagens que nos interessam e, as restantes, passamos para o procedimento padrão (o SAC que se vire!).

Existem vários procedimentos padrão: para uma janela normal, há o DefWindowProc; para uma janela MDI, existe o MDIDefWindowProc; e para caixas de diálogo, como neste caso, há o procedimento DefDlgProc. Dá para perceber que, para cada tipo de janela, podemos contar com um SAC especializado ;)

Sabendo disso, informamos na nossa estrutura wc que o procedimento padrão de troca de mensagens é o DefDlgProc (wc.lpfnWndProc = DefDlgProc;), indicando ao sistema o SAC apropriado.

Os dados mais importantes da estrutura foram explicados. Agora está na hora de registrar a estrutura para que seja colocada à nossa disposição pelo sistema: chamamos a função da API RegisterClass(&wc); levando como parâmetro o ponteiro para a nossa estrutura wc. O Windows que se vire com o resto...

A última declaração da função WinMain vale uma explicação mais detalhada. Agora que temos uma estrutura registrada, chamamos a função da API DialogBox:

               DialogBox(hinst,
                       MAKEINTRESOURCE(IDD_MAINDIALOG),
                       NULL, (DLGPROC) DialogFunc);

O parâmetro hinst, que muitas funções da API ainda exigem, é o valor que recebemos do sistema quando chamamos a função WinMain. Depois usamos a macro MAKEINTRESOURCE para fazer com que o compilador transforme em ponteiro o valor de IDD_MAINDIALOG. Este valor foi definido nos recursos, que serão tema do próximo módulo. Por enquanto, deixa quieto...

O terceiro parâmetro é NULL. Na verdade, deveria ser o valor do manipulador (handle) da janela-mãe desta caixa de diálogo. As caixas de diálogo, normalmente, são acessórios do aplicativo e têm nesse parâmetro a indicação do manipulador da janela à qual pertencem. Acontece que estamos construindo uma caixa de diálogo autônoma, que não possui uma janela-mãe (pobrezinha, é órfã!), portanto podemos passar um NULL (nadinha de nada) como referência.

O quarto e último parâmetro é a indicação de que, caso existam mensagens importantes, estas devem ser direcionadas para a função DialogFunc, que será vista a seguir.

A função DialogFunc

Esta é a central de atendimento particular na nossa janela do tipo caixa de diálogo. É para ela que o sistema envia as mensagens importantes, todas num formato padrão: o manipulador da janela da caixa de diálogo (como se fosse o endereço do destinatário), a mensagem e mais dois parâmetros extras.

static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
        switch (msg) {
        case WM_INITDIALOG:
               InitializeApp(hwndDlg,wParam,lParam);
               return TRUE;
        case WM_COMMAND:
               switch (LOWORD(wParam)) {
                       case IDOK:
                               EndDialog(hwndDlg,1);
                               return 1;
                       case IDCANCEL:
                               EndDialog(hwndDlg,0);
                               return 1;
               }
               break;
        case WM_CLOSE:
               EndDialog(hwndDlg,0);
               return TRUE;
        }
        return FALSE;
}

Esta função, com o uso de uma declaração switch, faz a triagem das mensagens recebidas. Apenas três tipos de mensagens são tratadas pela nossa central de atendimento, as restantes são ignoradas:

  • WM_INITDIALOG: esta mensagem é enviada depois da janela da caixa de diálogo ter sido criada e antes de ser mostrada na tela. É aqui que se pode fazer uma chamada para a função InitializaApp, aquela que eu disse não estar fazendo nada neste programa (retorna apenas o valor 1). Vai ser usada mais pra frente, aguarde.
  • WM_COMMAND: esta mensagem é enviada quando um dos controles (ou janela-filha, se quisermos ser mais exatos) quiser notificar a caixa de diálogo de algum evento importante, do tipo um botão foi clicado, um checkbox foi selecionado, o texto de uma caixa de texto foi alterado, etc. Como a caixa de diálogo pode conter vários controles, usamos novamente uma declaração switch para poder tratá-los individualmente.
  • WM_CLOSE: esta mensagem é recebida quando o usuário clicar a opção "close" do menu ou quando digitar Ctrl+F4 para fechar a caixa de diálogo.

Observe que, com exceção da mensagem WM_INITDIALOG, todas as outras são respondidas com EndDialog, ou seja, seja lá onde você clicar, a caixa de diálogo será fechada.

Mais uma coisa. Caso não tenha percebido, todas as mensagens são precedidas por WM_, que vem de Windows Message. Como nomes são mais fáceis de memorizar (quando se sabe Inglês ;) do que números, os números de identificação das mensagens padrão do Windows foram traduzidos para WM_tipo de mensagem.

Observações da vovó Vicki

Muita areia pro seu caminhãozinho? É, só que não tem escapatória: se quisermos brincar de aplicativo Windows, este é o caminho das pedras para chamar o sistema na chincha. E olha que é um aplicativozinho de nada (pus aplicativozinho só para dar uma força pro pessoal da terceira idade :0)))

Brincadeira a parte, a coisa não é tão complicada assim. Resumindo: a função WinMain aciona o sistema operacional para criar uma estrutura do tipo WNDCLASS. Depois colocamos os valores necessários na estrutura, principalmente o procedimento padrão de troca de mensagens (o SAC especializado, lembra?) e registramos tudo no sistemão do Windows. Criamos nossa própria central de mensagens (a função DialogFunc) para poder receber as mensagens do SAC, fazer a triagem e reagirmos apenas às que nos convierem.

Pois é, esta é a forma de fazer com que o sistema operacional trabalhe para nós e, o que é mais importante, da forma como NÓS determinamos. Para tanto é preciso ter uma boa fonte de referência da API do Windows. Aconselho fazer o download do win32hlp.exe nos mesmos endereços indicados para o download do lcc-win32 que, apesar de imenso (são 12,8 Mega), é essencial para nos orientar. Instale-o se você souber Inglês, senão... dê uma olhada nas tabelas NumaBoa. Não tem tudo, mas o principal está traduzido para o Português.

Se você se embananou, não se preocupe. Voltaremos ao assunto mais de uma vez.

DIÁLOGO PERSONALIZADO

Já sei, já sei... não estamos aqui para deixar o lcc-win32 fazer o que achar melhor. Afinal de contas, nós é que estamos no comando.

Já que sabemos como criar uma janela rapidinho, agora só falta saber como trabalhar com os recursos para personalizá-la. Não é necessário (ainda não) dominar a Resource Script Language, o lcc-win oferece uma maneira mais fácil: trabalhar no VISUAL!

Uma janela com firulas

Proceda exatamente como indicado no módulo anterior, A primeira janela para criar um novo projeto com o nome dlg2. NÃO compile o programa. Quando chegar à janela de edição do programa, está na hora de dar uma olhada nos recursos. Mas, o que são recursos? Acompanhe a seguir e tente entender o máximo possível, porque é exatamente nos recursos que vamos mexer para personalizar nossa caixa de diálogo.

Os recursos da caixa de diálogo

Recursos são a descrição gráfica de elementos da interface com o usuário. Esta descrição fica num arquivo à parte, o arquivo de recursos. Numa linguagem com sintaxe própria - a Resource Script Language - são descritos elementos como menus, cursores, ícones, caixas de diálogo com botões, strings de texto, etc. É como se fosse um "programa" à parte. Para ser incorporado ao executável, este "programa" também precisa ser compilado, o que é feito com compiladores especiais, os compiladores de recursos (o lcc-win32 também possui um compilador de recursos, o lrc.exe).

Como não fizemos nada relacionado aos recursos até agora, é sinal que o lcc-win32 cuidou disso para nós. Efetivamente, o sistema criou dois arquivos "irmãos": o dlg2res.h e o dlg2.rc. O primeiro, o dlg2res.h, é um arquivo cabeçalho com uma definição:

#define IDD_MAINDIALOG 100

Se você quiser conferir o conteúdo deste arquivo, clique em |File/Open|, selecione o arquivo dlg2res.h e clique em [Open]. Na janela de edição aparece o texto acima. Este texto nada mais é do que a definição do apelido IDD_MAINDIALOG para o número identificador 100. Cada elemento da interface gráfica descrito nos recursos precisa de um número de identificação (ou ID) próprio. Digamos que se pretenda incluir uma caixa de texto nos recursos - é claro que ela não poderá ser identificada pelo ID 100 (pode ser 110, 120, 15, qualquer número diferente de 100). O apelido também pode ser outro. Podemos trocá-lo por ID_DIALOGOPRINCIPAL ou qualquer outra coisa.

O arquivo "irmão" do dlg2res.h é o arquivo de código fonte dos recursos, o dlg2.rc, escrito em Resource Scripting Language (RSL). Neste arquivo encontram-se as características dos elementos gráficos:

IDD_MAINDIALOG DIALOG 38, 27, 195, 86
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "testedlg"
FONT 8, "Helv"
BEGIN
    DEFPUSHBUTTON   "OK", IDOK, 35, 66, 40, 14
    PUSHBUTTON       "Cancel", IDCANCEL, 95, 66, 40, 14
END

Confira abrindo o arquivo com |File/Open| e, na caixa de diálogo "Open as resource file?" clique em [No]. Todas estas declarações se referem à caixa de diálogo, sua aparência, a posição das janelas-filhas (lembre-se, cada elemento é, na verdade, uma janela-filha), etc. Analisando as declarações temos:

  1. Encontramos o mesmo "apelido" IDD_MAINDIALOG seguido pela declaração DIALOG e por algumas coordenadas. Isto significa em RSL que o elemento IDD_MAINDIALOG é uma caixa de diálogo e que as coordenadas estão expressas em Unidades de Diálogo (não em pixels!). Estas unidades são uma mandrakaria para fazer com que a caixa de diálogo tenha uma aparência semelhante em todas as resoluções e em diferentes tamanhos de tela. Estão baseadas indiretamente no tamanho da fonte do sistema e existem APIs que transformam estas unidades em pixels e vice-versa.
  2. A declaração STYLE (estilo) indica ao interpretador o que deve ser feito quando a janela for criada. Mais adiante, em outro módulo, quando criarmos uma janela real e não uma caixa de diálogo, que pode existir uma porção de coisas que podem ser feitas. Mas, no presente caso, o estilo indica que se trata de uma janela pop-up, visível, com título e um menu padrão do sistema.
  3. E, já que indicamos que queremos um título, precisamos indicar qual é este título através de uma string que segue a declaração CAPTION.
  4. A declaração FONT indica que queremos tamanho 8 e Helv.
  5. As declarações seguintes enumeram os controles da caixa de diálogo e suas descrições estão entre os delimitadores de bloco BEGIN e END. Aí se encontram dois botões, um destacado (DEFPUSHBUTTON) e um normal (PUSHBUTTON). Ambos possuem um texto ("Ok" e "Cancel"), IDs (IDOK, de valor numérico 1 e IDCANCEL, de valor numérico 2) e as coordenadas.

Este é o conjunto de instruções que será transformado, pelo compilador de recursos, num arquivo binário de recursos que o Windows possa interpretar. O mesmo será incorporado ao executável através de uma diretiva include. Reveja o início do programa:

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <string.h>
#include "testedlgres.h"
Observe a forma de chamar os quatro primeiros (#include <...>) e o último (#include "..."). Para o pre-processador, include com <...> significa que o arquivo a ser incluído se encontra no diretório indicado na configuração do lcc (no caso, \lcc\include\), enquanto que o include "dlg2res.h" se encontra no diretório do projeto (no caso, \lcc\projects\teste\).

Editando os recursos com o lcc-win32

Na primeira compilação e linkedição, o lcc-win32 também compila o arquivo de recursos para que possa ser incorporado ao executável. Se quisermos modificar os recursos gerados automaticamente, precisamos fazer as alterações antes de compilar o programa. Uma outra possibilidade é compilar os recursos através de um comando de linha do lcc-win32, assunto de módulos posteriores.

Para editar os recursos com o lcc-win32, clique em |Design/Open/New|, selecione dlg2res.h, clique em [Open], expanda o diretório "Dialogs" clicando no sinal de + e dê um duplo clique em IDD_MAINDIALOG 100 dlg2. Como resultado, aparece uma janela de edição de recursos com a caixa de diálogo.

Clique [T↓] - o primeiro botão da barra de ferramentas - para mostrar todos os controles disponíveis. Lá você vai encontrar campo de edição, check box, botão rádio, etc. Clique no campo de edição "Edit field" e o cursor indicará a seleção. Coloque o cursor na posição desejada dentro da caixa de diálogo e clique novamente para inserir o controle. Ele aparece como selecionado, o que é indicado pelos quadradinhos vermelhos ao redor do campo de edição.

No campo de edição do aplicativo, "Name", acima da barra de ferramentas, digite ID_EDICAO. Este será o apelido do nosso campo de edição e o editor de recursos deve lhe atribuir a ID 101 no arquivo dlg2res.h (você poderá trocar o número se quiser, contanto que não conflite com outra ID). A seguir, redimensione o campo de edição clicando e arrastando um dos quadradinhos. Clique também no título da janela dlg2 para selecioná-la e a redimensione também.

Para conferir o resultado, clique no botão de teste (o que possui um ícone de interruptor, o penúltimo da barra de ferramentas). Você pode digitar qualquer texto no campo e depois clicar em [OK], em [Cancel] ou no botão com o interruptor para fechar a janela. Deu tudo certo e você está satisfeito? Eu não! Quero que o botão com "Cancel" mostre "Cancela" e que o título da janela seja Diálogo Personalizado. Então vamos lá...

Tendo voltado ao modo de edição, clique no botão com o texto "Cancel" para selecioná-lo - o que faz os quadradinhos vermelhos aparecerem. Clique no último botão da barra de ferramentas (o que parece ter uma patente de sargento) para abrir a janela de propriedades referentes ao controle selecionado. No campo "Text" ponha Cancela. A seguir, clique no título da caixa de diálogo - aparecem as propriedades da mesma. No campo "Caption text" escreva Diálogo Personalizado e depois feche a janela de propriedades.

Teste novamente com o botão do interruptor. Ah... agora está bem melhor! Como por enquanto é só, feche a janela de edição de recursos e clique em [Yes] para salvar as alterações no arquivo dlg2res.h, em [Yes] para salvar as alterações no arquivo dlg2.rc e novamente em [Yes] para atualizar o arquivo tmpres????.res.

Interagindo com o usuário

Na verdade, o que queremos é interagir com o usuário do nosso programa. Seria muita gentileza de nossa parte se o cursor já estivesse colocado no campo de edição quando a janela fosse apresentada (economiza um clique de mouse ;). No Windows, existe sempre apenas uma janela que esteja em foco e que recebe todas as entradas do teclado e do mouse.

Podemos forçar o foco numa janela com a função da API SetFocus e, como queremos que determinar o foco ANTES da janela ser apresentada, é claro que vamos colocar esta informação na função InitializeApp do nosso programa - mesmo porque, ela foi feita para isto mesmo.

...
 
static int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
        SetFocus(GetDlgItem(hDlg,ID_EDICAO));
        return 1;
}

Beleza pura! Compile e teste o programa para verificar que... NÃO FUNCIONOU :((( Que mistério é esse? Será que nossa central de mensagens não está sabendo enviar nossas solicitações ao sistemão? Veja:

...
 
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
        switch (msg) {
        case WM_INITDIALOG:
               InitializeApp(hwndDlg,wParam,lParam);
               return TRUE;
        ...

Na nossa central de mensagens, quando recebemos a mensagem WM_INITDIALOG para avisar que nossa janela está pronta para ser mostrada na tela, antes de mostrá-la chamamos a função InitializeApp e enviamos como resposta TRUE. Se você tiver instalado o help das APIs do Windows, coloque o cursor sobre a mensagem WM_INITDIALOG e digite F1. A parte importante é "The dialog box procedure should return TRUE to direct Windows to set the keyboard focus to the control given by hwndFocus. Otherwise, it should return FALSE to prevent Windows from setting the default keyboard focus". Resumindo, para que o Windows não acione o foco padrão, a resposta precisa ser FALSE! Como o foco padrão não nos convém...

Taí! Troque o return TRUE; por return FALSE;. Salve a alteração com |File/Save| e dá-lhe testar novamente. Na mosca! O foco está no campo de edição.

Voltando para a função SetFocus, é preciso dizer que ela pede um parâmetro: o manipulador (handle) da janela (ou elemento da interface) que deve receber o foco. Temos o campo de edição de ID_EDICAO, mas desconhecemos seu manipulador. Então, o jeito é apelar novamente para a API e usar a função GetDlgItem. Esta função pede dois parâmetros - o manipulador da caixa de diálogo e o apelido do item - e retorna o manipulador do item indicado. O manipulador da caixa de diálogo vem pronto como parâmetro da função InitializeApp - é o hDlg. Pois bem, é possível obter o manipulador do item de diálogo com GetDlgItem que, por sua vez, é enviado como parâmetro da função SetFocus. É o famoso sanduiche de funções :)

Observações da vovó Vicki

Tudo muito bem, tudo muito bom, mas... sempre precisa ter um mas para justificar o próximo módulo deste tutorial: do que é que adianta solicitar uma string do usuário se não pudermos identificá-la e usá-la para alguma coisa?

Se você ainda tiver fôlego, siga para o próximo módulo. Se tiver dúvidas, é melhor reler o texto e tirá-las antes de prosseguir. Se estiver no mato sem cachorro, faça contato que terei prazer em ajudar.

 

CAÇANDO INFORMAÇÕES

A esta altura do campeonato já deu para perceber que o grande lance na programação Windows está diretamente relacionado à troca de mensagens entre o sistemão (o sistema operacional Windows) e a central de mensagens do nosso programa. Conforme prometido no final do módulo anterior, o Diálogo Personalizado, vamos rastrear as informações fornecidas pelo usuário. Se você não leu o módulo anterior, sugiro que o faça, pois este é apenas a continuação do assunto tratado anteriormente. Além do mais vamos expandir o programa dlg2.c, também criado no módulo anterior.

Definindo os objetivos

Se o usuário clicar no botão [OK], queremos saber o teor do texto digitado. Esta operação tem dois aspectos:

  1. Saber se o botão [OK] foi clicado (se o usuário clicar em [Cancela] ou fechar a janela, é sinal de que não quer revelar o conteúdo digitado).
Obter o texto do campo de edição.

Interceptando um clique no botão [OK]

Lá vamos nós para a central de mensagens do programa. Mostrando-a, para não dar margem a dúvidas:

static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
        switch (msg) {
        case WM_INITDIALOG:
               InitializeApp(hwndDlg,wParam,lParam);
               return FALSE;
        case WM_COMMAND:
               switch (LOWORD(wParam)) {
                       case IDOK:
                               EndDialog(hwndDlg,1);
                               return 1;
                       case IDCANCEL:
                               EndDialog(hwndDlg,0);
                               return 1;
               }
               break;
        case WM_CLOSE:
               EndDialog(hwndDlg,0);
               return TRUE;
        }
        return FALSE;
}
O ponto para interceptar um clique no botão [OK] já está definido: é quando nossa central de mensagens receber uma mensagem do tipo WM_COMMAND trazendo no parâmetro wParam o valor IDOK. O que precisamos, além de fechar a caixa de diálogo com EndDialog, é obter o texto que foi digitado (ou não) no campo de edição. Chegou finalmente a hora de botar a mão na massa - vamos escrever uma função que realize o trabalho que desejamos.

Caçando o texto

Sabemos que o texto que queremos obter se encontra no campo de edição de apelido ID_EDICAO. Para capturá-lo, mais uma vez, existe uma função da API, a GetDlgItemText. Acontece que esta função extrai o texto da janela-filha ID_EDICAO e transfere os caracteres para uma área de memória. Áreas de memória reservadas para armazenarem dados são chamadas de buffer, cuja tradução literal é pára-choque. Só Deus sabe porque escolheram este termo; pra mim, não tem nada a ver, preferia algo como cesta ou balaio ;)

É claro que o balaio... perdão, o buffer precisa ter um tamanho suficientemente grande para conter todos os caracteres que pretendemos transferir. Além disso, é uma exclusividade da casa e não precisa cair no domínio público, ou seja, outros programas ou módulos não precisam saber da sua existência. Chamamos isto de buffer estático.

Por uma questão de ordem, colocamos a definição desta variável estática no início do programa, logo depois dos includes e da declaração do protótipo da função DialogFunc (aliás, ainda não expliquei a razão de protótipos de funções. Aguarde mais um pouco que a gente chega lá). Então, a coisa fica assim se chamarmos a variável estática de balaio e determinarmos que possa conter até 1024 caracteres:

...
#include "testedlgres.h"
 
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
static char balaio[1024];
...

Também por uma questão de ordem, deixamos que a primeira função do programa seja a da entrada, a WinMain, seguida pela função de inicialização, a InitializeApp. Bem, então logo após, podemos escrever a seguinte função:

int PegaTexto(HWND hwnd)
{
        memset(balaio,0,sizeof(balaio));
        if (GetDlgItemText(hwnd, ID_EDICAO, balaio, sizeof(balaio))) {
               return 1;
        }
        return 0;
}

A função de nome PegaTexto exige como parâmetro um manipulador (handle) e retorna um inteiro (int). Agora vamos por parte:

  1. A função do C memset preenche a área de memória reservada para a variável balaio com 1024 zeros. A função do C sizeof dá o tamanho do balaio (1024 caracteres). O que queremos é enviado através dos parâmetros e o resultado é que o buffer é zerado - o mesmo que dizer "o balaio é esvaziado".
  2. Segue-se uma diretiva if (cuja tradução é se). Então, SE GetDlgItemText conseguir transferir o conteúdo do campo de edição para o balaio, retorne 1 (o mesmo que TRUE); caso contrário, retorne 0 (o mesmo que FALSE).

Falta destrinchar a função GetDlgItemText. Esta função precisa dos seguintes parâmetros:

  • hwnd: o manipulador a janela-mãe, nossa caixa de diálogo.
  • ID_EDICAO: o apelido do campo de edição, a janela-filha da qual se quer extrair o texto.
  • balaio: o buffer onde devem ser colocados os caracteres.
  • sizeof(balaio): o tamanho do buffer, ou seja, o número máximo de caracteres que podem ser transferidos.

Tudo bem, mas o que é que o if faz com a função GetDlgItemText? SE o que? É que GetDlgItemText retorna o número de caracteres transferidos. Se for 1 ou mais caracteres, é o mesmo que dizer if TRUE, a condição é preenchida e o valor de retorno é 1; caso contrário o valor de retorno é 0. Tudo em riba - só falta chamar nossa função quando o usuário clicar o botão [OK]:

static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
        switch (msg) {
        case WM_INITDIALOG:
               InitializeApp(hwndDlg,wParam,lParam);
               return FALSE;
        case WM_COMMAND:
               switch (LOWORD(wParam)) {
                       case IDOK:
                               PegaTexto(hwndDlg);
                               EndDialog(hwndDlg,1);
                               return 1;
                                   ...

Conferindo o resultado

Se você fez as alterações propostas até agora, teve a pachorra salvar o arquivo, fazer a compilação e conseguiu rodar o programa, não notou diferença nenhuma. Simplesmente é preciso acreditar que o texto do campo ID_EDICAO foi transferido para o balaio - não tem como ver. Como isto é muito chato, vamos espiar a área de memória usando a função da API MessageBox que cria uma caixa de mensagem. É uma saída de preguiçoso porque, usando esta função, não será preciso registrar uma classe janela, definir mais um procedimento, etc e tal.

MessageBox precisa como parâmetros o manipulador da janela-mãe (já deu para perceber que o sistemão adora handles), o buffer onde se encontra o texto que deve ser mostrado, uma string com o título da caixa de mensagem e o botão (ou botões) que acompanham a caixa. Aproveitando a interceptação do botão [OK], logo depois da função PegaTexto, entre o texto assinalado em negrito:

...
  case WM_COMMAND:
      switch (LOWORD(wParam)) {
      case IDOK:
          if (PegaTexto(hwndDlg)) {
              MessageBox(hwndDlg,balaio,"Texto digitado",MB_OK);
              EndDialog(hwndDlg,1);
          }
          return 1;
      ...

Salve com |File/Save| o programa dlg2.c, compile e rode. Tchan, tchan, tchan, tchan - funciona! Agora experimente clicar em [OK] com o campo de edição vazio. Você vai perceber que não acontece nada - nem caixa de mensagem aparece, nem a caixa de diálogo é fechada. Isto é porque a função PegaTexto retorna 0 (ou falso), fazendo com que a linha de execução no caso do IDOK não entre na área do if, que o EndDialog não seja executado e que o valor de retorno seja 1 (ou verdadeiro).

Mas que programa mais mal educado! Imagine a situação do usuário que clica em [OK] e fica sem saber porque nada acontece. Vai achar que o programa pendurou ou, se entender de programação, vai dizer que nosso programinha é uma droga. Durma com um barulho desses!

Melhorando a interface

Para corrigir este comportamento inaceitável do nosso programa existem duas alternativas: ou chamamos uma nova caixa de mensagem para avisar o usuário que o campo de edição não pode estar vazio, ou desabilitamos o botão [OK], que só será habilitado quando houver texto no campo de edição. A primeira alternativa não oferece grandes novidades. A segunda é um novo desafio de programação. Então, vamos optar pela segunda.

O botão [OK] deve ter as seguintes características: no início do programa, estar desabilitado; se for digitado texto no campo de edição, deve ser habilitado; se o texto do campo de edição for apagado, deve ser desabilitado novamente. Quem é que está dando as cartas? É o campo de edição, e ele é apenas mais uma das janelas-filhas. Toca correr para nossa central de mensagens...

...
  case WM_COMMAND:
      switch (LOWORD(wParam)) {
      case IDOK:
          if (PegaTexto(hwndDlg)) {
              MessageBox(hwndDlg,balaio,"Texto digitado",MB_OK);
              EndDialog(hwndDlg,1);
          }
          return 1;
      case IDCANCEL:
          EndDialog(hwndDlg,0);
          return 1;
      case ID_EDICAO:
          return ConfereTexto(hwndDlg,wParam);
      }
      break;
      ...

Aqui cabe uma explicação. Quando comecei a escrever o texto para habilitar/desabilitar o botão [OK], o código começou a ficar mais extenso do que eu imaginava e a aparência do switch ficou horrível. Por isso, decidi criar uma função à parte, para deixar o código mais elegante e limpo. Vale como sugestão: toda vez que o código começar a virar uma maçaroca, crie uma função. A leitura do código fica mais fácil.

Bem, a função ConfereTexto tem esta cara. Observe os comentários (o que é uma boa prática de programação) cujas linhas são iniciadas com //:

int ConfereTexto(HWND hDlg,WPARAM wParam)
{
        HWND hIdOk = GetDlgItem(hDlg,IDOK);
 
        switch (HIWORD(wParam)) {
        case EN_CHANGE:
               if (GetDlgItemText(hDlg,ID_EDICAO,balaio, sizeof(balaio))) {
                       // Tem texto no campo de edição. Habilitar o botão IDOK
                       EnableWindow(hIdOk,1);
               }
               else // sem texto, disabilitar o botão IDOK
                       EnableWindow(hIdOk,0);
               break;
        }
        return 1;
}

Na declaração switch estamos usando o HIWORD do wParam. É preciso explicar que wParam é um double word (também grafado como dword), ou seja, é composto por 32 bits. Um word é a metade de um dword e tem 16 bits. wParam carrega duas informações diferentes, uma nos 16 bits mais significantes (HIWORD) e outra nos 16 bits menos significantes (LOWORD). Nos bits do LOWORD encontramos a ID do controle que enviou a mensagem (o remetente) e nos bits do HIWORD encontramos a sub-mensagem de WM_COMMAND que o controle está enviando.

No nosso caso, LOWORD indicará ID_EDICAO e HIWORD indicará EN_CHANGE. Todas as mensagens oriundas de campos de edição possuem o prefixo EN, derivado de Edit Field Notification, e a tradução de CHANGE é mudar. Então, a cada mudança no campo de edição, uma mensagem EN_CHANGE será enviada através do LOWORD - botou uma letra, uma mensagem é enviada; tirou uma letra, a mesma coisa. É exatamente isto que vamos interceptar, ou seja, caso EN_CHANGE, então...

Então chamamos GetDlgItemText para verificar quantos caracteres há no campo de edição. Se houver mais do que 0 (zero) caracteres, habilitamos o botão. Se não houver caracteres, desabilitamos o botão.

Para variar, precisamos do manipulador (handle) do botão IDOK para poder alterar suas propriedades. O manipulador foi obtido logo no início da função quando chamamos a GetDlgItem e guardamos o valor de retorno na variável hIdOk (que é do tipo HWND). Chamamos a função EnableWindow com os parâmetros hIdOk (o handle do botão IDOK) e um inteiro: 1 (TRUE) para habilitar o botão e 0 (FALSE) para desabilitá-lo.

Só falta um detalhe: o programa precisa começar com o botão IDOK desabilitado. Neste caso, isto deve ser feito antes da janela ser apresentada na tela, o que nos remete à função InitializaApp. Como já somos cobras em sanduiches de funções, mandamos ver o seguinte:

static int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
        // Por foco no campo de edição
        SetFocus(GetDlgItem(hDlg,ID_EDICAO));
        // Desabilitar o botão IDOK
        EnableWindow(GetDlgItem(hDlg,IDOK),0);
        return 1;
}

Atualize o programa dlg2.c, salve-o, compile o código e execute o programa. Tá melhor, tá não?

Observações da vovó Vicki

Sobre a interface com o usuário: a solução de desabilitar o botão OK até que foi boa, mas prefiro usar as caixas de mensagem que explicam exatamente o que está acontecendo. Um botão ou um item de menu desabilitados, na verdade, só impedem que sejam clicados e o usuário não é obrigado a saber porque isto está ocorrendo. Mensagens explícitas facilitam as coisas, mesmo quando óbvias (já me aconteceu de ter que abrir o código para verificar porque eu havia desabilitado determinado item de menu, pois não me lembrava mais depois de alguns meses ;)

Bem, até agora nos preocupamos mais com o resultado da programação usando funções disponíveis. Está na hora de dar uma olhada nas principais bibliotecas que fornecem estas funções. Criamos nossas próprias funções e também fizemos uso de algumas variáveis. Não seria má idéia conhecer os tipos básicos, definições e declarações. Estes serão os assuntos do próximo módulo.

 

BIBLIOTECAS E TIPOS

A imensa coleção de funções que o sistema operacional do Windows nos oferece, a assim chamada API (Application Programming Interface), facilita muito nossa programação se soubermos exatamente como usar cada função. Ler a descrição técnica é essencial para saber, entre outras coisas, o número de parâmetros exigidos, o tipo destes parâmetros e o valor de retorno da função. Afinal de contas, como programadores, o mínimo a fazer é se familiarizar com a interface que nos é oferecida.

As bibliotecas padrão

Para inserir funções da API nos nossos programas é preciso indicar para o pre-processador onde ele poderá encontrá-las. Sabemos que funções da API do Windows ficam agrupadas em arquivos próprios de acordo com o tipo, numa espécie de biblioteca de funções. Estas bibliotecas são aquivos que possuem a extensão .dll, chamados simplesmente de DLLs.

Quando queremos usar uma função da API, ou uma função própria da linguagem C, não escrevemos a função no nosso programa. É muito mais prático pedir ao pre-processador que inclua o código da função desejada. Para isto, precisamos fornecer ao pre-processador um "mapa da mina", para que ele localize e inclua a função desejada. O lcc-win32 possui vários "mapas" prontinhos para serem utilizados, arquivos com a extensão .h chamados de cabeçalhos e que se encontram no diretório /lcc/include. Os principais

 

Cabeçalho

Uso

stdio.h

Standard Input Output (entradas e saídas padrão): este cabeçalho contém a definição da estrutura FILE, usada para todas as entradas (input) e saídas (output), além das definições de todas as funções que lidam com a abertura, fechamento, etc de arquivos. A famosa função printf também é definida aqui, juntamente com sprintf, fprintf e toda a família de funções relacionadas.

math.h

Funções Matemáticas: sin, cos, atan, log, exp, etc. Aqui encontramos trigonometria (sin, cos, tan, atan, etc), arredondamentos (ceil, floor), logaritmos (log, exp, log10, etc), raiz quadrada e cúbica (sqrt, cbrt) e constantes como pi, e, etc.

stdlib.h

Standard library functions (funções da biblioteca padrão): Contém abort (término anormal do programa), exit (término normal), atoi, itoa (conversão de texto para inteiro), malloc, calloc, free (módulo de memória dinâmica), rand, srand (números randômicos), putenv, getenv (administração do ambiente), qsort (ordenação), strtod, strtol (conversão de string para double/long), sleep (suspender a execução por um certo período de tempo).

stddef.h

Este arquivo define macros e tipos de uso geral em programas: NULL, offsetof, ptrdiff_t, size_t e muitos outros.

string.h

Manipulação de strings: aqui são definidas todas as funções que lidam com a representação padrão de como as strings são usadas em C. Temos strcmp (comparação de strings), strlen (obtenção do comprimento de uma string), strcpy (cópia de uma string para outra), strcat (concatenação de strings), strstr (substring numa string), memset (atribuição de uma região RAM para um caracter), memcpy (copiar memória), memmove (copiar memória cuidando da região de sobreposição).

windows.h

Todas as definições de janelas: criar, abrir, etc. É um arquivo cabeçalho grande com cerca de 500 mega de definições. Saiba que o lcc-win32 incorpora neste arquivo muitos dos arquivos que normalmente são individualizados em outras distribuições (como o winbase.h, por exemplo).
 

Se tiver curiosidade de saber como são montados estes arquivos de cabeçalho, basta abrí-los em qualquer editor de texto (o do lcc-win32 também serve).

Os tipos padrão

A linguagem C possui os seguintes tipos padrão:

Tipo

Tamanho

Descrição

_BOOL

1

Tipo lógico, pode ser 0 ou 1.

char

1

Tipo caracter, podendo ser com sinal ou sem sinal.

short

2

Inteiro armazenado em 16 bits, com ou sem sinal.

int

4

Inteiro armazenado em 32 bits, com ou sem sinal.

long

4

Idêntico ao int.

long long

8

Inteiro armazenado em 64 bits, com ou sem sinal.

float

4

Ponto flutuante de precisão simples (cerca de 7 dígitos).

double

8

Ponto flutuante de precisão dupla (cerca de 15 dígitos).

Manifestos e definições

É uma confusão danada! "Declaration" em Inglês é "manifesto" e "Statement" é "declaração". É muito importante entender a diferença entre um manifesto e uma definição no C.

Um manifesto apresenta um identificador ao compilador. É como se um manifesto dissesse "este identificador é o XXX e ele será definido mais adiante". Um manifesto pode ser, por exemplo:

extern double sqrt(double);

Com este manifesto apresentamos o identificador sqrt ao compilador, dizendo que se trata de uma função que usa um argumento de precisão dupla (double) e que retorna um resultado de precisão dupla (double). A apresentação pára por aí e nem ocupa espaço. É que, se esta função não for utilizada pelo programa, ela nem será incorporada - portanto, não ocupará espaço no executável.

Uma definição diz ao compilador para alocar espaço para o identificador. Por exemplo, para definir a variável "contador":

int contador;

Neste caso, o compilador faz espaço na área de variáveis locais da função que contenha esta definição, abrindo espaço suficiente para conter um número inteiro.

O que precisa ficar bem claro é que uma variável pode ser manifestada quantas vezes quisermos, mas só deve ser definida num único ponto. É o mesmo que dizer que, quando definimos uma variável, emitimos sua "carteira de identidade" (que precisa ser única) e fixamos sua residência (para que possamos encontrá-la quando for preciso). Quando manifestamos uma variável (ou uma função), dizemos apenas que deve aparecer uma "moça" de nome tal, ou um "rapaz" de nome tal, ou um int ou um double de nome tal... enfim, uma entidade de nome tal cujo tipo seja o que foi manifestado - mas que ainda está sem lenço e sem documento.

Manifestando e definindo variáveis

Uma variável é definida com <tipo><identificador>. Se quisermos apenas manifestar a variável, sem alocar espaço para ela, adicionamos a palavra-chave extern. Além disso, é possível definir uma variável e, simultaneamente, atribuir-lhe um valor. Veja alguns exemplos:

int a;
int b = 3;
double d = (1024*1024)/16;
extern long long h;

No exemplo acima, a variável h, de 64 bits, foi apenas manifestada. As restantes foram (manifestadas e imediatamente) definidas, sendo que a variável a não recebeu qualquer valor, a variável b recebeu o valor 3 e a variável d recebeu o valor de uma operação de multiplicação e divisão.

Ponteiros (que apontam para endereços) são manifestados ou definidos com asterisco. Tomemos a variável b como exemplo: se quisermos o valor de b, referenciamos b pelo "nome" e obtemos o valor 3 (nome é b e seu valor é 3). Mas, se quisermos saber em que endereço de memória foi armazenado o valor de b, usamos *b.

Para economizar digitação podemos colocar variáveis do mesmo tipo, separadas por vírgulas, numa única linha. Apenas para manter a ordem, é aconselhável não misturar ponteiros com outros inteiros:

int a, contador, base, c;
int *enderecoDaBase, *m, *pontA;

Manifestando funções

O formato padrão do manifesto de funções é <tipo> <nome>(<tipo do arg 1>, … <tipo do arg N> ) ;

Mas porque manifestar funções? Não seria mais rápido e prático definí-las diretamente e usá-las? Aí depende... veja o código abaixo:

Módulo A                              Módulo B
 
int funcTeste(int a)                  ...
{                              funcTeste(7, 9);
    return a + 8;                     ...
}

Se criarmos diretamente a função funcTeste no módulo de programa A e, no módulo do programa B, inadvertidamente chamarmos a função com parâmetros que não são os que foram definidos, o compilador não tem como identificar o erro. Vai tudo muito bem até que se resolva executar o programa que, obviamente, vai dar pau. Para que o compilador possa checar todas as chamadas de funções, é preciso manifestá-las antes de definí-las.

Funções apenas manifestadas são chamadas de PROTÓTIPOS de função. No exemplo acima, se funcTeste tivesse um protótipo, o compilador estrilaria quando estivesse checando a chamada no módulo B, avisando que a chamada está incorreta. Apenas uma linha de protótipo pode nos economizar horas de debug tentando encontrar um erro que só aparece em tempo de execução. Pense nisso!

Agora imagine que, para se garantir, você tenha que escrever os protótipos de todas as funções da linguagem C e da API do Windows que forem usadas pelo seu programa. Uma loucura! É aí que entram os arquivos cabeçalho, aqueles com a extensão .h e que se encontram no diretório /lcc/include/. Estes arquivos nada mais são do que coleções de protótipos. Basta incluir os cabeçalhos necessários com #include e ganhamos um monte de tempo. Graaaaande Jacob Navia, autor do lcc-win32, que forneceu o pacote completo!

Definindo funções

Definir funções tem o mesmo jeitão do manifesto de funções. Uma das diferenças é que, ao invés de usar ponto e vírgula, adicionamos um bloco de outras declarações delimitadas por colchetes. A outra diferença é que os argumentos precisam receber um nome (ou identificador). Estes identificadores são imprescindíveis porque apenas através deles é que poderemos acessar os argumentos. Um exemplo bem simples seria:

int SomaUm(int entrada)
{
        return entrada + 1;
}

Observações da vovó Vicki

Eu sei, este módulo foi um pouco sem graça porque não teve nenhum exemplo para brincar. Acontece que determinados conceitos são fundamentais se quisermos avançar com alguma segurança. Para compensar a falta de "brinquedinhos", os próximos módulos vão fazer a festa da garotada (e da adultada também ;)

Mas nem tudo foi pedreira. Se você entendeu o que é (e para que serve) um protótipo de função, então a linha abaixo, destacada em negrito, que consta do nosso programa dlg2.c, deixou de ser uma incógnita:

...
#include "testedlgres.h"
 
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
static char balaio[1024];
...
Para compensar a falta de exemplos neste módulo, o próximo é sobre uma "janela de verdade". Não se preocupe com os detalhes porque, à medida que for necessário, as explicações mais detalhadas serão dadas. Prometo ;)

Próximo

Free Web Hosting