You are on page 1of 75

C++ Win32 Tutorial

Para Console

C++ Win32 Tutorial

Win32 Tutorial
Ao invs de escrever um documento generalizado tentando abordar todos os principais conceitos (j existem muitos desses pela internet), criei vrios tutoriais acerca de tpicos especficos, sendo que a maioria dos temas so recorrentes nos fruns (ou fora, do latim, como desejar) em que participo. Os tutoriais usam as rotinas da 1 2 Win32 API, ou seja, no dependem de bibliotecas como a MFC ou a VCL . Eu encorajo o leitor a consultar a seo seguinte antes das outras partes deste documento. L esto detalhes de qual compilador usei, que verso do SO e outras informaes que podem ser teis. Existem alguns links para sites onde voc pode conseguir mais referncias ou at mesmo ajuda. Devo lembrar que nosso mundo evolui, portanto, no sei at quando estes tutoriais ou at mesmo os links estaro atualizados e/ou disponveis. Logo, como algum que quer se tornar um bom programador/profissional, habitue-se a utilizar ferramentas mais modernas e, to importante quanto, nunca deixe de pesquisar novas formas de como se fazer uma tarefa. No porque seu professor recomenda, para fins didticos, o uso do j morto, sepultado e esquecido Dev-C++ da Bloodshed Software, que voc ir desenterr-lo s vezes devemos ir mais longe e ressuscitar o Turbo-C++, da Borland (muito obrigado aos mesmos pelos longos anos de servio, mas vocs j cumpriram o seu dever e agora fazem parte da histria. Por favor, descansem em paz...). E outra: se voc est programando para console, aprenda a us-lo. A vida no se resume a dois cliques... Ento, sem mais delongas Exibindo um diretrio ou pasta Um tutorial em trs partes cobrindo a leitura do contedo de um diretrio ou pasta no Windows. Parte 1 desenvolve um programa rudimentar para a tarefa Parte 2 refina o mesmo cobrindo diversas condies de erro comuns Parte 3 explora alguns outros aspectos das rotinas usadas da API Trabalhando com Console no Windows Tutoriais demonstrando caractersticas das Win32 Console Applications ou "DOS boxes". Parte 1 descreve consoles Parte 2 nomeia o console, utiliza manipuladores padro, move o cursor (ou caret, como preferir), limpa a tela e utiliza blocos de caracteres Parte 3 desenha linhas, caixas e grades e esconde/modifica o cursor Parte 4 lida com caracteres especiais Parte 5 colore palavras e o fundo Parte 6 lida com eventos do teclado e do mouse Parte 7 trata de problemas com o tamanho do console Programao Multithreaded Bsica Tutoriais contendo uma introduo aos processos multitarefa. Parte 1 constri um programa de thread nico, ento o converte a uma verso multithreaded muito pobre do mesmo. Parte 2 comea corrigindo algumas das falhas introduo sincronizao Parte 3 corrige as falhas restantes e comenta de forma mais generalizada sobre os problemas nos projetos multithreaded

Microsoft Foundation Classes so classes fundamentais em C++ para programao Microsoft Windows; Visual Component Library um framework visual de componentes baseados em orientao a objetos para desenvolvimento de aplicativos para Microsoft Windows. Foi desenvolvido pela Borland para uso nas (e fortemente integrada com) ferramentas Delphi e C++ Builder RAD (agora propriedade da CodeGear, diviso da Embarcadero Technologies).
2

C++ Win32 Tutorial

Notas Gerais
Os cdigos presentes neste documento foram escritos em C++, mas podem ser facilmente convertidos para C, uma vez que evitei propositalmente o uso de orientao a objetos. Se o leitor demonstrar um pouco de boa vontade, conseguir cumprir esta tarefa (traduo de uma linguagem para outra) sem maiores problemas. Se voc nunca programou em C++, no se desespere no h nada de assustador neste documento. Alguns podem achar que o cdigo muito simplista e nem um pouco orientado a objetos. Neste caso, devo pedir aos mesmos que reflitam sobre o pblico a que este material voltado. Freqentemente, pessoas procurando por esse tipo de ajuda comearam a programar h pouco tempo, ento acredito que seria desanimador para esses indivduos se eu abordasse uma grande gama de modelos de classes e assim por diante. E no vejo necessidade de me distanciar do leitor somente para passar algumas dicas e orientaes. Dito isto, redigi este documento sem quaisquer preocupaes com formalidades. Para compilar meus cdigos fontes, fiz uso do MinGW 5.1.6 (um port para Windows do GCC e outras GNU binutils) em conjunto com a verso nightly build do Code::Blocks mais precisamente, a svn build rev 6181, de 27 de fevereiro de 2010. Tambm os testei com a Microsoft Visual C++ 2008 Express Edition j saiu a 2010. possvel que voc venha a utilizar um compilador diferente destes em que o cdigo fonte possa no funcionar neste caso, temo que a frase a vida... se aplique muito bem. Eu tento me manter preso s rotinas principais da API sempre que possvel, ento no devem existir problemas maiores. Se voc compilar algo e seu compilador reclamar de que a funo no est definida ou algo similar, tente encontrar a funo equivalente consultando a documentao do seu compilador (ou futricando nos headers caso voc se sinta vontade). Testei os programas no Windows XP 32bits, entretanto, a maior parte do que ser tratado aqui deve ser suportada tanto em verses mais antigas quanto nas mais recentes do Windows. Uma coisa que diferente a funo GetLastError() que voc me ver usando. Essa rotina no devidamente suportada nos consoles de 16bit que acompanham verses OS/2 do Windows (95, 98 e ME) e pode devolver zero mesmo existindo um erro. 3 Tambm testei os cdigos no Windows 7 64bits, mas como o compilador 32bits , no fundo acabei usando o modo de compatibilidade do sistema operacional, sendo que no tive problemas com os programas gerados. O Windows um sistema operacional de certa forma complicado e com uma API muito rica. Freqentemente, o mesmo resultado pode ser alcanado por mais de uma maneira. Os tutoriais deste documento geralmente demonstram um modo de se fazer algo. Podem existir, e freqentemente existiro, outros caminhos. Se voc estiver olhando tutoriais em mais de um site, e eles abordarem seu problema em particular de maneiras distintas, no se surpreenda. Por isso, tente no ficar chateado se algum dia o monitor da sua disciplina estiver chamando seu programa para console fora do console e no conseguir enxergar como o mesmo funciona sem nem ao menos olhar seu cdigo fonte e entender o que ele faz, descontando severamente sua nota. Uma abordagem diferente no significa que esta ou aquela est errada, elas so apenas diferentes embora devamos buscar solues que priorizem o tanto o desempenho quanto a segurana sempre que possvel (s vezes uma situao exclui a outra, por isso analise seu caso). Resumindo, voc pode freqentemente aprender muito mais estudando as diferentes solues de um mesmo problema. Caso tenha dvidas ou encontre alguma dificuldade com o que no foi abordado aqui (isso sempre acontece, no mesmo?), eu recomendaria uma visita aos seguintes endereos eletrnicos: www.cprogramming.com, www.cppreference.com e www.cplusplus.com, onde existem outros tutoriais, FAQs enormes e fruns de discusso. E nunca deixe de pesquisar... http://pt-br.lmgtfy.com/?q=Installing+MinGW Os fruns nos sites esto cheios de pessoas que tentaro ajud-lo, entretanto h, como sempre, uma ressalva: nem todos que esto tentando ajudar so necessariamente qualificados em faz-lo voc pode conseguir algumas respostas, no mnimo, exticas. A internet assim, esteja ciente disso Leia os FAQs e tente, ao menos, buscar sua resposta dentro do frum antes de fazer alguma pergunta.
O MinGW64 ainda no estvel/confivel o suficiente e a expanso 64bits do VC++ no suportada pela verso Express da IDE, apenas pela Professional, que paga.
3

C++ Win32 Tutorial

Este documento foi criado tendo como base um conjunto de tutoriais que h muito utilizo como referncia (http://www.adrianxw.dk/index.html). Tentei contatar o autor, Adrian Worley (um dinamarqus que freqentava os fruns do www.programmersheaven.com), para consult-lo sobre a traduo, distribuio e cpia de sua pgina, mas no obtive sucesso. Devo tentar faz-lo novamente em outro momento, mas desde j atribuo os crditos de boa parte deste documento ao trabalho dele. Devo agradecer tambm os membros da Comunidade C/C++ Brasil, em especial o Sr. Josu Andrade Gomes e o Sr. Paulo Pires, pelas observaes e comentrios feitos sobre a primeira verso deste documento e pela orientao quanto a alguns temas. Por que a escolha de programar para Windows ao invs de Linux? Simples, porque o sistema operacional mais presente no nosso cotidiano est certo, reconheo que sou horrvel como usurio Linux, mas tenho meus motivos. Ns ainda temos muito mais contato com o sistema operacional da Microsoft do que com distribuies Linux. Primeiramente porque h muito dinheiro envolvido neste produto em eterno desenvolvimento chamado Windows e a comunidade open source no consegue acompanhar o ritmo imposto pela empresa que plagia de forma descarada as melhores idias dos sistemas livres. E, apesar de o incentivo ao uso de sistemas operacionais e softwares abertos ou livres fazer parte das polticas do Governo e de muitas empresas espalhadas pelo pas (entenda esse comportamento como uma tentativa de reduo de custos), ns ainda estamos aqum da meta de conseguir o suporte necessrio para estes mesmos programas que usamos. A comunidade open source forte e continua a crescer, mas dentro dos exemplos de sucesso sempre h ao menos uma empresa de grande porte ditando os rumos do projeto, o que falta no Brasil. Ainda que o Governo incentive o uso do software livre, o mesmo no ocorre quanto sua manuteno e, mais importante at, ao seu desenvolvimento. E assim, projetos bons acabam morrendo vide o caso da distro Kurumin (que inclusive foi adotada pelo Governo e usada por muito tempo por essas empresas que vendem computadores de baixo custo nos hipermercados...). Como usurio Windows, assim como outros tantos, decidi estudar um pouco sobre este sistema operacional e compartilho neste documento parte do que aprendi e do que uso como referncia sempre que necessrio (estou longe de ser um desses programadores natos, talvez seja essa a razo de tentar reunir tudo em um s lugar, praticando um pouco deste saber). Disponibilizo este material como uma forma de incentivo, um pontap inicial para que os estudantes de programao continuem a buscar conhecimento sobre o sistema operacional que utilizam. E por fim tente tirar o mximo de proveito deste material. -- Ncolas I. Teixeira

C++ Win32 Tutorial

Exibindo um diretrio, parte 1.

Certo, ento seja l o que tenha em mente, seu programa precisa saber que arquivos esto em certo diretrio. Como fazer isso? Voc ficar contente em saber que esta tarefa fcil! Duas rotinas da Win32 API fazem justamente o que voc quer: FindFirstFile() e FindNextFile(). Primeiramente, por que duas rotinas? Bem, os designers da API poderiam ter criado uma rotina que devolveria um array de strings, mas pondere sobre o problema deles: poderiam existir centenas de milhares de arquivos, cada um com dezenas de caracteres em seus nomes. A estrutura de memria necessria para manter essa diviso deveria ser enorme! E s fica pior! O nome de arquivo (ou filename, como preferir) apenas uma das informaes que o Windows armazena para cada arquivo. Ele tambm mantm a data de criao, a data da ltima modificao, seu tamanho etc. plenamente possvel que um diretrio grande contendo arquivos com nomes longos, com todos os dados auxiliares inclusos, possa exigir mais memria do que o processo permite. A soluo escolhida foi ter uma rotina inicial que basicamente tira uma foto instantnea do diretrio e devolve a informao sobre o primeiro arquivo encontrado. Voc, ento, chama a segunda rotina repetidamente, onde, a cada chamada, a segunda rotina devolve a informao sobre o arquivo seguinte no diretrio, at que no existam mais arquivos. Na verdade h uma terceira rotina que ns precisaremos usar, mas falemos mais sobre a mesma posteriormente. Vamos dar uma olhada nas duas. Os prottipos das mesmas na MSDN so mostrados aqui...
HANDLE FindFirstFile( LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData );

... e...
BOOL FindNextFile( HANDLE hFindFile, LPWIN32_FIND_DATA lpFindFileData );

... ento vejamos como us-las para fazer algo til. Comecemos com a FindFirstFile(). O primeiro parmetro o nome do arquivo a se buscar. Certamente isso pode envolver os caracteres coringas do Windows. O outro parmetro um ponteiro para uma estrutura WIN32_FINDA_DATA esta estrutura de memria que o Windows ir preencher para voc com todos os dados sobre o arquivo. Ela descrita assim...
typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[ MAX_PATH ]; TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA;

... existem campos para atributos (provavelmente o mais comum Read Only, mas existem vrios outros); estruturas FILETIME para as datas/horas de Criado em, Acessado em e Modificado em; campos que armaze-

C++ Win32 Tutorial

nam o tamanho do arquivo (mais sobre isso depois...); alguns reservadores de memria pra expanso futura (ignore estes...); e dois filenames: um o nome verdadeiro do arquivo e o outro o filename moda antiga, no formato 8.3. Se nenhum erro ocorrer, FindFirstFile() devolve um Search Handle usado como um contexto para a busca. Contexto, como assim? Considere esse aplicativo: suponha que voc deseje ver se o mesmo arquivo existe em dois diretrios, voc chama FindFirstFile() com a string de busca apontando para um diretrio, ento novamente o faz no segundo diretrio. Se o primeiro arquivo no segundo diretrio no corresponder a um acerto, voc iria tentar o prximo arquivo no segundo diretrio, ento voc chamaria FindNextFile(). Mas como o sistema sabe que voc quer o prximo arquivo no segundo diretrio? De repente voc poderia estar fazendo o pedido no primeiro, no mesmo? para isso que este handle (manipulador) serve: voc passa o manipulador de busca que devolvido pela chamada de FindFirstFile() para FindNextFile(), ento agora o sistema saber qual deles que ns queremos. O search handle liga uma chamada a FindNextFile() com uma chamada a FindFirstFile(). O manipulador de busca o primeiro parmetro para a rotina FindNextFile(), o segundo , novamente, um ponteiro para uma estrutura WIN32_FIND_DATA. FindNextFile() devolve true se existir outro arquivo para devolver e no h erros. Acima, mencionei que usaramos uma terceira rotina, que a FindClose(). Quando terminamos o registro do diretrio, usamos FindClose() para finalizar o manipulador de busca, dizendo ao sistema que terminamos essa operao de busca em particular. O prottipo para FindClose() est aqui...
BOOL FindClose( HANDLE hFindFile );

... simples, no? Dado o que ns vimos at agora, podemos escrever um programa muito modesto que listar os arquivos de um diretrio os exibindo na tela. Aqui est o fonte...
#include <iostream> #include <windows.h> using namespace std; int main() { HANDLE hFind; WIN32_FIND_DATA FindData; cout << "Um demo basico de FindFirst/Next.\n" << endl; // Encontra o primeiro arquivo hFind = FindFirstFile("C:\\Windows\\*.exe", &FindData); cout << FindData.cFileName << endl; // Procura por outros arquivos while (FindNextFile(hFind, &FindData)) { cout << FindData.cFileName << endl; } // Encerra o manipulador(handle) dos arquivos FindClose(hFind); return 0; }

... aqui eu indiquei grosseiramente o local de busca (direto no cdigo), procurando todos os arquivos .exe no meu diretrio Windows. Quando eu executei esse programa, o resultado se pareceu com isso

C++ Win32 Tutorial

... ento o que ns fizemos aqui foi um file logger bem simples. Sendo mais criterioso, sabemos que h mais do que isso para se ver. O que acontece, por exemplo, se no houver arquivos que cumpram os requisitos da busca? E se no existirem arquivos .exe no nosso diretrio? Vamos dar uma olhadinha mais de perto nessa pergunta e em outros problemas em potencial na prxima sesso.

C++ Win32 Tutorial

Exibindo um diretrio, parte 2.


Como exerccio, mudemos o pathname no cdigo de tal forma que FindFirstFile() no encontre nada. Pedi para que o programa procurasse por "C:\\Windows\\*.exz" (no existem arquivos .exz, eu conferi), e consegui isso...

... nada elegante e dificilmente de alguma valia para o nosso programa. Na parte 1 deste tutorial, eu disse que FindFirstFile() devolve um manipulador de busca se no houver erros. Se a rotina incluir o caso onde nenhum arquivo confere com a especificao (ou seja, um erro), ela devolve uma constante INVALID_HANDLE_VALUE. Melhor do que ser otimista e assumir que FindFirstFile() est devolvendo um manipulador vlido, devemos conferir se isso est de fato acontecendo seremos futuros engenheiros e programadores, devemos nos acostumar a pensar nos infinitos problemas que podem aparecer (Lei de Murphy e da Perversidade da Matria so fatos presentes em nossas vidas, no mesmo?). Ns tambm precisamos saber se a razo do INVALID_HANDLE_VALUE foi pelo fato de o programa realmente no encontrar arquivos ou se algum outro erro ocorreu. Para fazer isso, utilizaremos a rotina GetLastError() que freqentemente usada para se obter mais informaes sobre as razes pela qual uma rotina da API devolveu um erro de estado. uma rotina que voc vir a cham-la de amiga! Modifiquei parte do cdigo original para acrescentar essas verificaes
hFind = FindFirstFile("C:\\Windows\\*.exe", &FindData); if (hFind == INVALID_HANDLE_VALUE) { ErrorCode = GetLastError(); if (ErrorCode == ERROR_FILE_NOT_FOUND) { cout << "Nao existem arquivos correspondentes neste local\n" << endl; } else { cout << "FindFirstFile() devolveu o codigo de erro " << ErrorCode << endl; } } else { cout << FindData.cFileName << endl; }

... e tambm adicionei essa linha s declaraes de variveis...


int ErrorCode;

... agora, procurando por arquivos .exz consegui isso...

... um pouco melhor. J um comeo. Por que eu digo um pouco melhor, ainda no terminamos? No, no terminamos. Parece que estamos conseguindo a sada correta, mas na verdade a chamada a FindNextFile() est falhando: o manipulador de busca

C++ Win32 Tutorial

invlido. Na realidade, se no existem arquivos, ns no precisamos nem chamar FindNextFile() ou FindClose() pelo fato de a busca nunca ter sido aberta, iniciada. Agora ns adicionaremos essa linha s declaraes...
bool Continue = true;

... e alteraremos o bloco if() no cdigo acima de tal forma que se a chamada a FindFirstFile() devolver INVALID_HANDLE_VALUE, a varivel booleana Continue ser configurada para false...
if (hFind == INVALID_HANDLE_VALUE) { ErrorCode = GetLastError(); if (ErrorCode == ERROR_FILE_NOT_FOUND) { cout << "Nao existem arquivos correspondentes neste local\n" << endl; } else { cout << "FindFirstFile() devolveu o erro " << ErrorCode << endl; } Continue = false; } else { cout << FindData.cFileName << endl; }

... agora quando pegamos a segunda parte do programa, ns podemos testar Continue para ver se precisamos chamar outras rotinas ou no, desta maneira...
if (Continue) { while (FindNextFile(hFind, &FindData)) { cout << FindData.cFileName << endl; } FindClose(hFind); }

... ento agora ns terminamos? Huuum, honestamente, no. Devo relembr-los da primeira parte do tutorial quando disse que FindNextFile() devolve false se no existirem mais arquivos ou se um erro ocorrer. Agora, de forma realstica, pouco provvel que uma devoluo false signifique algo mais, ento, para a grande maioria dos casos, o que ns temos agora est bom o suficiente. Se quisssemos fazer um trabalho mais profissional, deveramos conferir a devoluo de ambos FindNextFile() e FindClose() que tambm devolve false se encontrar um erro. Ambas conferncias so feitas neste programa...
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hFind; WIN32_FIND_DATA FindData; int ErrorCode; bool Continue = true; cout << "Uma demonstracao decente de FindFirst/Next.\n" << endl; hFind = FindFirstFile("C:\\Windows\\*.exe", &FindData); if (hFind == INVALID_HANDLE_VALUE) { ErrorCode = GetLastError(); if (ErrorCode == ERROR_FILE_NOT_FOUND) { cout << " Nao existem arquivos correspondentes neste local\n" << endl; } else { cout << "FindFirstFile() devolveu o erro " << ErrorCode << endl; } Continue = false; } else { cout << FindData.cFileName << endl; } if (Continue) {

C++ Win32 Tutorial

while (FindNextFile(hFind, &FindData)) { cout << FindData.cFileName << endl; } ErrorCode = GetLastError(); if (ErrorCode == ERROR_NO_MORE_FILES) { cout << endl << "Todos arquivos exibidos." << endl; } else { cout << "FindNextFile() devolveu o erro " << ErrorCode << endl; } if (!FindClose(hFind)) { ErrorCode = GetLastError(); cout << "FindClose() devolveu o erro " << ErrorCode << endl; } } return 0; }

... agora ns temos um file logger! Se especificarmos "*.*" como a mscara de arquivos, conseguiremos enxergar o diretrio inteiro. Existem dois arquivos curiosos que podem nos surpreender: um chamado . e outro chamado ... O primeiro uma auto-referncia ao diretrio que est sendo examinado e o segundo uma referncia ao diretrio pai do mesmo. O melhor conselho que posso dar ignorar estes arquivos e no tentar fazer qualquer tratamento com eles. H algo mais a dizer? Na verdade, vrias pessoas j me fizeram perguntas sobre o tutorial que eu adicionei na terceira parte, de como lidar com alguns temas adicionais. um assunto que ser abordado na prxima parte do tutorial e que voc pode gostar de ler, ou ento ignorar completamente... apesar de que no custa nada dar uma olhada na seo seguinte.

C++ Win32 Tutorial

Exibindo um diretrio, parte 3.


Eu comecei a parte 2 deste tutorial alterando a mscara do arquivo que estava buscando (de .exe para .exz). Fiz aquilo para demonstrar o comportamento do programa se no houvesse arquivos que atendessem a essa condio. Eu irei comear essa pgina mudando o caminho de C: para H: (eu no tenho um drive h:). Quando fao isso, o programa que fizemos nas pginas 8 e 9 mostra isso como sada...

...porque FindFirstFile() diferencia entre no conseguir encontrar um arquivo correspondente busca e uma tentativa de busca em um caminho invlido. Aqui, eu estendi o teste para permitir essa diferenciao eventualmente...
if (hFind == INVALID_HANDLE_VALUE) { ErrorCode = GetLastError(); if (ErrorCode == ERROR_FILE_NOT_FOUND) { cout << "Nao existem arquivos correspondentes neste local\n\n" << endl; } else if (ErrorCode == ERROR_PATH_NOT_FOUND) { cout << "O caminho especificado e' invalido\n" << endl; } else { cout << "FindFirstFile() devolveu o erro " << ErrorCode << endl; } Continue = false; } else { cout << FindData.cFileName << endl; }

... este programa mostra isso para o caminho H: ...

... como esperado. Outra coisa que geralmente as pessoas perguntam sobre o modo como especifico o caminho/mscara neste caso, "C:\\Windows\\*.exe". possvel, obviamente, usar uma varivel ao invs da forma literal. Estes tutoriais so para demonstrar tcnicas e no para produzir programas completos. Nos seus aplicativos, certamente voc ter uma varivel contendo seu caminho/mscara na chamada rotina FindFirstFile(). Surpreendentemente, s vezes me perguntam o porqu do uso de duas barras invertidas. Isso pelo fato de C e C++ serem linguagens que usam o caractere da barra invertida para introduzir um caractere especial dentro de strings \n, por exemplo, interpretado como um caractere de nova linha. Para se conseguir um caractere de barra invertida dentro do literal da string, usam-se duas barras invertidas, por isso "\\" interpretado como "\". Se voc no gosta das duas barras invertidas, use um nico caractere de barra, assim como nos sistemas Unix. Sendo assim, "C:\\Windows\\*.exe" e "C:/Windows/*.exe" funcionaro e produziro o mesmo resultado.

10

C++ Win32 Tutorial

E os outros campos na estrutura WIN32_FIND_DATA, como eles so usados? Relembremos primeiro a definio da estrutura
typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[ MAX_PATH ]; TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA;

... agora vamos dar uma olhada mais cuidadosa nela. Atributos so associados a todos os arquivos, dizendo algo sobre o mesmo. Existe uma lista inteira destes atributos, embora eu duvide que voc use a maioria deles eu s utilizei alguns poucos at hoje. A lista completa at o momento
FILE_ATTRIBUTE_ARCHIVE FILE_ATTRIBUTE_COMPRESSED FILE_ATTRIBUTE_DIRECTORY FILE_ATTRIBUTE_ENCRYPTED FILE_ATTRIBUTE_HIDDEN FILE_ATTRIBUTE_NORMAL FILE_ATTRIBUTE_OFFLINE FILE_ATTRIBUTE_READONLY FILE_ATTRIBUTE_REPARSE_POINT FILE_ATTRIBUTE_SPARSE_FILE FILE_ATTRIBUTE_SYSTEM FILE_ATTRIBUTE_TEMPORARY

... Eu digo at o momento porque o Windows um produto em desenvolvimento contnuo e a lista pode mudar no futuro. Se voc est interessado em saber o que so todos estes atributos e o que fazem, procure o help do seu compilador ou consulte a MSDN. (Se o link no funcionar, procure por "MSDN" no Google e ento navegue at a biblioteca por alguma razo, a Microsoft est sempre mudando suas URLs, dificultando manter os links atualizados).
FILE_ATTRIBUTE_DIRECTORY bastante til se o arquivo devolvido por FindFirstFile() ou FindNextFile()

for um diretrio ao invs de um arquivo normal. Voc usar isso se planejar processar arquivos em um diretrio ou em qualquer um dos seus subdiretrios. FILE_ATTRIBUTE_READONLY outro que pode vir a ter algum uso e, impreterivelmente, true se o arquivo foi configurado para "read only". Para testar se algum atributo em particular foi estabelecido para um arquivo, faa um AND bit a bit entre o membro dwFileAttributes da estrutura WIN32_FIND_DATA e o atributo em que voc est interessado. No exemplo seguinte modifiquei o programa desenvolvido nas duas primeiras partes do tutorial para apontar arquivos READONLY, lembrando que essa modificao deve ser feita para ambos os blocos FindFirstFile() e FindNextFile().
{ cout << FindData.cFileName; if (FindData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { cout << " Arquivo READONLY!"; } cout << endl; }

... no meu diretrio Windows, o programa retornou esta sada...

11

C++ Win32 Tutorial

... note que h trs arquivos alcrmv.exe, alcupd.exe e SOUNDMAN.EXE esto marcados como READ ONLY. Interessante que na minha mquina com Windows 7 no h arquivos somente leitura, diferentemente do que ocorre na mquina com o XP (essa a explicao pelo fato de o console ter essa aparncia diferente: usei outro sistema). As datas/horas de criao, ltima escrita e ltima modificao dos arquivos so devolvidas em uma estrutura
FILETIME. Tal estrutura no muito til para uso na sada, mas a API fornece uma rotina FileTimeToSystemTime() que converte esses valores em uma estrutura SYSTEMTIME que contm vrios campos que voc pode produzir como sada. A estrutura SYSTEMTIME declarada como... typedef struct _SYSTEMTIME { WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; } SYSTEMTIME;

O exemplo a seguir exibe a converso e sada da data de criao, entretanto, todas a trs informaes estatsticas podem ser tratadas do mesmo modo. Eu adicionei esta linha s declaraes...
SYSTEMTIME SysTime;

... e isso aos blocos de sada...


{ cout << FindData.cFileName << " "; FileTimeToSystemTime(&FindData.ftCreationTime, &SysTime); cout.width(2); cout.fill('0'); cout << SysTime.wDay << "/"; cout.width(2); cout.fill('0'); cout << SysTime.wMonth << "/"; cout << SysTime.wYear << " "; cout.width(2); cout.fill('0'); cout << SysTime.wHour << ":";

12

C++ Win32 Tutorial

cout.width(2); cout.fill('0'); cout << SysTime.wMinute << ":"; cout.width(2); cout.fill('0'); cout << SysTime.wSecond; cout << endl; }

... a sada deste programa agora se parece com isso...

... , eu sei que a formatao est muito a desejar deixarei isso como um exerccio para o leitor! Existem alguns outros campos disponveis na estrutura SYSTEMTIME que podem ser de interesse para alguns programadores. O modo como os criadores da API escolheram para devolver o tamanho do arquivo um pouquinho estranho. O fato que se fosse usado somente um nico valor de 32bit, o sistema operacional no conseguiria enxergar arquivos maiores que 4GB, entretanto, os desenvolvedores usam outras construes 64bit na estrutura WIN32_FIND_DATA, ento porque o mesmo no acontece para este campo? Bom, o que eles fizeram foi devolver dois valores 32bit, o primeiro representando os 32 bits mais significativos do tamanho e o segundo com os 32 bits menos significativos e, desta sorte, somos forados a nos contentar com este fato j que assim que funciona. Se voc tem certeza absoluta que seus arquivos tero menos de 4GB, voc pode ignorar o membro nFileSizeHigh e usar nFileSizeLow como o tamanho do arquivo. Estou certo que os arquivos no meu diretrio Windows tm menos de 4GB, ento eu posso produzir uma sada para seus tamanhos usando...
{ cout << FindData.cFileName; cout << " " << FindData.nFileSizeLow << endl; }

... a sada deste programa se parece com isso...

13

C++ Win32 Tutorial

... se voc precisar usar um valor de 64bit, carregue os valores 32bit em uma union LARGE_INTEGER. Ela declarada como...
typedef union _LARGE_INTEGER{ struct{ DWORD LowPart; LONG HighPart; }; LONGLONG QuadPart; } LARGE_INTEGER;

... declare a Union assim...


ULARGE_INTEGER FileSize;

... tambm poderamos usar LARGE_INTEGER, mas uma vez que um tamanho de arquivo negativo no tem significado, usando a variante unsigned nos fornecer um maior escopo. Prepare a unio desta forma...
FileSize.LowPart = FindData.nFileSizeLow; FileSize.HighPart = FindData.nFileSizeHigh;

... e ento use FileSize.QuadPart para operaes 64bit. O ultimo campo que usaremos o cAlternateFileName. Ele simplesmente contm o equivalente para o formato 8.3 do nome do arquivo. Modifiquei o programa para exibir ambos os nomes de arquivos...
{ cout << FindData.cFileName; cout << " " << FindData.cAlternateFileName << endl; }

... o que nos fornece a sada...

14

C++ Win32 Tutorial

... note que os nomes compridos tm filenames 8.3. Aqueles que j estavam em um formato 8.3 tm um campo cAlternateFileName vazio e sim, como d para ver acima, tive de improvisar. A ltima coisa que quero abordar neste tutorial diz respeito ao que chamamos de case sensitivity ou no bom portugus: maisculas e minsculas. A mscara passada para FindFirstFile() no case sensitive se voc der uma olhada em alguns dos resultados que mostrei at agora, eles incluem ambos arquivos .exe e .EXE, mesmo embora eu tenha sempre passado *.exe como mscara de busca. Existe uma segunda funo da API, FindFirstFileEx(), que muito parecida com a FindFirstFile() que usamos em todo o tutorial. Cheguei a esta funo quando pesquisava (h muito tempo) como fazer uma busca case sensitive de arquivos. Buscando pela internet, encontrei uma referncia dentro da documentao que vinha com o antigo VC++ 6.0 Pro dizendo...
dwAdditionalFlags Specifies additional flags for controlling the search. You can use the FIND_FIRST_EX_CASE_SENSITIVE flag for case-sensitive searches. The default search is case insensitive. At this time, no other flags are defined.

... e, sendo sincero, na poca (quase trs anos atrs) no consegui faz-la funcionar. A documentao mais atualizada (que a referncia online que o Visual Studio fornece, ou seja, a da MSDN hoje, 07/04/2010) diz algo diferente. Interessante que encontrei duas referncias mesma funo, uma de uso geral e outra como funo do Windows o que me deixou momentaneamente ainda mais intrigado: que outro SO a Microsoft andou desenvolvendo nos ltimos 20 anos? Qu se pasa, Microsoft? Bom, de qualquer forma, a descrio para o caso geral ...
dwAdditionalFlags [in] Unsupported; set to zero.

... enquanto que a para o caso Windows


dwAdditionalFlags [in] Unsupported; set to zero. Value FIND_FIRST_EX_CASE_SENSITIVE 1

Meaning Searches are case-sensitive. Uses a larger buffer for directory queries, which can increase performance of the find operation.

FIND_FIRST_EX_LARGE_FETCH 2

Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP/2000: This value is not supported until Windows Server 2008 R2 and Windows 7.

15

C++ Win32 Tutorial

logo, a concluso a que chego que a prpria Microsoft no tinha conseguido programar esta funcionalidade at pouco tempo. Considerando que ela foi proposta na poca do VC++ 6.0 (no encontrei referncias mais antigas funo e sendo que este data de 1998), ento somente este ano, junto com o lanamento do Windows 7 e da verso Server 2008 R2, que eles conseguiram resolver esta pendncia.

16

C++ Win32 Tutorial

Trabalhando com o Console no Windows, parte 1.

Que levante a mo quem j fez algo semelhante a isto quando comeou a programar! De repente em outra linguagem, talvez em outro sistema operacional, mas certamente foi algo como isso. Algum no se identificou com a situao? Voc no quer ver o cdigo fonte, quer? Est aqui, ento.
#include <iostream> using namespace std; int main(){ cout << "Hello World!" << endl; return 0; }

Sete linhas. Poderiam ser menos, mas para qu tornar as coisas mais difceis de ler? Essa uma das grandes vantagens de um programa para console: voc no precisa ter muito trabalho para conseguir criar um aplicativo que 4 funcione. por isso que eu adoro o console. Se eu preciso testar algum cdigo sem GUI e no tenho nenhum aplicativo adequado que possa usar como framework, crio um programa para console e estou certo de que qualquer outro programador para Windows age da mesma forma. A interface com o usurio precisa ser sofisticada somente o suficiente para conseguir fazer o trabalho bem feito. Dificilmente voc criar algo que precise de interface grfica, tanto como engenheiro quanto como cientista da computao! muito provvel que voc precise desenvolver somente aplicaes back-end, seja em sistemas embarcados ou para PC, ao invs que projetar janelas coloridas que esperem por cliques do usurio. Como exemplo, na rea de device drivers a maior parte da codificao desenvolvida em duas vertentes: o firmware do dispositivo e o programa que faz acesso a ele pelo PC. Neste campo muito interessante ter conhecimento de 5 Assembly, C e at mesmo a tupiniquim LUA (infelizmente, C++ pouco usada em microcontroladores ). Cada linguagem tem seu nicho, seu propsito, e a maior parte da codificao ficar longe dos olhos do usurio e, desta forma, da interface grfica. Ah, mas o console sem graa e d para fazer pouca coisa com ele, no mesmo? Engano seu, jovem Padawan. Nos tutoriais seguintes mostrarei algumas coisas interessantes que d para fazer no prompt de comando do Windows. E, acredite ou no, so vrias as perguntas acerca da programao para console todas as semanas nos fruns o que, para mim, apenas demonstra que as pessoas esto interessadas neste aspecto. O que o prompt de comando? um programa para Windows que age como um interpretador de linhas de comando la DOS. Ou se preferir, um emulador de terminal de texto. Ou de forma medocre e pouco correta, poderamos dizer at mesmo que o console uma janela, certamente um tipo especial, mas uma janela. Talvez voc j tenha escutado o termo "DOS boxes" fazendo referncia ao console pelo fato de o DOS ser uma UI base6 ada em caracteres, mas este termo tambm pouco correto. No existe DOS sob os sistemas operacionais com arquitetura NT (NT, 2000, XP, Vista, Server e 7), e ainda assim voc pode usar um console. Por qu? Porque um programa que roda em uma janela. Para o restante desta seo, assumirei que o console do leitor est com suas configuraes padro. O que torna o console especial que ele baseado em caracteres ao invs de ser em pixels. Uma janela normal, como a deste leitor de documentos, por exemplo, medida em pixels, onde cada um enderevel e modific-

4 5

Graphical User Interface Existem controladores que suportam C++, como a linha Arduino e o M16C, mas eles ainda apresentam uma srie de limitaes. 6 Existem emuladores de DOS para estas plataformas, mas isso foge ao escopo deste documento

17

C++ Win32 Tutorial

vel individualmente. Este no o caso em um meio baseado em caracteres, onde a menor unidade que pode ser endereada e, portanto, modificada uma clula de caractere. A rea de exibio de um console consiste de linhas e colunas de clulas de caracteres. uma grade ou um vetor bidimensional ou uma matriz de clulas, todos estes termos so equivalentes e igualmente utilizados. NormalNorma mente, a clula de caracteres no topo esquerdo a origem ou posio home, e designada como um ponto (0,0) em um eixo (x,y) imaginrio, A clula de caractere imediatamente direita da home (1,0), a prxima (2,0) e assim por diante. Em uma janela de console tpica existem 80 posies de clulas na horizontal e 25 na vertical. Assim, em tal console, onsole, a clula no canto inferior direito ser a (79,24), , lembrando que a grade tem sua origem nos zeros. A pequena gravura abaixo exibe graficamente o nmero de algumas clulas da poro superior ese querda da tela (ou a matriz completa de um console 4x4, no mesmo?).

A razo pela escolha destes valores 80x25 histrica. Antes de os terminais grficos serem amplamente utiliutil zados pela e at mesmo disponveis para a grande massa, as pessoas usavam terminais baseados em caractecaract res chamados "Visual Display Units", ou o VDU's, que tinham tipicamente uma tela com layout 80x25. Cada clula de caractere, como o nome denuncia, pode conter somente um caractere, caractere, diferentemente do que acontece com janelas baseadas em pixels. facilmente perceptvel esta diferena se eu, por exemplo, digitar cinco caracteres w, , e na linha de baixo cinco caracteres i... wwwww iiiii As linhas, assim como maior parte deste documento, docu foram editadas com a fonte Calibri, Calibri que no monoespaada, e voc pode notar a diferena no comprimento das da mesmas. Escrevi um aplicativo console que mostra os mesmos caracteres, sendo a sada... ...

Como podemos ver, cada clula possui um nico caractere o ultimo caractere i est diretamente abaixo do ultimo caractere w. Certo, ento ns temos uma grade de clulas, mas o que uma clula? De forma simplria, uma clula outra grade, onde cada clula tem 8 pixels de largura por 12 de altura. Os caracteres que compem a fonte devem ser representados dentro desta grade, incluindo quaisquer espaamentos isso mesmo, os pixels de uma clula esto diretamente adjacentes aos pixels da prxima. Isto de certa forma uma limitao, mas como veremos adiante, esta caracterstica tem suas vantagens. As figuras abaixo exibem como os caracteres w e i so desenhados em uma clula.

Note que uma coluna em branco direita do w assegura que dois w adjacentes tero pelo menos um pixel de espao entre eles. Perceba tambm que o ponto do i no alcana o topo da clula, o que assegura que qualqua

18

C++ Win32 Tutorial

quer caractere na linha acima a este i poder ser facilmente identificvel. De forma resumida, os caracteres normais so representados em grades 7x11 com uma linha em branco no topo e uma coluna em branco na margem direita. Os pixels marcados com um X na figura acima so exibidos (ou aportuguesando, renderizados) na cor do primeiro plano (ou foreground, como preferir) e os pixels vazios na cor de fundo (background). Por padro, o primeiro plano branco ou cinza claro, enquanto que o fundo preto. Mais adiante mostrarei como mudar estas cores. Existe um espao de um pixel nas margens da rea de exibio do console. Isto evita que os caracteres que fazem fronteira com os cantos superiores, inferiores ou laterais se fundam com a moldura da janela (ou frame, como preferir). Voc no pode escrever neste espao. Desta forma, a rea de exibio de um console de 2+(8x) pixels de largura, onde x o nmero de caracteres na linha, por 2+(12y) pixels de altura, onde y o nmero de linhas visveis podem existir mais linhas acima ou abaixo da visualizao atual, depende da posio da barra de rolagem. A primeira parte deste tutorial acabou se revelando apenas uma teoria pura. Mas muito do que feito no console assume que voc entenda essa mesma teoria que bsica, convenhamos. Na prxima parte mostrarei como dar nome ao seu console, obter alguns manipuladores padro, mover o cursor, escrever blocos de caracteres e limpar a tela.

19

C++ Win32 Tutorial

Trabalhando com o Console no Windows, parte 2.


Eu comecei a parte 1 deste tutorial com o clssico programa Hello World!. A sada se parecia com isso

Por padro, o Windows coloca o caminho do executvel no ttulo da janela. Podemos mudar isso para uma palavra ou frase qualquer. Aqui est o Hello World! modificado para fazer isso.
#include <iostream> #include <windows.h> using namespace std; int main(){ SetConsoleTitle("Hello!"); cout << "Hello World!" << endl; return 0; }

Perceba que adicionei o cabealho windows.h ao programa e uma chamada a SetConsoleTitle(). A sada agora se parece com isso.

Quase todas as rotinas da API devolvem um status indicando sucesso ou falhas/erros. Para tornar o cdigo de exemplo deste tutorial mais didtico, no estou conferindo este valor. Para um trabalho mais profissional, devemos sempre conferir todos os valores devolvidos pelas rotinas da API que so chamadas. Mesmo que no possamos recuperar a execuo do aplicativo por causa de um erro, na hora de debugar o programa, freqentemente muito til saber qual rotina falhou e, se possvel, o porqu. Este programa mostra a conferncia do status devolvido e aborta o programa caso a rotina falhe perceba que inclu o cabealho process.h para a rotina exit() da API. Voc deve sempre consultar o help do seu compilador ou a MSDN para ver quais so o valores de devoluo da rotina.
#include <windows.h> #include <process.h> #include <iostream> using namespace std; int main(){ int Status; Status = SetConsoleTitle("Hello!"); If (Status == 0) { Status = GetLastError(); cout << "SetConsoleTitle() falhou! Motivo : " << Status << endl; exit(Status); } cout << "Hello World!" << endl; return 0;

20

C++ Win32 Tutorial

Todos os consoles tm trs manipuladores padro e muitas das funes de operao do console precisam de um manipulador para efetuar I/O. Um manipulador simplesmente um inteiro de 32bit e representa o modo como o Windows diferencia entre objetos do mesmo tipo. Consideremos o console: ele tem uma barra de ttulo, botes de minimizar, maximizar e encerrar, uma ou mais barras de rolagem etc. Quando se pensa sobre isso, algo um tanto complicado de se lidar e em algum lugar deve existir um monte de dados que o sistema est usando para fazer tudo isso funcionar. O Windows esconde toda esta parte complexa do usurio (voc pode brincar com estes dados se quiser, claro), mas o ponto aqui que voc no tem de faz-lo se no quiser. O Windows toma conta disso e tudo o que voc tem de fazer dizer a ele o manipulador, ou seja l o que for que voc quer usar. Quando voc se aprofundar um pouco mais na programao para Windows, ver que vrias coisas so usadas se fornecendo um manipulador. Se voc no consegue entender isso agora, no se preocupe: um manipulador fcil de conseguir e fcil de usar. Para conseguir os manipuladores padro, declare uma varivel do tipo HANDLE e a inicie com uma chamada a GetStdHandle(). Esse programa (que na verdade no faz nada!), ilustra o processo. Usaremos os manipuladores padro mais tarde.
#include <windows.h> int main(){ HANDLE hIn; HANDLE hOut; HANDLE hError; hIn = GetStdHandle(STD_INPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE); hError = GetStdHandle(STD_ERROR_HANDLE); return 0; }

O manipulador de entrada usado com rotinas que lem os dados de um console, o manipulador de sada com rotinas que enviam dados para um console. O manipulador de erros tambm lida com sadas para o console por padro e, francamente, pouqussimo utilizado apenas esteja ciente disso. Existem duas rotinas ReadConsole() e WriteConsole() que realizam a I/O usando esses manipuladores, mas, ao menos por enquanto, continuaremos com as funes de I/O padro do C++. Agora que conseguimos os manipuladores, vamos fazer algo com eles: mover o caret pela tela. Para fazer isso, precisaremos usar a estrutura COORD. Esta uma estrutura muito simples contendo uma coordenada x e y. Ela declarada da seguinte forma.
typedef struct _COORD{ SHORT X; SHORT Y; } COORD;

Para mover o cursor, simplesmente configuramos a COORD para as coordenadas que queremos e chamamos a funo SetConsoleCursorPosition() da API. Aqui est um programa simples que faz isso.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; int i; COORD Position; hOut = GetStdHandle(STD_OUTPUT_HANDLE);

21

C++ Win32 Tutorial

for (i=0; i<10; i+=2) { Position.X = i; Position.Y = i; SetConsoleCursorPosition(hOut, Position); cout << "*" << flush; } return 0; }

A sada se parece com isso.

Como podemos notar, cada repetio do loop move o cursor duas linhas abaixo e duas colunas para a direita. Para retornar o cursor sua posio home (canto superior esquerdo), mande-o para (0,0). Usos criativos desta funo podem resultar em efeitos agradveis. A prxima rotina que quero trabalhar a FillConsoleOutputCharacter(). Essa rotina permite ao programador escrever uma srie de caracteres no console de uma vez. O prottipo da funo mostrado aqui.
BOOL FillConsoleOutputCharacter( HANDLE TCHAR DWORD COORD LPDWORD hConsoleOutput, cCharacter, nLength, dwWriteCoord, lpNumberOfCharsWritten );

Um pouco mais complicado que qualquer rotina que usamos at agora mas muito fcil de usar. O primeiro parmetro o manipulador para o buffer de sada voc j sabe como conseguir isso. O prximo o caractere que voc deseja escrever ainda mais fcil. O prximo um nmero indicando quantas vezes voc quer escrever o caractere qual a dificuldade nisso? O prximo uma COORD dizendo onde comear tambm j vimos isso. Finalmente, a rotina quer saber de um lugar onde possa nos dizer quantos caracteres ela escreveu de fato isto tem de ser, portanto, o endereo de uma varivel no seu programa. Voc pode se questionar o porqu deste ltimo argumento: consideremos a situao onde o buffer da sua tela de, por exemplo, 5000 caracteres. Voc diz rotina para escrever 5100: ela no devolver um erro, mas preencher os 5000 caracteres e ento dir a voc que fez aquilo usando o valor limite do buffer cada dispositivo de sada nico, logo cada buffer diferente. Se voc no est interessado, ignore este argumento, mas voc deve fornecer um lugar para a rotina usar. No exemplo seguinte, passei o endereo da varivel Written como &Written. Este programa usa a FillConsoleOutputCharacter() para desenhar um linha de 15 caracteres X comeando de (4,4) a sada do programa tambm mostrada abaixo.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; COORD Position; DWORD Written; hOut = GetStdHandle(STD_OUTPUT_HANDLE);

22

C++ Win32 Tutorial

Position.X = 4; Position.Y = 4; FillConsoleOutputCharacter(hOut, 'X', 15, Position, &Written); return 0; }

Vejamos o que acontece se eu mudar a sada para 150 ao invs de 15 caracteres.

O ponto que quero destacar que quando a rotina alcanou o fim da linha, ela continuou do comeo da linha seguinte. Suponha que ao invs da linha 5 coluna 5 eu tivesse dito (0,0), e suponha que eu tivesse usado um caractere de espao ao invs de X, e suponha tambm que eu tivesse pedido funo para escrever caracteres suficientes para encher completamente o buffer da tela Ora! Acabei de limpar a tela! Limpar a tela a pergunta mais comum sobre o console que aparece nos fruns. E aqui est a resposta. Por que opto por citar este exemplo ao invs de indicar a chamada ao sistema atravs de system(cls)? Porque de repente voc se encontra lidando com um display 24x2 de LEDs e no vai ser uma chamada ao sistema que resolver o seu problema alm de que precisamos monitorar o tipo de status que essa chamada devolve. Esta a soluo universal para este problema. Tambm possvel ler um ou mais caracteres de uma posio especfica no buffer da tela. Para fazer isso, usaremos a rotina ReadConsoleOutputCharacter() da API. Essa rotina tem parmetros muito semelhantes ltima. Seu prottipo mostrado aqui.
BOOL ReadConsoleOutputCharacter( HANDLE LPTSTR DWORD COORD LPDWORD hConsoleOutput, lpCharacter, nLength, dwReadCoord, lpNumberOfCharsRead );

Assim como antes, o primeiro parmetro o manipulador padro. O prximo (lembre-se que ns estamos lendo agora) um ponteiro para onde queremos guardar os caracteres. Os parmetros restantes so como na rotina anterior nmero de caracteres a processar, o ponto de partida e um ponteiro para o lugar onde podemos armazenar o nmero real de caracteres lidos. Esse programa usa a rotina duas vezes, uma vez para obter o caractere na posio (0,0) e uma segunda vez para recuperar cinco caracteres a partir da posio (4,0). Se o nmero de caracteres pedido maior que a linha atual, a leitura continuar a partir do incio da linha seguinte, assim como antes. E se o nmero pedido maior do que o tamanho do buffer, o caractere que simboliza o fim do buffer devolvido e o nmero real armazenado no parmetro final.
#include <iostream> #include <windows.h> using namespace std; int main(){

23

C++ Win32 Tutorial

HANDLE hOut; char Letter; char Letters[5]; COORD Where; DWORD NumRead; int i; hOut = GetStdHandle(STD_OUTPUT_HANDLE); cout << "Uma linha de poucas consequencias." << endl; Where.X = 0; Where.Y = 0; ReadConsoleOutputCharacter(hOut, &Letter, 1, Where, &NumRead); cout << "Letra em (0,0) e' " << Letter << endl; Where.X = 4; ReadConsoleOutputCharacter(hOut, Letters, 5, Where, &NumRead); cout << "5 letras a partir de (4,0) "; for (i=0; i<5; i++) { cout << Letters[i]; } cout << endl; return 0; }

A sada do programa se parece com isso.

Seria perfeitamente admissvel dimensionar o array Letters para 6, ler 5 caracteres do console e ento configurar o caractere final com NULL. Desta forma, cout poderia ter retirado a palavra da string j que agora termina com NULL ao invs de escrever os caracteres individualmente atravs de um loop. Na parte seguinte deste tutorial, brincaremos com as limitaes grficas do console: desenharemos linhas, caixas e grades.

24

C++ Win32 Tutorial

Trabalhando com o Console no Windows, parte 3.


Eu recomendo ndo que voc compile e execute o programa seguinte. . Ele mostrar os caracteres imprimveis do seu console. Se seu console de um tamanho diferente do meu, talvez haja necessidade de se ajustar os valores de x e y, mas essa uma tarefa simples. O resto do tutorial assume que seu mapa de caracteres o mesmo que uso possvel que haja um conjunto de caracteres de outro idioma carregado car no sistema e algumas posies podem ser diferentes.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; int i; int x = 0; int y = 0; COORD Position; hOut = GetStdHandle(STD_OUTPUT_HANDLE); for (i=32; i<=255; i++) { Position.X = x; Position.Y = y; SetConsoleCursorPosition(hOut, Position); cout.width(3); cout << i << " " << (unsigned char)i << flush; ++y; if(y > 20){ y = 0; x += 6; } } Position.X = 0; Position.Y = 22; SetConsoleCursorPosition(hOut, Position); return 0; }

A parte que nos interessa para este tutorial so os valores entre 179 e 218, mostrados abaixo.

Se recorrermos parte 1 deste tutorial, lembraremos que caracteres normais da fonte do console preenchem uma grade 7x11 em uma clula maior de 8x12 que tinha a linha superior e a coluna direita em branco para perpe

25

C++ Win32 Tutorial

mitir espaamentos. Como d para notar na figura acima, estes no so caracteres normais alguns deles se fundem com os que esto acima e abaixo deles, o que gera certo desconforto visual. Aqui esto os mesmos caracteres mais espaados, ficando mais fcil de visualizar.

Esses caracteres especiais no tm espaos na linha superior e nem na coluna direita. Eles foram criados propositalmente para se fundirem com o caractere vizinho. Desta forma, podemos desenhar linhas, grades e caixas. Uma sada que voc pode criar est mostrada abaixo.

As gravuras parecem tolas quando desenhadas neste tamanho, mas fiz isso apenas como exemplo e tambm porque no queria uma figura enorme nesta seo. Se voc olhar com ateno o mapa de caracteres, ver que so vrias as possibilidades. Para criar estas grades, escrevi uma funo bem simples que desenha um caractere em um lugar especfico da tela, mostrada aqui.
void At(int x, int y, unsigned char What){ static HANDLE hOut; static bool First = true; COORD Position; if (First) { hOut = GetStdHandle(STD_OUTPUT_HANDLE); First = false; } Position.X = x; Position.Y = y; SetConsoleCursorPosition(hOut, Position); cout << What << flush; return; }

Como podemos ver, no h conferncia de erros e nem do valor da varivel What, o programa simplesmente assume que voc sabe o que est fazendo. A funo recupera o manipulador de sada padro e o armazena estaticamente na primeira vez que invocada. Esse fragmento de cdigo mostra as chamadas necessrias para se desenhar a primeira caixa.

26

C++ Win32 Tutorial

At(1, 0, At(2, 0, At(3, 0, At(1, 1, At(10,1, At(3, 1, At(1, 2, At(2, 2, At(3, 2,

(unsigned (unsigned (unsigned (unsigned (unsigned (unsigned (unsigned (unsigned (unsigned

char)218); char)196); char)191); char)179); char) 32); char)179); char)192); char)196); char)217);

Dada a tabela de caracteres acima e esse exemplo simples (alis, 32 o caractere de espao), voc tem informaes mais do que suficiente para criar todos os tipos de caixas, grades, labirintos, formulrios, etc. Agora podemos at fazer jogos ASCII, como o antigo Rogue veremos a questo das cores mais adiante.

Enquanto desenhar caixas pode ser muito til, s vezes desejamos esconder ou modificar a aparncia do caret. Voc pode fazer isso com outra rotina da API chamada SetConsoleCursorInfo(). Ela recebe somente dois parmetros: um manipulador padro de sada e um ponteiro para a estrutura CONSOLE_CURSOR_INFO. Essa outra estrutura relativamente simples definida pelo Windows, se parecendo com isso...
typedef struct _CONSOLE_CURSOR_INFO { DWORD dwSize; BOOL bVisible; } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

O primeiro valor um inteiro simples, com valor de 1 a 100, e especifica o quanto a clula de caractere do cursor preenchida. O programa aqui configura o tamanho do cursor para 50. As trs figuras abaixo mostram os tamanhos 1, 50 e 100. Perceba o uso de um "&" antes de ConCurInf, lembre que a rotina espera um ponteiro para a estrutura que voc declarou.
#include <windows.h> int main(){ HANDLE hOut; CONSOLE_CURSOR_INFO ConCurInf; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTitle("Tamanho 50"); ConCurInf.dwSize = 50; ConCurInf.bVisible = true; SetConsoleCursorInfo(hOut, &ConCurInf); return 0; }

27

C++ Win32 Tutorial

O outro valor um campo booleano e indica se o cursor est visvel ou no. Um valor true (o padro) significa que o cursor est visvel, enquanto que false est invisvel. O programa seguinte oculta o cursor.
#include <windows.h> int main(){ HANDLE hOut; CONSOLE_CURSOR_INFO ConCurInf; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTitle("Sem Cursor"); ConCurInf.dwSize = 10; ConCurInf.bVisible = false; SetConsoleCursorInfo(hOut, &ConCurInf); return 0; }

Para exibir o cursor, chame a funo novamente com o membro bVisible configurado para true. uma boa idia configurar ambos os valores, especialmente na primeira vez que voc chamar a funo. Se configurarmos bVisible, mas ignorarmos dwSize, a varivel no especificada pode estar zerada ou conter um valor aleatrio (lixo) que poderia estar fora do intervalo 1-100 e a rotina falhar. De forma semelhante, se quisermos mudar o tamanho do cursor e no indicarmos que bVisible true, seu compilador pode zerar o campo no inicializado e o cursor desaparecer ao invs de mudar de tamanho. Como sempre, se voc conferir o valor devolvido da chamada, voc ver se erros aconteceram no meio do caminho. Lembre-se, o cursor serve para mostrar ao usurio onde est o ponto de insero de caractere. Ocultar o cursor no altera este fato. Se voc esconder o cursor, tenha certeza de criar outros meios para facilitar a navegao pela tela. Na prxima seo, veremos como imprimir alguns outros caracteres especiais.

28

C++ Win32 Tutorial

Trabalhando com o Console no Windows, parte 4.


At agora evitei utilizar caracteres acentuados e o c acedilhado. Esta parte do tutorial ser dedicada a outro tema muito recorrente nos fruns e que diz respeito nossa lngua ptria. Por gentileza, compile e rode o cdigo seguinte.
#include <iostream> using namespace std; int main(){ string sIn; string test = ""; cout << "Entre com caracteres especiais: " << flush; cin >> sIn; cout << "O que li foi: " << sIn << endl; cout << "Minha vez de tentar: " << teste << endl; return 0; }

Na minha mquina a sada se pareceu com isso...

... no uma viso muito agradvel, no mesmo? Por que quando leio o stream pelo buffer do console o programa imprime corretamente e quando uso a string que j estava no cdigo no consigo? O que est acontecendo? Antes de tentar apontar uma soluo para este problema, devemos entender porque obtivemos este resultado. Um conjunto normal de caracteres contm somente 256 caracteres: 32 deles so usados como caracteres de controle pelo sistema, como Line Feed, Carriage Return etc., o que nos deixa com somente 224 utilizveis. Mesmo restrito a caracteres latinos, um conjunto de 224 caracteres no suficiente para lidar com todos os idiomas do mundo. Isso sem se esquecer dos caracteres de blocos grficos que vimos na seo passada. A soluo utilizar diferentes conjuntos de caracteres para os diferentes idiomas. Cada conjunto desses recebe o nome de Code Page, ou abreviando, CP. Na lngua inglesa no existem caracteres especiais, ento os criadores do primeiro conjunto de caracteres para os IBM PCs adicionaram vrios blocos grficos. Este conjunto atualmente chamado de code Page 437. Mas ele tambm contm alguns caracteres especiais, como , , e etc., para o alemo, francs e outros idiomas europeus. O code page 865 para a Noruega e Dinamarca quase idntico ao 437, excetuando o fato de que dois blocos grficos foram substitudos por e . O code page 850, utilizado pelos sistemas DOS em portugus e outros idiomas do oeste europeu, tem menos blocos grficos e mais caracteres especiais utilizados na Europa, como , e . O CP 855 possui caracteres russos e assim por diante. A metade inferior da tabela de codificao de todos esses code pages idntica, uma vez que tm por base a tabela ASCII original. As verses em ingls do Windows usam, por padro, o code page 1252. Este CP engloba vrios dos caracteres presentes em CPs para o DOS. O problema do nosso programa que ele foi editado utilizando um code page, provavelmente o 1252, e executado em outro, o 850 (ou o 437, se seu sistema for todo em ingls). esse detalhe que gera aquela sada indesejada e que causa tanta dor de cabea aos programadores. O console utiliza, por padro, uma fonte chamada Raster (nos Windows com tecnologia NT), tambm conhecida como Bitmap Fonts (Windows 9x). Esta fonte ideal para o uso com a codificao 437 ou 850, mas est longe de ser apropriada para a 1252.

29

C++ Win32 Tutorial

Se consultarmos a tabela de caracteres usados pelo console veja o primeiro programa da seo anterior , podemos notar que os caracteres que usamos no portugus esto l. Uma soluo para o problema da codificao seria invocar os caracteres diretamente da tabela utilizada...
cout << "Solu" << (unsigned char)135 << (unsigned char)198 << "o."<< endl;

mas ela no nada elegante, alm de poluir visualmente nosso cdigo. E tambm no ataca a causa do problema: estaramos apenas fugindo do ponto principal que a diferena entre os code pages. Ora, ento basta configurarmos o code page do nosso editor para usar o CP-850! , mas quem dera a soluo fosse to simples assim. A menos que voc ainda use o EDIT.COM (isso se ele ainda existir no seu sistema), vers que esse formato de codificao no mais utilizado por programa de edio algum. At mesmo o notepad salva 7 seus documentos apenas em ANSI , Unicode, Unicode big endian ou UTF-8. Bom, ento j que no conseguimos alterar o CP do editor, nos resta mudar o do console, no mesmo? Huuum... Essa uma soluo plausvel quando se lida com idiomas diferentes do ingls no prompt de comando, mas no a ideal (no pare de ler). Vamos continuar a trabalhar com esta idia por enquanto. ! ! ! Aviso ! ! ! Para utilizar este mtodo, precisaremos alterar parte das configuraes padro do console. A mudana nas configuraes pode trazer prejuzos ao usurio ao gerar incompatibilidade com outros cdigos. No recomendo essa mudana. Trabalharei esta tcnica porque para o uso com outro conjunto de caracteres, como o cirlico (usado pelos russos), esta a maneira mais indicada de trabalhar, mas para o portugus existe outra soluo mais conveniente o mundo d voltas, sabe-se l o que precisaremos fazer no futuro. Se no desejar mudar tais configuraes, apenas acompanhe o documento para ver a soluo, ou v direto para a soluo final. Existem duas rotinas para alterar o code page do console: SetConsoleCP() e SetConsoleOutputCP(). Os prottipos dessas funes esto aqui.
BOOL SetConsoleCP( UINT wCodePageID );

e
BOOL SetConsoleOutputCP( UINT wCodePageID );

A primeira configura o code page associado com os processos de chamadas usado pelo console. O console usa seu CP de entrada para traduzir o scan code do teclado ao valor de caractere correto que precisamos armazenar. A segunda rotina altera o CP de sada, ou seja, ela traduz o valor dos caracteres codificao usada na escrita que aparece na tela. Uma desvantagem de se alterar o code page que precisamos restaurar seu valor original depois que terminamos com nosso programa, seno estaramos bagunando demais com as configuraes do console. Outros programas que precisam do console no funcionariam direito se utilizarem um CP diferente. Ento, o que precisamos fazer guardar o CP original, mudar para outro CP e, ao fim do programa, restaurar o primeiro CP. Existem outras duas funes que fazem essa captura...
UINT GetConsoleCP();

... e...
UINT GetConsoleOutputCP();

Ento temos duas funes que retornam um nmero que representa o CP atual.

7 O termo ANSI usado para indicar as code pages do Windows uma referncia histrica, mas na atualidade constitui um equvoco que persiste na comunidade Windows. A fonte deste problema vem do fato de que a CP 1252 do Windows foi originalmente baseada em um projeto da ANSI, que se tornou a norma ISO 8859-1. Entretanto, o code page 1252 e seus sucessores originalmente baseados na srie ISO 8859-x derivam do ISO por adicionarem pontos de cdigos em um intervalo reservado para os cdigos de controle do padro ISO. At hoje, no raro que a comunidade de desenvolvimento, dentro e fora do universo Windows, confunda o CP 8859-1 com o CP 1252, bem como chamar a pgina de codificao usada por esse SO de ANSI ou A.

30

C++ Win32 Tutorial

Agora que podemos manipular os code pages do console, precisamos apenas descobrir como escrever uma string com essa nova codificao. Devo relembrar que a fonte Raster inadequada para trabalhar com outros CPs. A forma mais direta utilizar uma fonte com tabela ANSI ou UNICODE e trabalhar com estes caracteres. Nas 8 propriedades do console, podemos alterar a fonte de Raster para Lucida Console, que ANSI. Ento teramos apenas de invocar alguma funo que escrevesse em ANSI no console. Ora, existe uma rotina que faz justamente o que ns precisamos: WriteConsoleA(). O prottipo desta funo est aqui.
BOOL WriteConsole( HANDLE hConsoleOutput, const VOID *lpBuffer, DWORD nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved );

O primeiro argumento o manipulador da tela do console j sabemos como consegu-lo. O segundo um ponteiro que indica onde nossa frase est armazenada existe um limite de 64K de dados que, convenhamos, mais que suficiente. O terceiro parmetro indica qual a quantidade de caracteres queremos que seja escrito. O quarto um ponteiro para uma varivel que nos dir quantos caracteres foram efetivamente escritos desta forma, caso ocorre uma falha temos como saber onde a rotina parou de escrever. O ltimo argumento, segundo a MSDN, uma varivel reservada e deve ser NULL. Desta forma, escrevi o seguinte programa...
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hIn, hOut; unsigned int uInCP, uOutCP; DWORD n; char *cIn = ""; // captura os manipuladores de entrada e sada do console hIn = GetStdHandle(STD_INPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE); // salva os code pages originais do console uInCP = GetConsoleCP(); uOutCP = GetConsoleOutputCP(); // configura o CP que desejamos SetConsoleCP(1252); SetConsoleOutputCP(1252); // escreve nossa frase WriteConsole(hOut, cIn, strlen(cIn), &n, NULL); // restaura o CP antigo SetConsoleCP(uInCP); SetConsoleOutputCP(uOutCP); return 0; }

... que na minha mquina apresentou a seguinte sada.

Basta clicar no cone que aparece no canto superior direito da janela do console.

31

C++ Win32 Tutorial

Conseguimos alcanar nosso objetivo, mas este mtodo tem suas desvantagens. A primeira coisa que podemos notar a diferena no tamanho da fonte: Lucida tem 7x12 pixels, enquanto a Raster tem 8x12. Se tnhamos um programa que assumia as dimenses padro de fonte, o mesmo no preservar sua aparncia com a nova configurao. Segundo, ns alteramos a tabela de codificao, deixamos de usar a CP-850 e adotamos a CP-1252. Modifiquei o primeiro programa da seo anterior para que mostrasse a tabela de codificao 1252 e consegui a seguinte sada na minha mquina.

Se o leitor comparar esta tabela com a que conseguimos na seo anterior, ver que a segunda metade dela muito diferente, em especial os caracteres de 179 a 220, que tnhamos usado para criar nossas grades e caixas. Este mtodo afeta seriamente a compatibilidade entre o que os programas acreditam estar escrevendo e o que o console est de fato exibindo. Nosso programa de desenhar caixas j no teria utilidade alguma com esta configurao do console precisaramos reescrever nosso cdigo para alcanar este objetivo e ainda correramos o risco de no ter todos os nossos caracteres. Ento podemos concluir que esta tcnica, para o nosso caso, no a mais inteligente a se usar. Nenhum dos dois mtodos apresentados at agora conseguiu resolver nosso problema satisfatoriamente. Talvez seja hora de pensarmos um pouco mais e atacar a fonte do mal... isso a! Se a Microsoft tivesse convertido o command prompt para Unicode nada disso aconteceria! Vamos invadir e ocupar Redmond! Exigir um console em utf-8! Abaixo opresso! (O.o) Acalme-se, Padawan, no me referia a esse tipo de ataque rebelde. Devemos nos lembrar que nosso amigo, o emulador de terminal de texto, assim porque precisa manter a compatibilidade com programas que foram escritos atravs dos anos afinal, compatibilidade tambm significa tornar as solues/problemas do passado uma constante no presente. Se voc alterou a fonte do seu console, desfaa essa modificao e tentemos outra forma de trabalhar com os caracteres especiais com o console. O que ns precisamos de uma forma de converter nossa string de caracteres que est em um code page 1252/Unicode para uma nova string de caracteres equivalentes e reconhecveis pelo CP 850 do console. Existem duas rotinas que, trabalhando em conjunto, podem realizar esta tarefa, mas ambas precisam de cuidados especiais quanto ao uso. So elas: MultiByteToWideChar() e WideCharToMultiByte(). Estas sero as funes mais complicadinhas que usaremos, prometo que as prximas sero mais simples. O prottipo da primeira est aqui.
int MultiByteToWideChar( UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,

32

C++ Win32 Tutorial

LPWSTR lpWideCharStr, int cchWideChar ); MultiByteToWideChar() mapeia um string de caracteres normais para um string de caracteres wide char, sendo

aquele string no necessariamente de um conjunto de caracteres multibyte (1252, utf-7, utf-8 etc.). O primeiro parmetro o code page a ser usado para fazer a converso qualquer valor de CP que esteja instalado ou disponvel no sistema operacional. Tambm podemos usar como primeiro argumento os identificadores CP_ACP (indica a codificao padro do Windows), CP_MACCP (codificao do sistema Macintosh) e CP_OEMCP (codifi9 cao do sistema OEM ), dentre outros mais informaes aqui. O segundo parmetro uma flag que indica o tipo de converso para converses com utf-7 e utf-8, este deve ser zero. O terceiro argumento um ponteiro para a string de caracteres a serem convertidos. O quarto parmetro deve ser o tamanho, em bytes, da string indicada no terceiro argumento. O quinto um ponteiro para onde devemos armazenar a string convertida. E o ltimo o tamanho, em caracteres, do buffer indicado no argumento anterior. Caso falhe, esta rotina devolve zero, caso contrrio, devolver o nmero de caracteres escritos na nossa string de traduo ou, se o ltimo parmetro for zero, devolver o tamanho necessrio, em caracteres, que essa mesma string dever ter. Perceba que esta rotina tem duas utilidades: converter uma string de um CP a outro escolhendo dentre vrios mtodos de converso e indicar qual o tamanho necessrio da string a receber a traduo. Devemos ter muita cautela ao lidar com esta funo j que a mesma pode comprometer o funcionamento do nosso programa. Essa rotina pode causar uma sobrecarga do buffer se mal utilizada, j que precisamos indicar o tamanho de lpMultiByteStr em bytes, enquanto que lpWideCharStr deve ser em caracteres para evitar essa sobrecarga, devemos especificar um tamanho buffer apropriado para o tipo de dados que o mesmo recebe. A rotina WideCharToMultiByte() converte uma string utf-16 (wide char) para uma nova string de caracteres. O prottipo dela est aqui.
int WideCharToMultiByte( UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar );

Assim como na outra funo, o primeiro argumento o code page usado para fazer a converso e o segundo uma flag que indica o tipo de converso. O terceiro parmetro um ponteiro para a string em Unicode que devemos converter. O quarto o tamanho em caracteres da string indicada no argumento anterior. O quinto parmetro um ponteiro para um caractere que usaremos caso este no possa ser indicado no novo code page. O sexto e ltimo parmetro um ponteiro para a flag que indica se a funo conseguiu usar os caracteres padro na converso. Esta rotina devolve o nmero de bytes escritos no buffer apontado por lpMultiByteStr se no falhar ou, caso cpMultiByte seja zero, devolve o tamanho em bytes necessrios para fazer a converso do buffer indicado por lpMultiByteStr. Agora que temos sabemos o que cada funo faz e como se comportam, podemos escrever nossa prpria rotina de converso da codificao 1252 para a CP que o console usa. Consegui chegar seguinte funo.
#include <iostream> #include <string> #include <windows.h> #include <malloc.h> using namespace std; string CPWin2CPConsole(string sIn);

Original Equipment Manufacturer um regime de comercializao de software que a Microsoft disponibiliza para as empresas. Os softwares OEM so manipulados pela prpria Microsoft ou por terceiros para conter modificaes/personalizaes. Neste caso, um code page dito OEM quando no aquele presente no sistema original, e sim uma modificao portugus, por exemplo, sendo que o Windows um produto em ingls.

33

C++ Win32 Tutorial

int main(){ string sIn = ""; cout << CPWin2CPConsole(sIn) << endl; return 0; } string CPWin2CPConsole(string sIn){ string sOut = ""; int sw, cb; size_t wCharNumber; wchar_t *wCharArray; char *temp; wCharNumber = MultiByteToWideChar(CP_ACP, 0, sIn.c_str(), sIn.size(), 0, 0); wCharArray = (wchar_t*)alloca(wCharNumber*sizeof(wchar_t)); if (sw = MultiByteToWideChar(CP_ACP, 0, sIn.c_str(), sIn.size(), wCharArray, wCharNumber)) { wCharNumber = WideCharToMultiByte(CP_OEMCP, 0, wCharArray, sw, 0, 0, 0, 0); temp = (char*)alloca(wCharNumber*sizeof(char)); if (cb = WideCharToMultiByte(CP_OEMCP, 0, wCharArray, sw, temp, wCharNumber, NULL, NULL)) { sOut.assign(temp,cb); } } return sOut; }

No acredito que o leitor tenha dificuldades com as 15 primeiras linhas de cdigo. Ento vamos partir para a anlise da rotina de converso de strings em nosso programa.
wCharNumber = MultiByteToWideChar(CP_ACP, 0, sIn.c_str(), sIn.size(), 0, 0); wCharArray = (wchar_t*)alloca(wCharNumber*sizeof(wchar_t));

Um dos valores que essa rotina devolve a quantidade de caracteres que a string traduzida deve ter, est lembrado disso? wCharNumber neste momento est sendo usada para indicar qual ser essa quantidade. Note que o primeiro parmetro CP_ACP, que o code page em que a string de entrada foi escrita. Mas acontece que WideCharToMultiByte() no recebe o tamanho do buffer como parmetro em nmero de caracteres, mas sim em nmero de bytes. Para evitar problemas de sobrecarga do buffer, precisamos fazer essa converso a que entra a funo alloca()10 e seu cabealho <malloc.h> e guardar o resultado em wCharArray. Na verdade estamos reservando o espao que um array de wide chars precisa ter.
if (sw = MultiByteToWideChar(CP_ACP, 0, sIn.c_str(), sIn.size(), wCharArray, wCharNumber)) {

Esta nica linha faz trs coisas ao mesmo tempo. Primeiro, armazena o contedo em Unicode da sIn em wCharArray foi para isso que alocamos aquele espao, estvamos preparando a varivel para receber o seu contedo. Segundo, guarda na varivel sw o nmero de caracteres Unicode que foram escritos em wCharArray. Precisaremos deste valor quando usarmos a WideCharToMultiByte(). Terceiro e ltimo, serve de controle de fluxo ao usar o contedo atribudo a sw como parmetro: se sw for zero, o teste no satisfeito lembre-se que if(0) a mesma coisa que if(false). Se sw for qualquer coisa diferente de zero, entra no escopo do if().
wCharNumber = WideCharToMultiByte(CP_OEMCP, 0, wCharArray, sw, 0, 0, 0, 0); temp = (char*)alloca(wCharNumber*sizeof(char));

A primeira linha armazena a quantidade de caracteres na nova codificao ns usaremos, reaproveitando a varivel wCharNumber no precisamos ocupar memria se podemos reciclar as variveis. A segunda prepara um array de chars temporrio para receber o contedo, alocando o espao necessrio que precisar ter.
10

Esta rotina tem funcionamento muito semelhante malloc() consulte a man page de cada uma para mais detalhes.

34

C++ Win32 Tutorial

if (cb = WideCharToMultiByte(CP_OEMCP, 0, wCharArray, sw, temp, wCharNumber, NULL, NULL)) { sOut.assign(temp,cb); }

A lgica deste if() a mesma do anterior. Eles diferenciam pelo funcionamento. Este converte o contedo Unicode do que est em wCharArray para o code page do console (CP_OEMCP), armazenando em temp. Assim como o anterior, armazena em cb a quantidade de caracteres convertidos e o utiliza como varivel de controle. A rotina assign(), pertecente <string>/<string.h>, substitui o valor de sOut com o contedo de temp, o que nos deixa com o resultado da converso pronto para ser devolvido pela funo de traduo. Acabei criando um cdigo que converte somente do CP do editor para o CP do console. Com pequenas modificaes, o leitor pode criar uma rotina de converso universal (na medida do possvel). Que tal tentar criar uma CP1ToCP2()? Este ltimo mtodo que usamos funciona muito bem quando existem caracteres equivalentes entre as code pages: no adianta forar uma codificao para japons e esperar que o console mostre os caracteres porque as rotinas iro falhar. Este ltimo modelo tambm pode ser muito bem empregado quando lidamos com manipulao de arquivos: caso a codificao dos strings do cdigo seja diferente da codificao usada no contedo de um arquivo, devemos recorrer a este tipo de converso. Parece que no, mas essa briga entre code pages fonte de muito aborrecimento entre os programadores. Enfim, espero ter dado alguma paz de esprito para aqueles que precisam fazer essas converses ao apresentar esta soluo. Na prxima parte deste tutorial, exploraremos alguns tpicos relacionados s cores.

35

C++ Win32 Tutorial

Trabalhando com o Console no Windows, parte 5.


At agora todas as sadas de console que criamos foram em preto e branco. Esta parte do tutorial mostra como eu fiz isso a embaixo!

Agora, antes que voc fique muito animado, deixe-me alert-lo de que a paleta de cores disponveis muito limitada. E, honestamente, esta parte cosmtica ao lidar com o console no o foco do nosso currculo, mas no por isso que ela seja menos importante, certo? Afinal, s vezes precisamos agradar ao usurio (o suficiente para que tenhamos bons resultados). Por fim, se voc, estudante de programao, decidiu em algum momento pesquisar sobre Como colorir as letras e o fundo no console ou algo do gnero, certamente se deparou com alguma pgina indicando o uso das funes textcolor() e textbackground() do cabealho CONIO.H, que no faz parte das bibliotecas padro do C/C++ e que, apesar de existirem uma infinidade de cdigos mostrando como usar estas funes, nenhum delas funcionou, no mesmo? Vejo-me na obrigao de contar-lhes uma historinha: h aproximadamente 20 anos, quando os PCs mais modernos tinham processadores de 100MHz e 16MB de RAM (o Windows no passava de uma interface grfica para o DOS), existia uma ferramenta de programao composta de IDE e compilador chamada Turbo-C, de uma empresa muito bem sucedida de nome Borland. Como parte do Turbo-C, existiam bibliotecas que faziam o de melhor no seu tempo. E dentre estas havia uma que se destacava quando o assunto era console: a CONIO.H (CONsole Input/Output). Essa nica biblioteca realizava praticamente tudo o que cobrimos nestes tutoriais e ainda fazia a interface entre o programa e o hardware. Como o Turbo-C era uma das principais ferramentas utilizadas e os programadores j estavam muito habituados a ela, vrios dos novos compiladores que apareceram a posteriori criaram bibliotecas semelhantes s que j existiam no compilador da Borland. To semelhantes que o gcc tem uma de mesmo nome, s que sem vrias das rotinas da CONIO.H original isso porque naquela poca, os PCs rodavam basicamente o MS-DOS e o DR-DOS, muito semelhantes em arquitetura (reduzindo os problemas de portabilidade), diferentemente do que acontece hoje, o que torna impossvel o uso destas rotinas. Ento no se decepcione se voc caiu nessa armadilha certamente uma das primeiras bibliotecas que voc veio a usar quando estava aprendendo C ou C++ foi a conio.h e acredito que todos ns fomos seduzidos por ela em algum momento. Mas agora, deixemos o passado de lado e trabalhemos com as solues do presente. Ns usaremos a funo SetConsoleTextAttribute() da WIN32 API. Essa funo recebe dois argumentos. O primeiro o manipulador padro j cobrimos essa parte. O segundo uma mascara de bit WORD de 16bit contendo zero ou mais bits. No necessrio se preocupar com os valores dos vrios bits. Existe uma srie de constantes que esto definidas na windows.h (na verdade, na wincon.h, mas esta est includa na windows.h), que simbolicamente permitiro a voc construir os valores necessrios de forma fcil. Olhemos para este programa e sua sada.
#include <iostream> #include <windows.h> using namespace std;

36

C++ Win32 Tutorial

int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, FOREGROUND_RED); cout << "Este texto e vermelho." << endl; SetConsoleTextAttribute(hOut, FOREGROUND_GREEN); cout << "Este texto e verde." << endl; SetConsoleTextAttribute(hOut, FOREGROUND_BLUE); cout << "Este texto e azul." << endl; return 0; }

Um azul e um vermelho desbotados. Sendo bits, eles podem sofrer operaes OR para fazer combinaes. O programa seguinte mostra o resultado desta operao.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN); cout << "Este texto e' amarelo." << endl; SetConsoleTextAttribute(hOut, FOREGROUND_GREEN | FOREGROUND_BLUE); cout << "Este texto e' ciano." << endl; SetConsoleTextAttribute(hOut, FOREGROUND_BLUE | FOREGROUND_RED); cout << "Este texto e' magenta." << endl; SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); cout << "Este texto e' branco" << endl; return 0; }

37

C++ Win32 Tutorial

A operao OR bit-a-bit "|" combina os bits passados como argumento. Por isso, vermelho | verde resulta em amarelo, verde | azul resulta em ciano, azul | vermelho resulta em magenta e todos os trs combinados do branco. A ordem no importante, vermelho | azul d o mesmo resultado que azul | vermelho. Outro bit que pode sofrer operaes OR FOREGROUND_INTENSITY. Todas as cores que mostrei at agora esto um pouco apagadas, mas ao adicionar o bit de intensidade, as cores parecem brilhar. Esse programa mostra o uso e os resultados do bit de intensidade.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, FOREGROUND_RED); cout << "Vermelho " << flush; SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_INTENSITY); cout << "Vermelho" << endl; SetConsoleTextAttribute(hOut, FOREGROUND_GREEN); cout << "Verde " << flush; SetConsoleTextAttribute(hOut, FOREGROUND_GREEN | FOREGROUND_INTENSITY); cout << "Verde" << endl; SetConsoleTextAttribute(hOut, FOREGROUND_BLUE); cout << "Azul " << flush; SetConsoleTextAttribute(hOut, FOREGROUND_BLUE | FOREGROUND_INTENSITY); cout << "Azul" << endl; SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN); cout << "Amarelo " << flush; SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); cout << "Amarelo" << endl; SetConsoleTextAttribute(hOut, FOREGROUND_GREEN | FOREGROUND_BLUE); cout << "Ciano " << flush; SetConsoleTextAttribute(hOut, FOREGROUND_GREEN | FOREGROUND_BLUE |FOREGROUND_INTENSITY); cout << "Ciano" << endl; SetConsoleTextAttribute(hOut, FOREGROUND_BLUE | FOREGROUND_RED); cout << "Magenta " << flush; SetConsoleTextAttribute(hOut, FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY); cout << "Magenta" << endl; SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); cout << "Branco " << flush; SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); cout << "Branco" << endl; return 0; }

38

C++ Win32 Tutorial

O preto conseguido ao se configurar nenhuma cor no primeiro plano (afinal, preto a ausncia de cores). Ento, no total ns temos 15 cores Eu avisei para no ficar muito animado... Alm de podermos mudar a cor do primeiro plano (a fonte), tambm podemos alterar as cores de fundo. As mesmas cores esto disponveis.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, BACKGROUND_RED); cout << "Vermelho " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_RED | BACKGROUND_INTENSITY); cout << "Vermelho " << endl; SetConsoleTextAttribute(hOut,BACKGROUND_GREEN); cout << "Verde " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_GREEN | BACKGROUND_INTENSITY); cout << "Verde " << endl; SetConsoleTextAttribute(hOut, BACKGROUND_BLUE); cout << "Azul " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_BLUE | BACKGROUND_INTENSITY); cout << "Azul " << endl; SetConsoleTextAttribute(hOut, BACKGROUND_RED | BACKGROUND_GREEN); cout << "Amarelo " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY); cout << "Amarelo " << endl; SetConsoleTextAttribute(hOut, BACKGROUND_GREEN | BACKGROUND_BLUE); cout << "Ciano " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_GREEN | BACKGROUND_BLUE |BACKGROUND_INTENSITY); cout << "Ciano " << endl; SetConsoleTextAttribute(hOut, BACKGROUND_BLUE | BACKGROUND_RED); cout << "Magenta " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY); cout << "Magenta " << endl; SetConsoleTextAttribute(hOut, BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); cout << "Branco " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); cout << "Branco " << endl; return 0; }

39

C++ Win32 Tutorial

Um caractere de espao ser exibido apenas como um fundo colorido. Foi assim que criei a logomarca da Universidade de Braslia no incio dessa seo a biblioteca graphics.h permitiria criar uma logomarca mais arredondada, prximo do real, mas o estudo da mesma foge do escopo deste tutorial. possvel configurar as cores da fonte e do fundo simultaneamente. Aqui, por exemplo, configurei a cor de fundo para amarelo e a fonte para ciano intenso.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, BACKGROUND_GREEN | BACKGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); cout << "Ciano intenso no fundo amarelo." << endl; return 0; }

Voc pode usar quaisquer combinaes com as cores disponveis, embora, obviamente, seja intil utilizar a fonte e o fundo com a mesma configurao de cor. Lembre-se tambm que usar nenhum bit de primeiro plano resulta em texto em preto, e nenhum bit de fundo resulta em um fundo preto. Uma pegadinha muito comum esquecer que fonte e fundo devem ser configurados a cada chamada. Se voc quiser escrever em um fundo amarelo primeiro em verde e depois em vermelho, configure o fundo em ambas chamadas, ou caso contrrio a funo poder assumir que voc quer retornar para o fundo preto.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, BACKGROUND_GREEN | BACKGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); cout << "Verde " << flush; SetConsoleTextAttribute(hOut, BACKGROUND_GREEN | BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_INTENSITY);

40

C++ Win32 Tutorial

cout << "Vermelho" << endl; return 0; }

Se voc for realizar vrias mudanas com cores, ao invs de fazer uma operao OR a todo instante, prefervel fazer tais operaes de uma vez declarando as cores como constantes globais atravs de um const int ou se preferir crie funes que faam isso, esta apenas uma sugesto. No cdigo abaixo, const int FRI representa uma fonte vermelho intenso (Foreground Red Intense), BC um fundo ciano (Background Cyan), FW uma fonte branca (Foreground White) e BNULL o fundo preto. Utilizar as operaes OR em pequenos programas de testes no faz muita diferena, mas em programas grandes com vrias mudanas de cor, o cdigo pode se tornar muito menor usando esta tcnica.
#include <iostream> #include <windows.h> using namespace std; const const const const int int int int FRI = FOREGROUND_RED | FOREGROUND_INTENSITY; FW = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; BC = BACKGROUND_GREEN | BACKGROUND_BLUE; BNULL = 0;

int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); cout << "Normal, " << flush; SetConsoleTextAttribute(hOut, FRI | BC); cout <<"colorido" << flush; SetConsoleTextAttribute(hOut, FW | BNULL); cout << " e preto." << endl; return 0; }

Finalmente, nesta seo, disponibilizo um arquivo de cabealho que contm um conjunto completo das cores. Para us-lo, copie o cdigo e cole-o em um arquivo com a extenso .h, "ShortColours.h" por exemplo. Ponha uma cpia deste arquivo no mesmo diretrio que seu cdigo e o inclua em seu programa desta forma.
#include "ShortColours.h"

Se voc prefere criar uma biblioteca particular e reunir seus cabealhos em um s lugar ao invs de criar cpias dos mesmos, proceda assim.
#include "D:/Programao/C/Headers/ShortColours.h"

41

C++ Win32 Tutorial

Quando utilizamos o par de aspas duplas, estamos instruindo o pr-processador a buscar os arquivos do caminho especificado por I (gcc) ou /I (Visual Studio C++): no primeiro caso, no mesmo diretrio em que seu fonte se encontra e, no segundo, naquele caminho especficado. Quando utilizamos os sinais de menor e maior que, estamos instruindo o pr-processador a buscar os arquivos presentes nas variveis do ambiente.
#ifndef SHORTCOLOURS_H #define SHORTCOLOURS_H const const const const const const const const const const const const const const const const const const const const const const const const const const const const const const #endif int int int int int int int int int int int int int int int int int int int int int int int int int int int int int int FW = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; FR = FOREGROUND_RED; FG = FOREGROUND_GREEN; FB = FOREGROUND_BLUE; FY = FOREGROUND_RED | FOREGROUND_GREEN; FC = FOREGROUND_GREEN | FOREGROUND_BLUE; FM = FOREGROUND_BLUE | FOREGROUND_RED; FWI = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; FRI = FOREGROUND_RED | FOREGROUND_INTENSITY; FGI = FOREGROUND_GREEN | FOREGROUND_INTENSITY; FBI = FOREGROUND_BLUE | FOREGROUND_INTENSITY; FYI = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; FCI = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; FMI = FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY; FNULL = 0; BW = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; BR = BACKGROUND_RED; BG = BACKGROUND_GREEN; BB = BACKGROUND_BLUE; BY = BACKGROUND_RED | BACKGROUND_GREEN; BC = BACKGROUND_GREEN | BACKGROUND_BLUE; BM = BACKGROUND_BLUE | BACKGROUND_RED; BWI = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY; BRI = BACKGROUND_RED | BACKGROUND_INTENSITY; BGI = BACKGROUND_GREEN | BACKGROUND_INTENSITY; BBI = BACKGROUND_BLUE | BACKGROUND_INTENSITY; BYI = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY; BCI = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY; BMI = BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY; BNULL = 0;

Originalmente tinha usado #defines ao invs de vrios const int. Me foi indicado, no entanto, algumas razes pelas quais devemos preferir a segunda forma em detrimento da primeira. Favor consultar o endereo eletrnico http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7 para esclarecimentos. Este programa produz a mesma sada que o ltimo.
#include <iostream> #include <windows.h> #include "ShortColours.h" using namespace std; int main(){ HANDLE hOut; hOut = GetStdHandle(STD_OUTPUT_HANDLE); cout << "Normal, " << flush; SetConsoleTextAttribute(hOut, FRI | BC); cout <<"colorido" << flush; SetConsoleTextAttribute(hOut, FW | BNULL); cout << " e preto." << endl;

42

C++ Win32 Tutorial

return 0; }

A paleta de cores muito limitada, mas no fim muito til. Lembre-se, precisamos apenas que a interface seja boa o suficiente para conseguir um trabalho bem feito. Na parte 2 deste tutorial, mostrei como usar a ReadConsoleOutputCharacter() para recuperar os caracteres em uma ou mais posies do buffer. Por si s, a rotina no capaz de recuperar os atributos de primeiro plano e fundo desses caracteres coloridos. Esta informao tambm est disponvel, mas atravs de outra rotina chamada ReadConsoleOutputAttribute(), que muito semelhante ReadConsoleOutputCharacter(). O prottipo da funo est aqui.
BOOL ReadConsoleOutputAttribute( HANDLE hConsoleOutput, LPWORD lpAttribute, DWORD nLength, COORD dwReadCoord, LPDWORD lpNumberOfAttrsRead );

Esse programa configura a cor do primeiro plano para FOREGROUND_GREEN | FOREGROUND_RED usando uma string como sada. Ento usa ReadConsoleOutputAttribute() para recuperar o atributo do caractere em (4,0). O resultado 6, isso porque FOREGROUND_GREEN est definido para ter o valor 0x0002 e FOREGROUND_RED 0x0004. 2+4=6. Se voc se sente muito vontade em trabalhar com os valores numricos, use-os. Particularmente, prefiro usar as constantes j definidas por tornar o cdigo mais compreensvel.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; WORD Attribute; COORD Where; unsigned long NumRead; hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, FOREGROUND_GREEN | FOREGROUND_RED); cout << "Este texto e' amarelo." << endl; Where.X = 4; Where.Y = 0; ReadConsoleOutputAttribute(hOut, &Attribute, 1, Where, &NumRead); cout << "Atributo e' " << Attribute << endl; return 0; }

A sada se parece com isso.

Na parte seguinte do tutorial, ns investigaremos os eventos do mouse e do teclado.

43

C++ Win32 Tutorial

Trabalhando com o Console no Windows, parte 6.


Esta seo do tutorial abordar alguns poucos tpicos que, embora no sejam difceis de usar, podem ser um pouco complicados de se entender conceitualmente. Vou me esforar para que voc, leitor, ao menos assimile o que tentarei transmitir quase certeza de que vou me complicar, mas no custa nada arriscar. O que usaremos agora so os eventos do console. Um evento algo que acontece ao console. Existem vrios tipos de eventos, mas neste tutorial iremos nos focar naqueles relacionados ao teclado e ao mouse. A manipulao dos eventos do console um assunto muito extenso; no tenho conhecimento suficiente e nem capacidade de cobrir tudo em um tutorial pequeno como este mas se mesmo assim, voc estiver interessado no tema, d uma lida na MSDN ou procure pela documentao do seu compilador. A funo que usaremos para conseguir os eventos do console a ReadConsoleInput(). Essa funo recebe quatro parmetros. O primeiro argumento o manipulador de entrada, que ns j vimos a respeito. O segundo parmetro um ponteiro para uma estrutura INPUT_RECORD. Esta uma estrutura razoavelmente simples que contm um valor indicando o tipo de evento que aconteceu (teclado, mouse etc.), e outra estrutura que contm os detalhes do evento. O terceiro parmetro o nmero de eventos a se ler. Os eventos no console so armazenados em um buffer o que significa que o sistema continua gravando eventos assim que eles acontecem, mas somente os entrega ao seu programa quando voc pergunta por um. Eles so entregues na ordem em que aconteceram. Frequentemente voc perguntar por um evento de cada vez, ou seja, pega um evento, processa e pega o prximo. Voc pode, se desejar, usar um terceiro parmetro para ler vrios eventos em um array de estruturas INPUT_RECORD. Se voc perguntar por mais eventos do que existe no buffer, a rotina devolver aqueles que esto disponveis e o quarto parmetro conter o nmero verdadeiro de eventos recuperados, ou seja, Ele no ir esperar at que exista o nmero pedido. Algo muito importante a se destacar que, se no existem eventos no buffer, a chamada a ReadConsoleInput() no devolver valor algum! uma chamada bloqueadora (blocking call), ir aguardar at que exista um. Em um programa mais adiante, mostrarei como evitar esse bloqueio at que o evento ocorra. Considere este ptograma. interessante que voc o compile e execute.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hIn; HANDLE hOut; COORD KeyWhere; COORD MouseWhere; COORD EndWhere; bool Continue = true; int KeyEvents = 0; int MouseEvents = 0; INPUT_RECORD InRec; DWORD NumRead; hIn = GetStdHandle(STD_INPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE); cout << "Pressione 'x' para encerrar o programa." << endl; cout << "Eventos de Teclas : " << endl; cout << "Eventos do Mouse : " << flush; KeyWhere.X = KeyWhere.Y = MouseWhere.X MouseWhere.Y EndWhere.X = EndWhere.Y = 22; 1; = 22; = 2; 0; 4;

while(Continue){

44

C++ Win32 Tutorial

ReadConsoleInput(hIn, &InRec, 1, &NumRead); switch (InRec.EventType) { case (KEY_EVENT): ++KeyEvents; SetConsoleCursorPosition(hOut, KeyWhere); cout << KeyEvents << flush; if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x') { SetConsoleCursorPosition(hOut, EndWhere); cout << "Saindo..." << endl; Continue = false; } break; case (MOUSE_EVENT): ++MouseEvents; SetConsoleCursorPosition(hOut, MouseWhere); cout << MouseEvents << flush; break; } } return 0; }

A sada do programa, depois de vrias teclas pressionadas e aes com o mouse e, por fim, pressionando-se x est mostrada aqui.

A primeira coisa a se destacar o lao...


While (Continue) { ReadConsoleInput(hIn, &InRec, 1, &NumRead); ... }

... voc ver que, embora o lao seja construdo para ficar se repetindo continuamente, o programa no est de fato fazendo isso. a este comportamento que me refiro quando digo que ReadConsoleInput() uma blocking call. Ela apenas devolve algo quando h algo a devolver. Consulte o gerenciador de tarefas, por exemplo, e voc ver que o programa no est usando tempo de CPU algum, quando, normalmente, um lao em execuo contnua estaria. Agora, vamos dar uma olhada no switch...
switch (InRec.EventType) { case (KEY_EVENT): ... break; case (MOUSE_EVENT): ... break; }

45

C++ Win32 Tutorial

... a structure INPUT_RECORD tem um membro chamado EventType, que diz qual tipo de evento est sendo reportado. O Windows define constantes para os valores devolvidos e, neste caso, ns conferimos se o evento flagrado uma KEY_EVENT um evento do teclado ou MOUSE_EVENT um evento do mouse. Em ambos os casos, incrementamos um contador. No caso dos eventos do teclado, h esse pedao adicional de cdigo...
if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x') { SetConsoleCursorPosition(hOut, EndWhere); cout << "Saindo..." << endl; Continue = false; }

Lembra que eu disse que os eventos so gravados e precisam ser consultados? para isso que existe a structure INPUT_RECORD. O contedo dessa estrutura varia dependendo do tipo de evento. No seria sensato, por exemplo, incluir eventos da roda do mouse (mouse wheel) em uma estrutura para eventos do teclado... A estrutura INPUT_RECORD declarada, de fato, como uma unio de diferentes estruturas EVENT_RECORD.
if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x') InRec.Event diz que ns queremos consultar os detalhes do que aconteceu, ao invs de informaes generalizadas sobre um evento.

if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x')

Ns j sabemos que isso um evento do teclado, este cdigo existe dentro do caso KEY_EVENT. Ento ns podemos selecionar o membro KeyEvent da unio EVENT_RECORD.
if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x') uChar um membro da estrutura KEY_EVENT_RECORD e , novamente, uma unio. Por que uma unio? Porque

alguns sistemas trabalham com caracteres 8bit ANSI e outros com caracteres 16bit Unicode, ento precisamos especificar o que ns queremos.
if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x')

Isso diz que eu quero o cdigo ASCII da tecla que foi pressionada. O enunciado if() serve para perguntar se o caractere x foi pressionado. Caso afirmativo, prepara as condies de sada do lao e encerra o programa. Vale a pena brincar um pouco com este programa. Perceba, por exemplo, que pressionar uma tecla gera dois 11 eventos (no, no ziquizira): um para quando a tecla pressionada (key down) e outro para quando ela solta (key up). Pressionar a tecla por mais tempo pode, em sistemas assim configurados, acionar a funo auto repeat, onde cada repetio identificada como um evento key down distinto. Similar s teclas, o movimento do boto do mouse de descer e subir so eventos separados. E percorrer o mouse pela tela gera um monte de eventos que param se o ponteiro estiver fora da rea do console. No ultimo programa, o lao s foi executado quando um evento aconteceu. No programa seguinte faremos o contrrio: o loop se repetir continuamente at que um evento ocorra para ento process-lo. Ele aborda alguns dos outros problemas encontrados do cdigo anterior tambm. Aqui est o programa.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hIn; HANDLE hOut; COORD KeyWhere;
Utilizarei os termos em ingls, key down e key up, por no conseguir equivalente em portugus de boa sonoridade. Aperta tecla e solta tecla? Que coisa horrvel... os termos em ingls so referentes ao movimento da tecla, e no ao do usurio como no portugus...
11

46

C++ Win32 Tutorial

COORD LoopWhere; COORD EndWhere; bool Continue = true; DWORD EventCount; int LoopCount = 0; int KeyEvents = 0; INPUT_RECORD InRec; DWORD NumRead; unsigned char HoldKey; hIn = GetStdHandle(STD_INPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE); cout << "Pressione 'Alt-x' para encerrar o programa." << endl; cout << "Evento de Teclas: " << flush; KeyWhere.X = 20; KeyWhere.Y = 1; LoopWhere.X = 0; LoopWhere.Y = 2; EndWhere.X = 0; EndWhere.Y = 3; while (Continue) { SetConsoleCursorPosition(hOut, LoopWhere); cout << LoopCount++ << " " << flush; Sleep(10); // Para desacelerar um pouco o programa!!! GetNumberOfConsoleInputEvents(hIn, &EventCount); while (EventCount > 0) { ReadConsoleInput(hIn, &InRec, 1, &NumRead); if (InRec.EventType == KEY_EVENT) { if (InRec.Event.KeyEvent.bKeyDown) { HoldKey = InRec.Event.KeyEvent.uChar.AsciiChar; } else { ++KeyEvents; SetConsoleCursorPosition(hOut, KeyWhere); cout << KeyEvents << flush; LoopCount = 0; if (HoldKey == 'x') { if (InRec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) { SetConsoleCursorPosition(hOut, EndWhere); cout << "Saindo..." << endl; Continue = false; } } } } GetNumberOfConsoleInputEvents(hIn, &EventCount); } } return 0; }

A sada do programa se parece com isso.

47

C++ Win32 Tutorial

Novamente, o lao do programa roda at que a varivel Continue se torne falsa. O programa exibe o contador do loop que reiniciado sempre que uma tecla pressionada.
Sleep(10); // Para desacelerar um pouco o programa!!!

Adicionei esta linha porque o lao estava sendo executado muito rpido e no conseguia ver os resultados na mquina em que estou trabalhando um Pentium Centrino T6600. Remova ou altere o valor de Sleep() essa chamada causa a suspenso da execuo do programa por um nmero especfico de milisegundos.
GetNumberOfConsoleInputEvents(hIn, &EventCount);

Esta funo olha adiante para ver quantos eventos esto na fila de entrada do programa, ou seja, o nmero devolvido por EventCount.
while (EventCount > 0) { ReadConsoleInput(hIn, &InRec, 1, &NumRead); ... GetNumberOfConsoleInputEvents(hIn, &EventCount); }

O lao interno recupera e processa os eventos que nos interessam caso existam.
if (InRec.EventType == KEY_EVENT) { ... }

Este bloco if() est dizendo que s estamos interessados nos eventos do teclado, como no caso de KEY_EVENT no ltimo programa.
if (InRec.Event.KeyEvent.bKeyDown) { HoldKey = InRec.Event.KeyEvent.uChar.AsciiChar; } else { ... }

Esse if() precisa ser melhor explicado. O membro bKeyDown da KEY_EVENT_RECORD um campo booleano e true se o evento reportar que uma tecla est sendo pressionada (lembre-se que o auto repeat como uma srie de eventos key down), e false se for um evento key up, (reportado uma vez). Fazendo essa conferncia para ver se isso false ns conseguimos um nico evento para uma pressionada de tecla mesmo se o usurio a segurar por um tempo o que costuma ser muito til. isto o que queria fazer neste programa. Ento por que preciso manter uma cpia da tecla pressionada no evento de key down? Explicarei isto em breve.

++KeyEvents; SetConsoleCursorPosition(hOut, KeyWhere); cout << KeyEvents << flush;

48

C++ Win32 Tutorial

Se o evento um key up, ns incrementamos o contador de eventos e zeramos o contador do lao. Macete old school que aprendi enquanto brincava de recriar jogos ASCII.
if (HoldKey == 'x') { if (InRec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) { SetConsoleCursorPosition(hOut, EndWhere); cout << "Saindo..." << endl; Continue = false; } }

O prximo teste confere se a cpia que mantivemos do evento key down foi um caractere x. Em seguida, fazemos um teste adicional, conferimos o estado do membro dwControlKeyState da structure KEY_EVENT_RECORD. Neste caso, conferimos se a tecla Alt esquerda tambm est em posio baixa (down), ou seja, se o usurio pressionou 'Alt-x' a condio de sada do programa. No modo padro do console, muitas das teclas Alt e Ctrl no so passadas ao programa, mas sim manipuladas pelo Windows o resultado disso que quando o usurio pressiona 'Alt-x', o evento key down corretamente reporta a tecla pressionada (da podemos manter uma cpia da mesma), mas quando o evento key up ocorre, pelo fato de o Windows lidar com a combinao Ctrl + tecla, a identidade da tecla no reportada! Portanto, se quisermos reagir a 'Alt -x', precisamos pular por esta parte. Existem vrias outras teclas de controle que voc pode testar, procure na documentao do seu compilador por inspirao! Este programa, portanto, est em um loop continuo, reagindo s teclas de qualquer maneira e sai quando Alt-x pressionado. Um esqueleto de cdigo muito til. A base de muitos jogos no modo console. Por que eu mostrei esta trapaa, esta forma de lidar com as teclas? H, na verdade, vrias razes. Primeiro, a partir do ponto de vista da criao do cdigo, usando uma combinao complexa de teclas, como Alt-x, reduzimos as chances de algum acidentalmente encerrar o programa ao pressionar a tecla errada (esse tipo de falha humana muito comum, no mesmo?). E tambm porque podemos manter todas as teclas comuns disponveis para uso no aplicativo. Segundo, eu quis demonstrar a tcnica ela uma trapaa de certa forma, mas o cdigo est a caso queira usar. Importante destacar que ela no funcionar com todas as seqncias de controle: Ctrl-c, por exemplo. Terceiro, enquanto escrevi este tutorial, me senti quase na obrigao de comentar sobre o conceito de modo console que, francamente, est fora do escopo deste documento. Existe uma rotina da API, SetConsoleMode(), que permite a escolha de vrias opes de entrada/sada. Neste tutorial assumi o uso do modo padro. O truque/trapaa que eu usei no cdigo acima funciona bem com o modo ENABLE_PROCESSED_INPUT, onde muitos dos caracteres/seqncias especiais que so normalmente interceptados/manipuladas e no reportadas/escondidas pelo OS podem ser pegas e tratadas. Se voc quiser brincar com muitas dessas caractersticas do console, de repente pode valer a pena reiniciar o modo do console para remover esta flag. Entretanto, ao se fazer isso, voc tambm remover o processamento natural das coisas como o uso do tab e o retorno docursor, precisando process-los voc mesmo (por esta razo, escolhi no usar esta tcnica neste tutorial. J tive experincias desagradveis no passado que me renderam uma formatao na mquina por no conseguir desfazer essas alteraes, ento deixemos este aborrecimento de lado). J mostrei que capturar eventos do mouse to fcil quanto capturar os do teclado. Esse programa demonstra uma forma de entrada (input) bsica do mouse. Ele idntico ao ultimo programa: um lao roda continuamente e reporta eventos do mouse quando estes acontecem. Todos os eventos bsicos do mouse esto demonstrados. Se precisar de mais eventos (um quarto ou quinto boto, rolagem de roda horizontal etc.) consulte a documentao do seu compilador ou a MSDN.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hIn;

49

C++ Win32 Tutorial

HANDLE hOut; COORD MouseWhere = {19, 1}; COORD DClickWhere = {19, 2}; COORD LeftWhere = {19, 3}; COORD RightWhere = {19, 4}; COORD WheelWhere = {19, 5}; COORD LoopWhere = {0, 6}; COORD EndWhere = {0, 7}; bool Continue = true; DWORD EventCount; int LoopCount = 0; INPUT_RECORD InRec; DWORD NumRead; hIn = GetStdHandle(STD_INPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE); cout cout cout cout cout cout << << << << << << "Pressione 'x' para encerrar o programa." << endl; "Ponteiro esta' em : " << endl; "Clique duplo em : " << endl; "Botao esquerdo : Up" << endl; "Botao direito : Up" << endl; "Rolagem da roda : " << flush;

while (Continue) { SetConsoleCursorPosition(hOut, LoopWhere); cout << LoopCount++ << " " << flush; Sleep(10); // Para desacelerar o programa!!! GetNumberOfConsoleInputEvents(hIn, &EventCount); While (EventCount > 0) { ReadConsoleInput(hIn, &InRec, 1, &NumRead); if (InRec.EventType == KEY_EVENT) { if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x') { SetConsoleCursorPosition(hOut, EndWhere); cout << "Saindo..." << endl; Continue = false; } } else if (InRec.EventType == MOUSE_EVENT) { if (InRec.Event.MouseEvent.dwEventFlags == 0) { SetConsoleCursorPosition(hOut, LeftWhere); if (InRec.Event.MouseEvent.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) { cout << "Down" << flush; } else { cout << "Up " << flush; } SetConsoleCursorPosition(hOut, RightWhere); if (InRec.Event.MouseEvent.dwButtonState & RIGHTMOST_BUTTON_PRESSED) { cout << "Down" << flush; } else { cout << "Up " << flush; } } else if (InRec.Event.MouseEvent.dwEventFlags == MOUSE_MOVED) { SetConsoleCursorPosition(hOut, MouseWhere); cout << InRec.Event.MouseEvent.dwMousePosition.X << "," << InRec.Event.MouseEvent.dwMousePosition.Y << " " << flush; } else if (InRec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK) { SetConsoleCursorPosition(hOut, DClickWhere); cout << InRec.Event.MouseEvent.dwMousePosition.X << "," << InRec.Event.MouseEvent.dwMousePosition.Y << " " << flush; } else if (InRec.Event.MouseEvent.dwEventFlags == MOUSE_WHEELED) { SetConsoleCursorPosition(hOut, WheelWhere); if (InRec.Event.MouseEvent.dwButtonState & 0xFF000000) { cout << "Down" << flush;

50

C++ Win32 Tutorial

} else { cout << "Up }

" << flush;

} } GetNumberOfConsoleInputEvents(hIn, &EventCount); } } return 0; }

A sada se parece com isso.

A maior parte da lgica do programa a mesma utilizada no anterior, ento serei breve. H um lao verificando se h quaisquer eventos no console, caso afirmativo, processamos os mesmos. A resposta a um nico evento do teclado est engatilhada (o x foi pressionado?). Todos os eventos do mouse esto descritas abaixo.
else if (InRec.EventType == MOUSE_EVENT) { if (InRec.Event.MouseEvent.dwEventFlags == 0) { ... } else if (InRec.Event.MouseEvent.dwEventFlags == MOUSE_MOVED) { ... } else if (InRec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK) { ... } else if (InRec.Event.MouseEvent.dwEventFlags == MOUSE_WHEELED) { ... } }

O bloco if() identifica os eventos do mouse disponveis. Estranhamente, existem constantes definidas para movimento, clique duplo e movimento da roda, mas no para eventos dos botes, que so representados por dois membros dwEventFlags da MOUSE_EVENT_RECORD configuradas para zero. O coutedo de cada bloco esto descritos abaixo e, nessa altura do campeonato, a maior parte deles j so bvios!
if (InRec.Event.MouseEvent.dwEventFlags == 0) { SetConsoleCursorPosition(hOut, LeftWhere); if (InRec.Event.MouseEvent.dwButtonState & 0x01) { cout << "Down" << flush; } else { cout << "Up " << flush; } SetConsoleCursorPosition(hOut, RightWhere); if (InRec.Event.MouseEvent.dwButtonState & 0x02) { cout << "Down" << flush; } else { cout << "Up " << flush; } }

51

C++ Win32 Tutorial

Se dwEventFlags zero, um evento de boto do mouse aconteceu. A maioria dos mouses modernos tem pelo menos trs botes, mas no passado todos tinham somente dois. Mantenha isso em mente enquanto explico o modo aparentemente ridculo de conferir que boto foi pressionado ou solto! Primeiro, perceba que, assim como as teclas, um boto do mouse indo para cima e para baixo so reportados como eventos diferentes. Tradicionalmente, o evento boto up que considerado significante e eu te aconselharia a projetar seus programas com isso em mente. Simplesmente, se um usurio colocar o ponteiro do mouse onde ele quer clicar, pressionar o boto e ento mudar de idia (quem nunca fez isso?), ele pode mover o ponteiro para longe de onde estava e soltar o boto. O bit 0 (0x01) no membro dwButtonState de MOUSE_EVENT_RECORD representa o boto esquerdo no mouse se fizermos um teste mandando imprimir a constante FROM_LEFT_1ST_BUTTON_PRESSED, veremos que seu valor 0x01. Fazemos um AND bit a bit para ver se o bit est ativo: se est, imprimimos down, caso contrrio, imprimimos up. O bit 1 (0x02) representa o boto direito e ns fazemos o mesmo que foi feito antes para verificar seu estado novamente, RIGHTMOST_BUTTON_PRESSED uma constante de valor 0x02. Se tivermos um mouse com mais botes, a constante FROM_LEFT_2ND_BUTTON_PRESSED o valor 0x04, FROM_LEFT_3RD_BUTTON_PRESSED 0x08 e FROM LEFT_4TH_BUTTON_PRESSED 0x10. Confesso que fiquei muito tentado em usar os valores numricos no exemplo anterior se o fizesse, estaria quebrando minha prpria regra de usar sempre as constantes! O fato que a Microsoft nunca foi muito boa quando o assunto nomenclatura (tanto de seus produtos quanto de suas constantes). Ento no se surprenda se um dia voc encontrar que ESSA_CONSTANTE_GIGANTE_REPRESENTA_ISSO seja apenas um bit, como o caso no exemplo acima. Esses nomes enormes atrapalham no layout do cdigo fonte fazendo com que uma linha fique muito longa, ultrapassando a margem direita (100 caracteres no meu caso) e precisando ser quebrada em mais linhas. Em contrapartida, facilitam a vida do programador quando ele no precisa passar por situaes por que, cargas dgua, tem um 0x01 aqui?
else if (InRec.Event.MouseEvent.dwEventFlags == MOUSE_MOVED) { SetConsoleCursorPosition(hOut, MouseWhere); cout << InRec.Event.MouseEvent.dwMousePosition.X << "," << InRec.Event.MouseEvent.dwMousePosition.Y << " " << flush; }

A cada evento, a posio do ponteiro do mouse reportada em uma estrutura COORD. Lembre-se, console so telas baseadas em clulas de caracteres. Por isso, mover o mouse em uma rea muito pequena gerar um evento MOUSE_MOVED, sendo que o resultado sempre ser as coordenadas da clula. Assim, mover o ponteiro apenas um pixel ou dois criar eventos, mas no mudar o valor devolvido para a posio do mouse.
else if (InRec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK) { SetConsoleCursorPosition(hOut, DClickWhere); cout << InRec.Event.MouseEvent.dwMousePosition.X << "," << InRec.Event.MouseEvent.dwMousePosition.Y << " " << flush; }

O evento DOUBLE_CLICK reportado no "button up" do segundo clique. Dois cliques juntos so considerados um duplo clique dependendo das configuraes do mouse no Painel de Controle..
else if(InRec.Event.MouseEvent.dwEventFlags == MOUSE_WHEELED) { SetConsoleCursorPosition(hOut, WheelWhere); if (InRec.Event.MouseEvent.dwButtonState & 0xFF000000) { cout << "Down" << flush; } else { cout << "Up " << flush; } }

52

C++ Win32 Tutorial

As rodinhas do mouse so uma inovao relativamente nova. Verses anteriores do Windows (pr-Windows 2000) nem oferecem suporte a esta funcionalidade. Se voc tem um mouse com roda e quer us-lo, voc pode embora eu no tenha encontrado documentao na MSDN sobre os eventos que dizem se a roda foi rolada para cima ou para baixo. A soluo que encontrei satisfatria, mas como no a vi em documentao nenhuma, no posso garantir que ela sempre funcione. Eu no demostrei isso, mas possvel usar os mesmos eventos das teclas Ctrl com os eventos do mouse assim como usamos com os eventos do teclado. Na prxima parte deste tutorial, investigaremos sobre as dimenses do console e algumas outras funes geralmente teis.

53

C++ Win32 Tutorial

Trabalhando com o Console no Windows, parte 7.


Nesta parte do tutorial, quero trabalhar as dimenses do console. Para fazer isso, existem dois itens que precisam ser discutidos. O primeiro a janela atual que contm o console e o segundo a grade de clulas de caracteres chamada de buffer de tela do console, que descrevi na parte 1 deste tutorial. Para demonstrar a co-relao entre os dois, abra um console qualquer um dos programas no tutorial servir para esta tarefa. Coloque o ponteiro do mouse no canto inferior direiro e tente redimensionar a janela. Perceba que possvel tornar a janela menor do que era originalmente e, note tambm que, a janela no redimensionada suavemente ao contrrio, ela o faz aos trancos. Perceba que se no existirem barras de rolagem, elas aparecero quando necessrio. Se voc tentar tornar a janela muito larga, assim que a barra de rolagem desaparecer, o console trava sua dimenso horizontal. Dependendo das configuraes do Windows que esteja usando, voc provavelmente conseguir alongar verticalmente a janela at certo ponto. A razo para esses comportamentos que observamos est na forte ligao entre os tamanhos da janela e o buffer da tela. A janela exibe uma grade de clulas de caracteres. A razo para a janela redimensionar aos trancos porque ela no mostra partes de clulas eventos de redimensionamento menores que uma clula (comprimento ou altura) so ignorados. possvel que a janela mostre menos que o tamanho todo do buffer da tela na verdade, este o caso normal por isso podemos reduzir a janela. Mas impossvel que ela exiba uma rea maior que o buffer de tela, portanto, no conseguimos estic-la indefinidamente. Claramente, o tamanho do buffer da tela importante, mas qual o tamanho dele? Quo grande ele ? O Windows fornece rotinas da API para obter esta informao. Este pequeno programa mostra como obter esta informao.
#include <iostream> #include <windows.h> using namespace std; int main() { HANDLE hOut; CONSOLE_SCREEN_BUFFER_INFO SBInfo; hOut = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(hOut, &SBInfo); cout << "Tamanho do buffer da tela : "; cout << SBInfo.dwSize.X << " x "; cout << SBInfo.dwSize.Y << endl; return 0; }

A sada no meu sistema se parece com isso.

A funo GetConsoleScreenBufferInfo() recebe o manipulador do buffer de sada e uma ponteiro para uma estrutura CONSOLE_SCREEN_BUFFER_INFO. O membro dwSize da estrutura uma COORD que preenchida com as dimenses 'X' e 'Y' do buffer da tela. No meu sistema, ele mostra 80 x 300, ou seja, existem 80 clulas de caracteres na direo 'X' horizontalmente e 300 na direo 'Y' verticalmente , o que diz que tenho disponveis 300 linhas, cada uma com 80 caracteres de comprimento. O padro do seu sistema pode no ser o mesmo, e talvez voc precise alterar alguns dos valores no programas seguintes caso o tamanho do buffer seja outro.

54

C++ Win32 Tutorial

Apenas um lembrete: o buffer da tela tem origem no zero, ou seja, a clula superior esquesda [0,0] logo, o buffer da minha tela comea em [0,0] e termina em [79,299] . Vamos dar uma olhada rpida nos outros membros da SBInfo. dwCursorPosition outra COORD, e devolve as coordenadas da clula que atualmente contm o cursor. wAttributes um buffer de 16bit que devolve os atributos de cores atuais do primeiro plano (fonte) e do segundo plano (fundo), j discutidos na parte 4 deste tutorial. Os outros dois membros so srWindow e dwMaximumWindowSize, que ns usaremos posteriormente, ento eu no os explicarei aqui. A seguir, ns faremos de forma computacional o exerccio que voc fez antes mover o canto inferior direito da janela. Aqui est o programa.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; CONSOLE_SCREEN_BUFFER_INFO SBInfo; SMALL_RECT DisplayArea = {0, 0, 0, 0}; int x; int y; hOut = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(hOut, &SBInfo); x = SBInfo.srWindow.Right; y = SBInfo.srWindow.Bottom; DisplayArea.Bottom = y; While (x > (SBInfo.srWindow.Right / 2)) { --x; DisplayArea.Right = x; SetConsoleWindowInfo(hOut, true, &DisplayArea); Sleep(50); } While (y > (SBInfo.srWindow.Bottom / 2)) { --y; DisplayArea.Bottom = y; SetConsoleWindowInfo(hOut, true, &DisplayArea); Sleep(50); } While (x != SBInfo.srWindow.Right) { ++x; DisplayArea.Right = x; SetConsoleWindowInfo(hOut, true, &DisplayArea); Sleep(50); } While (y != SBInfo.srWindow.Bottom) { ++y; DisplayArea.Bottom = y; SetConsoleWindowInfo(hOut, true, &DisplayArea); Sleep(50); } return 0; }

Desta vez no exibirei a sada, como na maioria dos exemplos anteriores, porque uma figura no seria capaz de mostrar o que o programa faz. Por gentileza, compile, rode e veja por voc mesmo o funcionamento do aplicati-

55

C++ Win32 Tutorial

vo. O que voc deveria ver a janela do console comear com certas dimenses, encolher horizontalmente e ento verticalmente para metade do tamanho que ela tinha e, por fim, se expandir para o tamanho original, primeiro horizontalmente e depois verticalmente. Vamos dar uma olhada no cdigo-fonte.
x = SBInfo.srWindow.Right; y = SBInfo.srWindow.Bottom;

A primeira coisa que fazemos chamar GetConsoleScreenBufferInfo(), como antes. Desta vez, ns usamos o membro srWindow, que uma estrutura SMALL_RECT contendo quatro short integers chamados Top, Left, Bottom e Right. Neste caso, a funo devolve um SMALL_RECT contend a regio rectangular do buffer da tela que est sendo atualmente exibida dentro da janela no meu sistema, a area inicialmente [0,0] a [24,79], ou seja, 25 linhas cada uma com largura de 80 caracteres. Armazeno os membros Bottom e Right da estrutura para uso posterior.
DisplayArea.Bottom = y; While (x > (SBInfo.srWindow.Right / 2)) { --x; DisplayArea.Right = x; SetConsoleWindowInfo(hOut, true, &DisplayArea); Sleep(50); }

No incio do programa, declarei outra estrutura SMALL_RECT chamada DisplayArea. Fiz isso porque a rotina que usaremos para mudar o tamanho da janela, na verdade, no quer saber quo grande a janela deveria ser!Ao invs disso, ela quer saber que parte do buffer da tela deveria ser mostrada. Existem dois modos de se fazer isso: absoluta ou relativa. Neste programa usamos as especificaes absolutas, desta forma, usamos uma estrutura SMALL_RECT para indicar o canto superior esquerdo e o canto inferior direito da rea do buffer da tela que queremos exibir. A janela ento se ajusta para mostrar aquela regio. Todos os membros da estrutura DisplayArea foram inicializados para zero, por isso Top e Left j esto corretos, mas preciso configurar manualmente o membro Bottom para ser igual altura original da janela. Atravs de um lao, decrementando x, atualizo a estrutura DisplayArea e chamo SetConsoleWindowInfo(). O primeiro parmetro o manipulador de sada. O prximo um parmetro booleano neste caso, eu o configure para true indicando que a estrutura DisplayArea contm coordenadas absolutas dentro do buffer da tela, como descrito acima. Finalmente, eu passo um ponteiro para a estrutura DisplayArea e a funo reduz o comprimento em uma clula de caractere. O lao continua at que o comprimento esteja pela metade do original. A presena do Sleep() no lao apenas reduz a velocidade do programa para podermos ver o que est acontecendo. Os outros trs laos so, basicamente, como este aqui, e reduzem a altura pela metade e, ento expande a janela para seu tamanho original. Quando usar SetConsoleWindowInfo(), lembre que voc no pode perguntar janela para exibir partes que no existem do buffer da tela! Se voc pedir para exibir valores negatives no modo absolute ou maiores do que o comprimento ou a altura do buffer, a chamada ir falhar e devolver false. Analisando o cdigo, perceptvel que eu no verifiquei o estado das rotinas da API nestes exemplos coisa que voc deve sempre fazer, mesmo que voc no saiba como corrigir as falhas, til saber que rotina falhou e, se possvel, o porqu. No prximo programa, usaremos ambas especificaes absolutas e relativas para criar um programa que rola o texto para cima, baixo, esquerda, direita e at mesmo diagonalmente. Aqui est o fonte.
#include <iostream> #include <windows.h> #include <stdlib.h> #include <time.h> using namespace std; int main(){ HANDLE hOut; CONSOLE_SCREEN_BUFFER_INFO SBInfo;

56

C++ Win32 Tutorial

SMALL_RECT DisplayArea = SMALL_RECT ScrollRight = SMALL_RECT ScrollDown = SMALL_RECT ScrollLeft = SMALL_RECT ScrollUp = int Lines; int Characters; int Random; int i;

{5, 5, 0, 0}; {-1, 0, -1, 0}; {0, -1, 0, -1}; {1, 0, 1, 0}; {0, 1, 0, 1};

hOut = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(hOut, &SBInfo); DisplayArea.Bottom = SBInfo.srWindow.Bottom - 5; DisplayArea.Right = SBInfo.srWindow.Right - 5; SetConsoleWindowInfo(hOut, true, &DisplayArea); srand((unsigned)time(NULL)); for (Lines = 0; Lines < (SBInfo.srWindow.Bottom - 1); Lines++) { for (Characters = 0; Characters < 80; Characters++) { Random = (rand() % 32); If (Random > 25) { cout << " " << flush; } else { cout << (char)('a' + Random) << flush; } } } Sleep(250); for (i=0; i<5; i++) { SetConsoleWindowInfo(hOut, false, &ScrollRight); Sleep(100); } Sleep(250); for (i=0; i<5; i++) { SetConsoleWindowInfo(hOut, false, &ScrollDown); Sleep(100); } Sleep(250); for (i=0; i<5; i++) { SetConsoleWindowInfo(hOut, false, &ScrollLeft); Sleep(100); } Sleep(250); for (i=0; i<5; i++) { SetConsoleWindowInfo(hOut, false, &ScrollUp); Sleep(100); } Sleep(250); for (i=0; i<5; i++) { SetConsoleWindowInfo(hOut, false, &ScrollRight); SetConsoleWindowInfo(hOut, false, &ScrollDown); Sleep(100); } Sleep(250); DisplayArea.Top = 0; DisplayArea.Left = 0;

57

C++ Win32 Tutorial

DisplayArea.Bottom = SBInfo.srWindow.Bottom; DisplayArea.Right = SBInfo.srWindow.Right; SetConsoleWindowInfo(hOut, true, &DisplayArea); return 0; }

Novamente no mostrarei a sada: rolagem de texto no perceptvel em um bitmap. O que voc deve ver uma tela com um falso texto rolando para a direita, para baixo, para a esquerda, para cima e, finalmente, diagonalmente para baixo e para a direita. Olhemos o cdigo.
GetConsoleScreenBufferInfo(hOut, &SBInfo); DisplayArea.Bottom = SBInfo.srWindow.Bottom - 5; DisplayArea.Right = SBInfo.srWindow.Right - 5; SetConsoleWindowInfo(hOut, true, &DisplayArea);

Como no cdigo anterior, peguei as informaes sobre o tamanho da janela, configurei os membros Bottom e Right da estrutura DisplayArea Top e Left foram configuradas na inicializao e ento usei as especificaes absolutas para reduzir o tamanho da janela em 5 clulas em todas as direes.
srand((unsigned)time(NULL)); for (Lines = 0; Lines < (SBInfo.srWindow.Bottom - 1); Lines++) { for (Characters = 0; Characters < 80; Characters++) { Random = (rand() % 32); if (Random > 25) { cout << " " << flush; } else { cout << (char)('a' + Random) << flush; } } }

Tudo que isso faz preencher a janela do console com texto aleatrio. Perceba os #includes extras no topo do programa, necessrios para a gerao dos nmeros aleatrios.
for (i=0; i<5; i++) { SetConsoleWindowInfo(hOut, false, &ScrollRight); Sleep(100); } Sleep(250);

Este lao usa o modo relative para deslocar a area do buffer da tela, sem mudar o tamanho da janela. Perceba que o segundo parmetro agora false, o que diz funo que a estrutura SMALL_RECT do terceiro parmetro possui valores relativos. A estrutura ScrollRight inicializada no topo do programa tem os membros Left e Right configurados para -1. Quando chamamos SetConsoleWindowInfo() no modo relative com essa estrutura, ela subtrai 1 de quaisquer que sejam os valores de Left e Right e exibe a area correspondente do buffer da tela, rolando efetivamente o buffer um caractere para a direita numa janela fixa poderamos dizer, tambm, que estamos rolando a janela um caractere para a esquerda num buffer de tela fixo, o que a mesma coisa. Usei -1 nesta estrutura, mas isso certamente no essencial. Se eu tivesse dito -2, por exemplo, ento o buffer da tela apareceria rolando dois caracteres a cada iterao do lao. Os prximos trs laos fazem a mesma coisa usando diferentes estruturas para resultar em diferentes direes. O ultimo lao chama a rotina duas vezes uma para mover para a direita e outra para baixo , o que resulta em uma rolagem na diagonal. Certamente seria possvel usar outra verso da estrutura SMALL_RECT com valores apropriados, ou seja, ccriar uma estrutura ScrollRightDown que faria o efeito de rolagem na vvertical com apenas uma chamada.

58

C++ Win32 Tutorial

Como antes, existem vrias chamadas a Sleep() por todo o programa. Elas apenas desaceleram as coisas para que possamos ver o funcionamento do programa.
DisplayArea.Top = 0; DisplayArea.Left = 0; DisplayArea.Bottom = SBInfo.srWindow.Bottom; DisplayArea.Right = SBInfo.srWindow.Right; SetConsoleWindowInfo(hOut, true, &DisplayArea);

Finalmente, o programa usa outra chamada em modo absoluto para restaurar a janela ao seu tamanho original. O buffer da tela tem nos limitado em tudo o que fizemos at agora. Vamos mudar o tamanho do buffer para que possamos fazer coisas maiores, ao invs de menores. Este primeiro programinha mostra como faz-lo e destaca um ponto importante. Aqui est o cdigo.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; CONSOLE_SCREEN_BUFFER_INFO SBInfo; COORD NewSBSize; hOut = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(hOut, &SBInfo); NewSBSize.X = SBInfo.dwSize.X + 5; NewSBSize.Y = SBInfo.dwSize.Y; Sleep(2000); cout << "Aumentando agora" << endl; SetConsoleScreenBufferSize(hOut, NewSBSize); Sleep(2000); NewSBSize.X = SBInfo.dwSize.X; cout << "Diminuindo agora" << endl; SetConsoleScreenBufferSize(hOut, NewSBSize); return 0; }

O program espera por dois segundos, aumenta o tamanho do buffer da tela e espera outros dois segundos, retornando o buffer ao seu tamanho original. To simples que no vou me dar ao trabalho de detalhar seu funcionamento: voc simplesmente passa uma COORD para a funo que indica o novo valor do canto inferior direito. O que importante notar que aumentar o tamanho do buffer da tela no aumenta o tamanho da janela automaticamente eles esto relacionados, mas so coisas separadas. O que voc deve observer uma barra horizontal de rolagem aparecer e desaparecer. Se voc tentar fazer o buffer da tela menor que o tamanho da janela, a chamada ir falhar. Quo grande uma janela pode ser? O primeiro programa que mostrei nesta seo reportava o tamanho do meu buffer. Minha tela comprida o suficiente para mostrar todos os 80 caracteres, mas no larga o suficiente para mostrar 300 linhas seria o telo

59

C++ Win32 Tutorial

dos meus sonhos! Para compensar esta limitao, o Windows coloca uma barra de rolagem direita do console que nos permite ver o contedo do buffer verticalmente. De forma semelhante, no ultimo programa, quando o buffer da tela era mais comprido do que a janela podia mostrar, uma barra de rolagem apareceu no canto inferior da janela do console, nos permitindo rolar horizontalmente o buffer. Por fim, podemos aumentar o tamanho do buffer at o limite da tela, mas no podemos criar janelas maiores que a tela. O que voc precisa saber qual o maior tamanho possvel da janela dada o tamanho da tela e da fonte. J respondemos parcialmente a isso. O ltimo membro restante da estrutura CONSOLE_SCREEN_BUFFER_INFO que temos usado dwMaximumWindowSize, outra COORD.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; CONSOLE_SCREEN_BUFFER_INFO SBInfo; hOut = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(hOut, &SBInfo); cout << "X Maximo : " << SBInfo.dwMaximumWindowSize.X << endl; cout << "Y Maximo : " << SBInfo.dwMaximumWindowSize.Y << endl; return 0; }

A sada se parece com isso.

O que o programa me diz que, dados a fonte, o tamanho do buffer, o tamanho fsico, a resoluo da tela, podemos criar uma janela com comprimento de 80 caracteres j sabamos disso e 58 linhas de largura. Estes valores certamente podem ser diferentes no seu sistema. Certo, eu sei como fazer o buffer da tela mais largo, mas quo larga eu posso fazer uma janela? Felizmente, existe uma funo que nos d esta informao. O prximo programa busca o tamanho mximo possvel e ento aumenta os tamanhos do buffer da tela e da janela a praticamente preencher toda a tela.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hOut; COORD NewSBSize; SMALL_RECT DisplayArea = {0, 0, 0, 0}; hOut = GetStdHandle(STD_OUTPUT_HANDLE); NewSBSize = GetLargestConsoleWindowSize(hOut); SetConsoleScreenBufferSize(hOut, NewSBSize);

60

C++ Win32 Tutorial

DisplayArea.Right = NewSBSize.X - 1; DisplayArea.Bottom = NewSBSize.Y - 1; SetConsoleWindowInfo(hOut, true, &DisplayArea); return 0; }

Perceba que GetLargestConsoleWindowSize() devolve uma COORD, que contm o maior tamanho de janela que ir caber na tela lembre-se que o buffer tem sua origem no zero, por isso precisamos subtrair 1 do valor devolvido. Como que fao para colocar o console em tela cheia?. Deparei-me uma vez com esta pergunta e decidi pesquisar sobre este problema. Esta ser a ltima coisa que desejo cobrir nesta seo. O ltimo programa mostrou como produzir uma janela do tamanho da tela provavelmente voc teve de mover a janela, mas ela cobre sim toda tela e ainda foram mantidas as caixas Minimizar, Maximizar e Fechar, o menu do sistema e as funcionalidades de redimensionamento. Depois de brigar um pouco com este problema, comecei a me questionar por que algum iria querer que o programa se parecesse com um terminal moda antiga. V entender... Bom, como quase tudo no Windows, h vrias maneiras de se fazer isso, algumas geralmente mais portveis que outras de qualquer forma, no h uma maneira infalvel de se realizar esta tarefa para todas as verses do Windows (tanto que ainda no descobri um modo computacionalmente vivel para o Windows Vista e o Seven). Um ponto de partida em comum para esta tarefa olhar o modo como o prprio Windows permite que ns possamos expandir o console para tela cheia. Abra um console e pressione Alt-Return ou Alt-Enter novamente, a 12 Microsoft eliminou esta funcionalidade nas verses mais recentes ... uma pena. O console vai a tela cheia, pressionando a mesma combinao novamente e a janela volta ao seu tamanho original. O mtodo que usarei funciona do Win 95 ao XP, o que cobre a maioria dos sistemas disponveis e suas eventualidades. Entretanto, h dois problemas. O primeiro que o mtodo usa uma funo da API chamada keybd_event(), que simula o pressionamento de teclas inserindo dados no stream do teclado. Um dos problemas com essa funo o segundo parmetro. Se voc ler o que est escrito na biblioteca da MSDN atualmente, aqui, ver que este parmetro especifica um scan code do hardware para a tecla. O primeiro problema justamente descobrir qual o scan code que desejamos usar. O segundo problema reside no fato de que o scan code no o mesmo para os vrios tipos de teclados e linguagens o cdigo de um teclado ABNT provavelmente diferente do de um ABNT2. O exemplo que usarei utiliza valores comuns de scan codes, mas eu recomendo fortemente que voc confira se estes valores esto corretos. O cdigo abaixo uma verso modificada do primeiro programa da parte 5 do tutorial, compile e rode-o, anote o scan code das teclas Alt e Enter do seu teclado. Se elas diferirem do cdigo mais abaixo, edite-o no tente rodar o programa com os scan codes incorretos: fazer isso equivale a pressionar teclas aleatoriamente, o que pode vir a causar danos ao sistema. O scan code exibido na linha de cima. Para sair do programa, pressione a tecla x.
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hIn; HANDLE hOut; COORD KeyWhere; COORD MouseWhere; COORD EndWhere; bool Continue = true; int KeyEvents = 0; int MouseEvents = 0;
12

Pesquisando um pouco encontrei a desculpa oficial da Microsoft em http://support.microsoft.com/kb/926657, que diz No possvel definir uma janela de prompt de comando para exibir no modo de tela cheia. Este sintoma somente se aplica s verses 32-bit do Windows Vista e do Windows 7. Programas 16-bit no so suportados nas verses 64-bit do Windows Vista ou do Windows 7.

61

C++ Win32 Tutorial

INPUT_RECORD InRec; DWORD NumRead; hIn = GetStdHandle(STD_INPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE); cout << "Pressione 'x' para encerrar o programa" << endl; cout << "Eventos do Teclado : " << endl; cout << "Eventos do Mouse : " << flush; KeyWhere.X = KeyWhere.Y = MouseWhere.X MouseWhere.Y EndWhere.X = EndWhere.Y = 21; 1; = 21; = 2; 0; 4;

while (Continue) { ReadConsoleInput(hIn, &InRec, 1, &NumRead); switch (InRec.EventType) { case KEY_EVENT: ++KeyEvents; SetConsoleCursorPosition(hOut, KeyWhere); cout << KeyEvents << " ScanCode : "; cout << hex << InRec.Event.KeyEvent.wVirtualScanCode << flush; if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x') { SetConsoleCursorPosition(hOut, EndWhere); cout << "Saindo..." << endl; Continue = false; } break; case MOUSE_EVENT: ++MouseEvents; SetConsoleCursorPosition(hOut, MouseWhere); cout << MouseEvents << flush; break; } } return 0; }

O prximo programinha declara uma funo AltEnter() que simula a combinao de teclas com este nome. Resumindo o funcionamento do programa, eu espero, chamo a funo, espero, chamo a funo novamente e saio do programa. O que eu deveria ver que o console comea normalmente, vai tela cheia e volta ao normal. A tecla VK_MENU representa a tecla Alt e VK_ENTER a tecla Enter.
#include <iostream> #include <windows.h> using namespace std; void AltEnter(void); int main(){ Sleep(5000); AltEnter(); cout << "Tela cheia." << endl; Sleep(10000); AltEnter(); cout << "De volta ao normal." << endl; return 0;

62

C++ Win32 Tutorial

} void AltEnter(){ keybd_event(VK_MENU, 0x38, 0, 0); keybd_event(VK_RETURN, 0x1c, 0, 0); keybd_event(VK_RETURN, 0x1c, KEYEVENTF_KEYUP, 0); keybd_event(VK_MENU, 0x38, KEYEVENTF_KEYUP, 0); return; }

Uma coisa importante a se notar que a mudana para/de tela cheia no instantnea: o tempo depende da sua placa de vdeo, poder de processamento e at mesmo da sua tela. Um problema final a funo em si. keybd_event() uma funo obsoleta, ou seja, uma cuja funcionalidade foi substituda por outra funo neste caso, por SendInput(). A funo mais nova muito mais flexvel, mas no funciona em sistemas Win-95 ou Win-98 e, embora seja compatvel com Windows Vista e Seven, ela sofre com 13 as restries impostas pelas UIPI destes sistemas operacionais, sendo que nem GetLastError() e nem o valor devolvido indicam a causa da falha. Existem vrias formas de conseguir deixar o console em tela cheia, mas todas sofrem de um problema ou outro. Existe um hack do registro, mas a chave no est no mesmo lugar para todos os SOs. Existe um hack de manipuladores, mas os valores necessrios no so os mesmos para todas as verses de SO. Existem limitaes de segurana e por a vai. Bom, no fim das contas, tive de me contentar com meu console rodando numa janela mesmo... Nos prximos tutoriais, exploraremos a programao com mltiplos threads.

13

User Interface Privilege Isolation

63

C++ Win32 Tutorial

Programao Multithreaded, parte 1.


Como meu programa pode fazer duas coisas ao mesmo tempo? Essa uma pergunta clssica que aparece de vez em quando. Voc pode, por exemplo, ter um clculo extenso ou uma operao no banco de dados a se fazer, mas enquanto o cdigo est rodando, a interface com o usurio no responder. Existe uma infinidade de cenrios onde conseguimos imaginar nosso aplicativo fazendo duas ou mais coisas simultaneamente ento, como proceder? A resposta requer um pouco de esforo para que consigamos esta soluo de forma correta, mas, em essncia, no muito difcil. Este um tutorial muito superficial, ideal como uma introduo ao assunto. Para cobrir todos os prs e contras do design multithreaded, codificao e linking seriam necessrias muitas centenas de pginas. O material apresentado aqui suficiente para que tenhamos uma noo desse tipo de programao mesmo porque ainda estou estudando isso: vivendo e aprendendo. Tentarei abordar alguns conceitos chave que devem ser esclarecidos ou, ao menos, no serem totalmente obscuros durante as pginas seguintes. Comearei fazendo uma pequena observao! Se voc tem um nico processador no seu computador o que acho difcil hoje em dia , ele somente ser capaz de trabalhar uma coisa por vez. Quando, neste tutorial, eu falar sobre fazer duas ou mais coisas ao mesmo tempo, o que eu quero dizer que parece que esto a fazer duas coisas ao mesmo tempo! Assumirei que voc tenha um sistema com um nico processador. Certo, ento vamos comear. D uma olhada neste programa.
#include <iostream> #include <windows.h> using namespace std; void Func1(); void Func2(); int main(){ Func1(); Func2(); cout << "Sai da main()" << endl; return 0; } void Func1(){ int Count; for (Count = 1; Count < 11; Count++) { cout << "Func1 loop " << Count << endl; } return; } void Func2(){ int Count; for (Count = 10; Count > 0; Count--) { cout << "Func2 loop " << Count << endl; } return; }

Pense no que acontence quando voc executa este programa. O Windows cria um processo, ento chama a funo main(). A funo principal chama Func1() e, ento, chama Func2(), voltando para a main() e o Windows encerra o processo. Voc, na verdade, acaba de rodar um thread. Um thread simplesmente uma cadeia de eventos nada mais. Todos os programas tm pelo menos um thread. Este programa muito simples, bobo at, mas demonstra um problema clssico. Func1() e Func2() so amplamente independentes uma da outra no completamente, como veremos , e ainda assim, Func2() no

64

C++ Win32 Tutorial

pode comear a rodar at que Func1() tenha terminado. Func1() simplesmente conta de 1 a 10, enquanto que Func2() counta de 10 at 1. A sada deste programa se parece com isso.

O que ns faremos agora modificar o programa para que Func1() e Func2() sejam executadas em seus prprios threads. Eu disse no incio que era fcil de fazer, mas se voc seguir este tutorial ver que ser fcil no quer dizer que est correto! ! ! ! Aviso ! ! ! Este cdigo tem srios problemas de design (bugs) o propsito dele apenas para demonstrao! Teremos problemas se utilizarmos esta abordagem simplista em nossos programas. muito importante que voc continue a ler este documento.
#include <iostream> #include <windows.h> #include <process.h> using namespace std; void Func1(void *); void Func2(void *); int main(){ _beginthread(Func1, 0, NULL); _beginthread(Func2, 0, NULL); Sleep(1000); cout << "Sai da main()" << endl; return 0; } void Func1(void *P){ int Count; for (Count = 1; Count < 11; Count++) { cout << "Func1 loop " << Count << endl; } return; } void Func2(void *P){ int Count;

65

C++ Win32 Tutorial

for (Count = 10; Count > 0; Count--) { cout << "Func2 loop " << Count << endl; } return; }

A primeira coisa a se notar que incluimos um arquivo de cabealho adicional, <process.h>, que contm os prottipos das novas funes que usaremos neste programa. Perceba tambm que mudei ambos os prottipos e a primeira linha de Func1() e Func2(). Em particular, ao invs de receber parmetros (uma lista de parmetros void), elas agora recebem um nico parmetro do tipo void *: um ponteiro void. Isto necessrio, mesmo que ns no o utilizemos neste exemplo. Na funo main(), substitu as chamadas s funes Func1() e Func2() por chamadas a _beginthread(). Esta uma das vrias funes da API que iniciam a execuo separada de um thread. uma das mais simples, mas tambm a que oferece o menor controle alm de possuir alguns problemas que discutiremos adiante. O primeiro parmetro para a API o nome da funo que ser executada, sem parnteses. Os outros dois parmetros so configurados para seus valores padro. A ltima mudana a destacar que adicionei uma chamada a Sleep() na funo main(), logo depois que os threads foram invocados e antes da mensagem de sada. Essas alteraes no cdigo so relativamente fceis de fazer. Mas potencialmente, existem outras mudanas que voc precisar fazer no seu ambiente de desenvolvimento caso voc no utilize verses mais atuais do seu compilador. Talvez acontea de o linker no conseguir fazer seu trabalho de ligar bibliotecas de multithread, portanto o cdigo acima compilar, mas o linker pode vir a falhar porque no consegue encontrar a implementao de _beginthread(). Caso isto acontea, procure nas configuraes da sua IDE algum parmetro que habilite o uso das bibliotecas de tempo de execuo algo como Use run-time library , sem se esquecer de ativar o debug em multithread. Felizmente, a maioria dos compiladores e linkers modernos j vm preparados para trabalhar em multithread. Caso no consiga compilar, linkar e rodar o cdigo acima, voc no conseguir rodar os programas presentes neste tutorial. Esta a razo pela qual comecei com este pedao simples de cdigo precisamos saber se ele funciona com voc. Precisamos aprender a engatinhar antes de correr. Assumindo que o cdigo funciona para voc, tente rod-lo algumas vezes. Embora dependa muito do que sua mquina esteja fazendo se h tempo de mquina disponvel , a cada vez que voc executar o programa provavelmente conseguir uma sada diferente! Aqui est uma sada tpica na minha mquina.

Isso! Funciona! Se olharmos cuidadosamente, veremos que o programa executou ambos os laos, s que a sada est toda embaralhada. Lembra quando eu disse que as funes eram amplamente independentes? Elas na

66

C++ Win32 Tutorial

verdade compartilham um recurso muito importante: a tela! Sempre que um program multithreaded compartilha recursos de qualquer tipo entre os threads, devemos tomar precaues para se evitar uma srie de problemas em potencial. Em um programa de um nico thread, o fato de o sistema operacional repartir o tempo de processador entre as aplicaes no importa. A aplicao resumida no ponto em que foi interropido geralmente chamados de incidentes antecipados (pr-empted incidently). Esses programas podem ser resumidos em qualquer ponto e sem gerar efeitos colaterais. Em um programa multithreaded, os threads so antecipados da mesma forma e podem ser interrompidos a qualquer momento, em qualquer instruo me refiro a qualquer instruo de cdigo de mquina (em Assembly), e no uma declarao de uma linguagem de alto nvel. Lembre-se que muitas vezes o compilador converte uma nica linha de cdigo de um programa de alto nvel em vrias instrues de cdigo de mquina. Se olharmos a sada do programa acima, o thread rodando Func2() comeou a escrever Func2 loop , mas foi interropido antes de a sada exibir 10 e o caracterere de nova linha. O thread rodando Func1() comeou a rodar, mas tambm foi interrompido antes de conseguir inserir o caractere de nova linha. Se voc seguir a sada cuidadosamente, conseguir ver onde cada thread foi paralisado e retomado. Em um programa simples como esse, pode-se ver o que aconteceu, mas em um programa mais complicado com dezenas de threads, o resultado pode se tornar uma baguna de caracteres sem sentido. O que precisamos trabalhar uma forma de sincronizar a atividade dos threads para prevenir esse resultado desagradvel e muitos outros problemas que podem nos incomodar. Comearemos a discutir sincronizao na prxima parte do tutorial.

67

C++ Win32 Tutorial

Programao Multithreaded, parte 2.

Em 1954, o autor ingls William Golding publicou o seu livro mais famoso: O Senhor das Moscas (The Lord of the Flies) e sim, esta a parte 2 do tutorial de programao multithread, continuem lendo. O livro retrata a regresso selvageria de um grupo de crianas inglesas de um colgio interno, presas em uma ilha deserta sem a superviso de adultos, aps a queda do avio que as transportava para longe da guerra. Ralph, um dos garotos, eleito o lder do grupo. Os meninos realizavam reunies para decidir o que fazer, mas as mesmas eram caticas j que todos falavam ao mesmo tempo (parece o Congresso Nacional discutindo matrias polmicas). Para resolver o problema, Ralph cria uma regra: somente a pessoa que estiver segurando A Concha uma concha marinha pode falar. Quando esta terminasse, passaria a concha para outro, que ento falava. Com essa pequena observao literria em mente, talvez agora voc entenda o porqu de comearmos nosso tutorial deste modo: a concha est sendo usada como uma ferramenta de sincronizao! Se voc entende o conceito da concha, ento no ter problemas com sincronizao. Muitas abordagens sincronizao de threads envolvem aquisio e ento liberao de algum tipo de ficha (token) ou objeto. O Windows fornece cinco ferramentas diferentes para sincronizao, geralmente chamados de objetos de sincronizao (synchronisation objects). Alguns so mais sofisticados que outros; nem todos esto disponveis; ou funcionam integralmente em verses mais antigas do Windows. O objeto de sincronizao que iremos usar neste programa o mais simples, embora seja o menos flexvel. Para este tipo de problema, porm, a escolha adequada: rpido e alcana o objetivo desejado. Ele chamado de Regio Crtica (Critical Section). Nossa meta neste programa muito similar do Ralph em O Senhor das Moscas: temos dois threads tentando conversar com a tela ao mesmo tempo. O que precisamos introduzir A Concha! Lembra quando eu disse na parte 1 que a interrupo de um thread pode acontecer a qualquer instante? Ns precisamos ter certeza que as declaraes cout iniciam e terminam sem que a sada de outro thread aparea neste meio tempo. Para utilizar um objeto CRITICAL_SECTION, precisamos declarar um. Para este programa, ns simplesmente o declaramos como uma varivel global. importante lembrar que, quando trabalhamos com programas multithreaded e mesmo com o Windows agendando a execuo dos threads da mesma forma que o faz com os programas, seus threads no so programas separados, eles ainda so parte do seu programa! Tal como Func1() e Func2(), embora rodando em threads separadas, elas ainda podem acessar qualquer varivel global declaradas no seu programa da mesma forma que uma funo non threaded o faria. Aqui est a rea das variveis globais modificada no nosso programa.
#include <iostream> #include <windows.h> #include <process.h> using namespace std; void Func1(void *); void Func2(void *); CRITICAL_SECTION Section;

68

C++ Win32 Tutorial

A nica mudana a adio da declarao de objeto global CRITICAL_SECTION que eu escolhi nomear de Section chame do que quiser. CRITICAL_SECTION um tipo definido em windows.h na verdade, winbase.h, mas ela est inclusa na windows.h , ento podemos criar variveis do tipo CRITICAL_SECTION da mesma forma que criamos um int, por exemplo. Antes que qualquer chamada para usar a regio crtica possa ser feita, ela tem de ser inicializada. Qualquer thread pode fazer isso, mas voc deve ter certeza que a inicializao feita antes da tentativa de acesso da regio crtica. No nosso programa, o lugar mais bvio para se fazer isso na funo main(), antes de rodar qualquer thread. Fazemos isso chamando InitializeCriticalSection(). Esta funo recebe um ponteiro para o objeto CRITICAL_SECTION que ns desejamos inicializar neste caso, Section, ento ns passamos &Section. De forma semelhante, quando conclumos nossa tarefa, devemos dizer ao Windows que terminamos de usar a regio crtica. Novamente faremos isso na funo main() ao chamar DeleteCriticalSection() com o ponteiro da regio como nico parmetro. No devemos excluir a regio crtica at que todos os nossos threads que a usam tenham terminado sua execuo. A funo main() modificada aparece aqui.
int main(){ InitializeCriticalSection(&Section); _beginthread(Func1, 0, NULL); _beginthread(Func2, 0, NULL); Sleep(1000); DeleteCriticalSection(&Section); cout << "Sai da main()" << endl; return 0; }

Agora precisamos modificar Func1() e Func2() para tentar adquirir o objeto da regio crtica antes da declarao cout e liber-la depois disso. Para sincronizar dois ou mais threads para um nico recurso, eles devem ambos/todos tentarem adquirir o mesmo objeto de regio crtica. Desta forma, se um thread tenta adquirir a regio crtica enquanto outro o tem, aquele ter de esperar at que este o libere. Funciona assim como com A Concha. Aqui est a funo Func1() modificada.
void Func1(void *P){ int Count; for (Count = 1; Count < 11; Count++) { EnterCriticalSection(&Section); cout << "Func1 loop " << Count << endl; LeaveCriticalSection(&Section); } return; }

Como podemos ver, adicionei uma chamada a EnterCriticalSection() imediatamente antes da declarao cout, e uma chamada a LeaveCriticalSection() logo aps dela. Ambas funes recebem o mesmo ponteiro CRITICAL_SECTION que usamos anteriormente. Func2() modificada exatamente da mesma maneira. As chamadas Enter e Leave devem ser dispostas de modo que uma quantidade mnima de cdigo esteja entre elas. Ento fiz isso
void Func1(void *P){ int Count; EnterCriticalSection(&Section); For (Count = 1; Count < 11; Count++) { cout << "Func1 loop " << Count << endl;

69

C++ Win32 Tutorial

} LeaveCriticalSection(&Section); return; }

... que deve funcionar. Mas agora, o primeiro thread a adquirir a regio crtica ir executar at a sua concluso enquanto o outro thread aguarda basicamente ns voltamos de onde comeamos. Huuum... no bem isso que queramos fazer. Traduzindo: sempre devemos ponderar cuidadosamente onde posicionar nossas chamadas de sincronizao. O fonte completo de uma possvel sincronizao do nosso programa aparece aqui.
#include <iostream> #include <windows.h> #include <process.h> using namespace std; void Func1(void *); void Func2(void *); CRITICAL_SECTION Section; int main(){ InitializeCriticalSection(&Section); _beginthread(Func1, 0, NULL); _beginthread(Func2, 0, NULL); Sleep(1000); DeleteCriticalSection(&Section); cout << "Sai da main()" << endl; return 0; } void Func1(void *P){ int Count; for (Count = 1; Count < 11; Count++) { EnterCriticalSection(&Section); cout << "Func1 loop " << Count << endl; LeaveCriticalSection(&Section); } return; } void Func2(void *P){ int Count; for (Count = 10; Count > 0; Count--) { EnterCriticalSection(&Section); cout << "Func2 loop " << Count << endl; LeaveCriticalSection(&Section); } return; }

A sada se parece com isso.

70

C++ Win32 Tutorial

Como podemos ver, a introduo de um CRITICAL_SECTION resolveu o nosso problema de sada. Entretanto, ainda existem outros problemas com esse programa. Na parte 1 do tutorial, quando converti o programa para multithread, adicionei uma chamada Sleep(). No expliquei o porqu, mas vamos explorar isso na seo seguinte do tutorial. Como uma observao, se voc est usando um console 32-bit (NT, XP, 2000), existe outra funo que pode ser chamada, TryEnterCriticalSection(), que recebe o ponteiro da regio como um parmetro, como acima. Se a regio pde ser adquirida, e , a funo devolve um valor no zero lembre-se que voc precisa testar o return e, se non-zero, chamar LeaveCriticalSection() quando terminar de usar o thread. Se a regio estiver emu so por outro thread, essa funo devolve zero imediatamente, ou seja, ela no bloqueia o thread chamado. Essa funo til em vrias situaes, evitar impasses, por exemplo. Infelizmente, essa funo no est disponvel em sistemas de 16bit (95, 98 e ME).

71

C++ Win32 Tutorial

Programao Multithreaded, parte 3.


Na primeira parte deste tutorial, enquanto convertia o programa original em um multithreaded, adicionei um chamada funo Sleep(). Eu no disse o porqu de ter feito isso, mas agora vamos trabalhar este detalhe importantssimo. Sleep(1000); Removendo a chamada e rodando o programa, no meu sistema, consegui a seguinte sada.

Voc pode conseguir algo diferente dependendo do seu sistema e do quo ocupado ele est. Claramente, algo deu muito errado aqui, mas o qu? A resposta est no modelo de threading usado pelo Windows. Quando as funes main() dos programas finalizam, o processo termina, por isso qualquer thread que foi iniciada pelo programa terminar naquele ponto. S que existe um tempo para o carregamento dos threads que ignorado pelo Windows lembre-se que ele trata os threads da mesma forma que trata um programa. Os leitores que utilizam mutlthreading de outro sistema operacional (*NIX, POSIX ou pthreads, por exemplo), onde este comportamento geralmente no ocorre, frequentemente so derrubados por essa diferena. A chamada a Sleep() foi adicionada para dar tempo de giro suficiente para rodar os threads at sua concluso 14 antes da funo main() terminar. Fazer isto certamente um remendo desesperado! Neste programa de teste, essa adio funciona, mas em aplicaes para o mundo real, voc pode no saber de quanto tempo um thread precisa. Pode ser o processamento de uma consulta a um banco de dados em um servidor que s vezes est ocupado, outras no, ou uma conexo com host desconhecido de forma alguma poderemos prever quanto tempo precisaremos. Ns temos de saber quando os threads terminaram de verdade. Na verdade, precisamos de duas coisas: uma forma de identificar os threads e um modo de dizer se eles terminaram ou no. Ento, comecemos vendo o primeiro caso. Quando ns criamos os threads l na parte 1, chamamos _beginthread() e ignoramos qualquer valor que poderia nos ser devolvido. Na verdade, a funo nos revela informaes teis, em particular (e assumindo que a chamada no falhou), como a devoluo do manipulador dos threads criados. Um manipulador o que o Windows utiliza para identificar certos tipos de objetos. medida que voc programa para Windows ver que muitas coisas so usadas via manipuladores. Algumas pessoas acham que um manipulador um tipo de ponteiro, outros que um nome interno usem a metfora que quiser e no se preocupem com isso um HANDLE simplesmente um HANDLE, e so devolvidos por algumas funes e usados por outras. Quando usamos os HANDLEs, raramente precisamos fazer algum tipo de modificao ou manipulao neles, apenas precisamos obt-los e repass-los. Um HANDLE, assim como o CRITICAL_SECTION que usamos na parte 2, um tipo acessvel ao programa quando inclumos o windows.h, e da mesma forma podemos declarar variveis para o tipo HANDLE. Aqui o programa modificado devolve e armazena o HANDLE do thread.
int main(){ HANDLE hThreads[2]; InitializeCriticalSection(&Section); hThreads[0] = (HANDLE)_beginthread(Func1, 0, NULL); hThreads[1] = (HANDLE)_beginthread(Func2, 0, NULL);

14

Eba! \o/ Programao Orientada a Gambiarra!

72

C++ Win32 Tutorial

Sleep(1000); DeleteCriticalSection(&Section); cout << "Sai da main()" << endl; return 0; }

A primeira coisa a se notar que os HANDLEs esto declarados dentro da funo main(), e no globalmente. E j que a funo main() quem far a espera, ento relevante apenas que a mesma veja os HANDLEs. Segundo, armazenei os dois HANDLEs em uma matriz, ao invs de em duas variveis HANDLE individuais. Poderia ter feito desta outra forma, mas voc entender o porqu de optar pelo array em breve. Terceiro, note que eu converti o valor de devoluo de _beginthread() para um HANDLE atravs de um casting. Isso porque _beginthread() foi projetado para devolver um unsigned int ou um unsigned 64bit int, dependendo da plataforma. Sem o cast, as regras normais do C++ preveniriam a atribuio. Finalmente, eu no conferi se ocorreram erros. Num programa para o mundo real, devemos sempre fazer isso. Se consultarmos a documentao, veremos que _beginthread() devolve -1 se encontrar um erro. Em programas pequenos como este, ele raramente falha, mas em um projeto com centenas de grandes threads complicados, muito possvel se deparar com escassez de recursos falta de memria, por exemplo. Na superfcie, parece que no chegamos a lugar algum, mas alguns manipuladores so mais inteligentes que outros. Eu disse acima que muitos objetos Windows tm manipuladores. Alguns desses objetos tambm tm manipuladores inteligentes: eles enviam um sinal de concluso da tarefa. Os HANDLEs dos threads se enquadram nesta categoria. Para se livrar daquele Sleep(), simplesmente precisamos fazer nossa main() esperar pelos sinais dos threads indicando sua concluso. A funo que ns usaremos para fazer isso a WaitForMultipleObjects(). A funo recebe quatro parmetros. Aqui est a funo main() novamente modificada, desta vez preparada para usar essa rotina.
int main(){ HANDLE hThreads[2]; InitializeCriticalSection(&Section); hThreads[0] = (HANDLE)_beginthread(Func1, 0, NULL); hThreads[1] = (HANDLE)_beginthread(Func2, 0, NULL); WaitForMultipleObjects(2, hThreads, true, INFINITE); DeleteCriticalSection(&Section); cout << "Said a main()" << endl; return 0; }

Os dois primeiros parmetros se referem ao array que criamos anteriormente pronto, est a a explicao para no usar duas variveis independentes. Esta funo espera que o HANDLE seja passado como um array. O primeiro parmetro um contador de quantos HANDLEs existem na matriz, e o segundo o array em si. O terceiro parmetro um booleano. Ele diz funo como esperar. Se quisermos esperar at que todos os
HANDLEs assinalem sua concluso, ento indicamos o valor true que o desejado neste caso. Se o parmetro est configurado para false, ento a espera retorna assim que receber o sinal de qualquer HANDLE.

O parmetro final nos permite dizer funo o quanto esperar. Se dissermos INFINITE, ela ir esperar at que os threads sinalizem, independente do tempo que levar. possvel passar um valor inteiro como parmetro: neste caso, o tempo ser em milisegundos. Se os threads no se completarem dentro do tempo especificado, a

73

C++ Win32 Tutorial

funo retorna de qualquer forma, testa o valor devolvido e, se houver um estouro do tempo estipulado, devolve a constante WAIT_TIMEOUT. A sada se parece com a mesma de quando usamos a Sleep(), mas ao rodar ambos os programas, vers que este novo muito mais rpido. Perceba que nenhuma mudana foi necessria s funes dos threads para alcanar este resultado. Agora ns convertemos um programa de thread nico em uma verso multithreaded do mesmo. Espero que voc concorde que no foi difcil. Multithreading, em essncia, no difcil. O que necessrio, no entanto, usar a cabea e planejar. Vrios problemas com sincronizao, por exemplo, so causados quando as pessoas tentam impor multithreading em um programa complicado que no foi concebido daquela forma. Vrias gambiarras/improvisos so inseridos e frequentemente aparecem erros difceis de serem detectados. Programas multithreaded devem ser concebidos para serem multithreaded. A estratgia de sincronizao deve ser parte do design. O ponto final que gostaria de destacar neste tutorial sobre o programa que desenvolvemos diz respeito ao desempenho. Um erro comum ao tornar uma aplicao multithreaded assumir que seu tempo de execuo ser menor. No passado, at certo ponto, isso era verdade. Quando um thread queria usar um dispositivo de sada, um disco, por exemplo, o thread era suspenso at que o disco fizesse suas operaes de I/O. Este era um perodo de tempo substancial gasto pelo fato de os discos serem lentos. Hoje, a maioria dos pedidos de I/O de um thread no feita de imediato: as solicitaes so armazenadas em um cache, o que significa que o sistema as coloca em fila para execuo em um momento mais conveniente. Quando um thread interrompido, frequentemente se faz necessrio completar ou eliminar todas as operaes do cache. As prximas instrues parcialmente decodificadas nos pipelines do processador so descartadas. E os registradores dos threads e as pilhas tm de ser salvas. Tudo isso acontece antes de os registradores e pilhas do thread seguinte serem restaurados e assim executados. Isso acontece toda vez que um thread interrompido. Todo este procedimento gera um custo de tempo adicional. Se cronometrarmos cuidadosamente o programa original serial e a verso multithreaded, acabaremos por ver que o segundo um pouco mais lento justamente por causa deste custo adicional. O que quero destacar que certos tipos de aplicativos no so apropriadas ao multithreading. Tarefas que esto amarradas CPU, execuo de clculos e etc. geralmente no so apropriadas para um multithreading extensivo. Ela recomendada quando se tem um nico thread realizando um monte de clculos e outro gerenciando a interface com usurio. Dividir clculos em vrias operaes paralelas geralmente acaba com o desempenho deixemos isso para a programao em paralelo, com suas teorias e mtodos prprios. Tarefas que gerenciam uma grande quantidade de trfego de rede usando bloqueio de I/O, tarefas processando bancos de dados pesados em servidores variavelmente carregados sejam remotos ou locais , enfim, tarefas em que frequentemente se aplica a frase senta e espera podem obter um ganho de desempenho muito grande com o uso correto de threads. No incio, eu disse que essa era apenas uma introduo a um assunto muito extenso. Peo a todos que leiam a documentao, o help e a MSDN para descobrir mais sobre os conceitos e as armadilhas das rotinas que usei. Meu conhecimento sobre threads ainda limitado, ento, se algum melhor preparado que eu sobre o assunto quiser completar esta documentao, por favor, sinta-se vontade. O intento deste conjunto de documentos incentivar os estudantes de programao a descobrir como trabalhar com este sistema operacional, alm de reunir solues viveis a perguntas freqentes. Bom, no momento estou satisfeito com o que consegui reunir nestes tutoriais. Talvez venha a inserir outros tpicos a este documento no futuro. Ou ser que algum deseja faz-lo?

74

You might also like