Notas sobre desenvolvimento em Symbian OS. Luis Valente

Notas sobre desenvolvimento em Symbian OS Luis Valente [email protected] Este trabalho representa uma pesquisa sobre conceitos e características...
3 downloads 1 Views 429KB Size
Notas sobre desenvolvimento em Symbian OS Luis Valente [email protected] Este trabalho representa uma pesquisa sobre conceitos e características do sistema operacional Symbian OS. A plataforma utilizada neste trabalho é a Série 60 da Nokia, sendo que o SDK de referência é o S60 3rd Edition MR (Maintenance Release).

Índice 1.Visão inicial do Symbian OS............................................................................................................ 3 1.1.Processos....................................................................................................................................3 1.2.Memória.....................................................................................................................................4 1.3.Kernel.........................................................................................................................................4 1.4.DLLs.......................................................................................................................................... 5 1.5.GUI............................................................................................................................................ 5 2.Ferramentas de desenvolvimento...................................................................................................... 6 2.1.Criando um projeto.................................................................................................................... 6 2.2.Importando projetos................................................................................................................... 9 2.3.Especificando bibliotecas........................................................................................................ 10 2.4.Distribuição da aplicação.........................................................................................................10 2.5.Como assinar automaticamente o pacote.................................................................................12 3.Programação no Symbian em C++..................................................................................................13 3.1.Tipos de dados......................................................................................................................... 13 3.1.1.Classes T.......................................................................................................................... 13 3.1.2.Classes C.......................................................................................................................... 14 3.1.3.Classes R.......................................................................................................................... 14 3.1.4.Classes M......................................................................................................................... 14 3.2.Mecanismo de tratamento de erros.......................................................................................... 14 3.2.1.Importante........................................................................................................................ 16 3.2.2.Panics............................................................................................................................... 17 3.2.3.Macros de depuração....................................................................................................... 17 3.3.Outras convenções................................................................................................................... 18 4.Descritores....................................................................................................................................... 19 4.1.Base..........................................................................................................................................19 4.2.Literais..................................................................................................................................... 19 4.3.Descritores baseados em buffer............................................................................................... 20 4.4.Descritores baseados em pointeiros......................................................................................... 20 4.5.Descritores baseados na heap.................................................................................................. 21 4.6.Outras notas............................................................................................................................. 21 4.7.Convertendo descritores entre 8 bits e 16 bits......................................................................... 22 5.Programação de GUI....................................................................................................................... 23 5.1.Inicialização............................................................................................................................. 23 5.2.Classes iniciais......................................................................................................................... 23 5.3.Tratamento de eventos e entrada............................................................................................. 24 5.3.1.Teclas............................................................................................................................... 24 5.3.1.1.Detectando teclas para jogos.................................................................................... 25 5.3.2.Comandos de menu.......................................................................................................... 26 5.4.Parte gráfica............................................................................................................................. 27 5.4.1.Desenhando...................................................................................................................... 27 5.4.1.1.Texto .......................................................................................................................28

5.4.2.Formas geométricas......................................................................................................... 28 5.5.Recursos...................................................................................................................................29 5.5.1.Strings.............................................................................................................................. 31 5.5.2.Definições de comandos.................................................................................................. 33 5.5.3.Menus...............................................................................................................................33 5.5.4.Mensagens de alerta e caixas de diálogo pré-definidas................................................... 34 5.6.Painéis...................................................................................................................................... 35 6.Objetos ativos.................................................................................................................................. 36 6.1.Roteiro para a criação de objetos ativos.................................................................................. 36 6.1.1.Construção....................................................................................................................... 37 6.1.2.Registro no escalonador................................................................................................... 38 6.1.3.Chamada assíncrona.........................................................................................................38 6.1.4.Resposta ao término do serviço....................................................................................... 38 6.1.5.Cancelamento de requisições........................................................................................... 39 6.1.6.Tratamento de erros......................................................................................................... 39 6.2.Possíveis erros..........................................................................................................................39 6.3.Exemplo................................................................................................................................... 40 7.Temporização para jogos.................................................................................................................44 8.OpenGL ES..................................................................................................................................... 47 8.1.Introdução................................................................................................................................ 47 8.2.Arquivos necessários............................................................................................................... 47 8.3.Roteiro – Inicializando a OpenGL ES .................................................................................... 48 8.3.1.Recuperar a tela padrão do dispositivo............................................................................ 49 8.3.2.Inicializar a OpenGL ES.................................................................................................. 49 8.3.3.Escolher uma configuração para a OpenGL ES...............................................................49 8.3.4.Criar o contexto OpenGL ES........................................................................................... 50 8.3.5.Ativar o contexto criado...................................................................................................50 8.3.6.Finalização da OpenGL ES..............................................................................................51 8.4.Algumas diferenças entre OpenGL e OpenGL ES.................................................................. 51 8.4.1.Particularidades e dicas para OpenGL ES....................................................................... 52 9.Dicas úteis....................................................................................................................................... 54 9.1.1.Diretório privado da aplicação......................................................................................... 54 9.1.1.1.Copiando arquivos de dados para o emulador..........................................................54 9.1.1.2.Copiando arquivos de dados para o dispositivo....................................................... 55 9.1.2.Modo console................................................................................................................... 55 9.1.3.Como trocar o UID do projeto......................................................................................... 56 9.1.4.Códigos de erro................................................................................................................ 56 10.Referências.................................................................................................................................... 57

1. Visão inicial do Symbian OS O Symbian é um sistema operacional para dispositivos móveis, desenvovolvido pelo consórcio Symbian Ltd [Symbian 2007] . Esse consórcio é formado por empresas que atuam no mercado de telefones celulares. O Symbian OS é um sistema operacional multitarefa de 32 bits, que foi derivado do sistema operacional EPOC [Wikipedia 2007a]. O Symbian OS possui uma arquitetura flexível o bastante para permitir diferentes tipos de interface com o usuário sejam implementadas acima da camada principal do sistema. Dessa forma, os fabricantes podem personalizar os aparelhos de acordo com as suas necessidades. Atualmente, existem três plataformas de referência no que diz respeito à interface gráfica: Série 60, Série 80 e UIQ [Coulton e Edwards 2007]. A Série 60 (S60) representa aparelhos que possuem tela colorida e resoluções que podem variar entre 176x208, 320x240 e 352x288 (modo retrato ou paisagem). O método de entrada predominante é o teclado numérico básico dos telefones. A Série 80 representa aparelhos que possuem uma tela colorida maior (por exemplo, 640x200) e teclado (QWERTY) completo. A plataforma UIQ representa aparelhos com tela colorida de resolução 200-208x320 e método de entrada baseado em caneta ou toque (touch-screen). Esses dispositivos não possuem teclado. Uma outra característica importante do Symbian OS é que esse sistema é aberto em relação à instalação de aplicações, ou seja, qualquer um pode desenvolver aplicações e disponibilizar para outros usuários. Entretanto, a partir de versão 9.0, é necessário que a aplicação possua um certificado digital para pode utilizar certas características do sistema (por exemplo, escrever um arquivo no aparelho). De acordo com [Babin 2005], o Symbian pode ser dividido nos seguintes componentes: ▪

kernel: Componente principal do sistema operacional, sendo responsável por gerir recursos compartilhados do sistema e execução de programas;



bibliotecas básicas: Oferecem funcionalidades e serviços básicos do sistema operacional, como sistema de arquivos, timers, gestão de banco de dados, entre outras. Estão disponíveis na forma de APIs especializadas, que podem ser usadas tanto por desenvolvedores de aplicação quanto pelos próprios serviços básicos do sistema. Essas APIs também podem implementar utilidades como funções para manipular strings e outras estruturas de dados;



serviços, engines e protocolos: Fornecem acesso a serviços e dados de aplicação;



framework de aplicação: Representa a estrutura básica para se construir aplicações com interface gráfica;



arquitetura de comunicação: Representa a estrutura e ferramentas para realizar comunicação de dados, como protocolos TCP/IP, Bluetooth, infra-vermelho e serviços de mensagens (SMS, MMS);



middlewares diversos: APIs diversas para os outros tipos de funcionalidades.

1.1. Processos O Symbian OS é um sistema operacional multitarefa preemptivo. Um aplicativo nesse sistema pode fazer uso de várias threads, embora isso não seja recomendado devido ao custo gerado para a administração desses recursos. Para evitar esse tipo de situação, o Symbian OS provê um framework para comunicação assíncrona batizado de objetos ativos.

Os objetos ativos podem ser usados para se implementar processamento cooperativo, simulando a existência de várias threads. Uma thread qualquer possui um escalonador ativo, que contém um disparador de eventos e uma lista de objetos ativos. Durante o seu ciclo de vida, o escalonador espera por eventos e quando este acontece, invoca o objeto ativo que estava esperando pelo evento. Uma vez tratado o evento recebido, o escalonador volta a esperar novos eventos. Como os objetos ativos funcionam de maneira cooperativa, existe o risco de uma tarefa bloquear todo o processo, o que deve ser evitado. Os processos no Symbian OS possuem espaço de memória reservado a eles, não sendo possível que outro processo acesse essa área diretamente, por questões de segurança. Vários processos podem ser executados ao mesmo tempo, sendo o kernel responsável por realizar a gestão de execução. Cada processo possui pelo menos uma thread, e caso exista mais de uma, todas elas podem acessar o espaço de memória correspondente ao processo que as contém. O modelo de arquitetura de muitos serviços no Symbian OS é o cliente/servidor. Um servidor é um aplicativo que não possui interface com o usuário e provê um tipo de funcionalidade para interessados em utilizá-la. Por exemplo, o servidor de arquivos. Os serviços usualmente são executados em um processo separado, de modo que a comunicação entre clientes e servidores se dá através do sistema operacional. 1.2. Memória O tipo de memória utilizado em dispositivos baseados no Symbian OS pode ser de quatro tipos: memória RAM, ROM, discos internos flash e cartões de memória. A memória RAM representa a memória de trabalho do sistema, disponível para os aplicativos. A memória ROM armazena o sistema operacional, código de boot, drivers e código específico do hardware. O usuário não pode escrever nessa área. Os discos flash internos representam memórias adicionais que pode funcionar como um disco rígido para leitura e escrita de dados. Os cartões de memória também funcionam como discos de armazenamento, só que removíveis. Dessa forma, é possível alterar a capacidade de armazenamento de um dispositivo. Em relação ao endereçamento de memória, os processadores atuais dos dispositivos baseados em Symbian OS (ARM 32 bits) possuem unidades de mapeamento de memória (MMU) capazes de endereçar até 4GB de memória através de memória virtual. É importante ressaltar que a memória física disponível em um dispositivo pode ser muito menor do que esse valor. 1.3. Kernel O kernel é responsável pela gestão e controle do sistema operacional. Isso inclui funcionalidades para comunicação entre processos, gestão de estruturas para sincronismo (semáforos e objetos de exclusão mútua, por exemplo) e criação de threads e processos. O sistema possui uma camada de abstração de hardware (HAL), que provê uma API para acessar serviços e dados do kernel. A implementação do HAL depende de cada hardware, e a API é utilizada como um meio de abstrair esses detalhes. Entretanto, funcionalidades mais específicas (para acessórios, por exemplo) podem ser tratadas através de drivers especializados. Nesse caso o kernel possui funcionalidades para carregar esses drivers de modo que os aplicativos possam utilizá-los. O Symbian OS possui uma biblioteca do usuário ( user.dll) para que as aplicações possam acessar serviços do kernel em modo usuário. Isso se deve ao fato de que muitas funções do kernel são executadas em modo privilegiado (com acesso a toda memória de recursos de hardware), não sendo possível que as aplicações a utilizem diretamente.

1.4. DLLs As DLLs são bibliotecas de ligação dinâmica, assim como aquelas que existem em sistemas operacionais como Windows. As DLLs são bastante usadas no Symbian OS. Existem dois tipos de DLLs no Symbian OS: estáticas e polimórficas. As DLLs estáticas representam a versão tradicional de DLLs que contém um conjunto de funções que podem ser compartilhadas entre várias aplicações. As DLLs polimórficas atuam como plug-ins no sistema operacional. Em outras palavras, esse tipo de DLL tipicamente contém implementação de classes abstratas. Um ponto importante a considerar em DLLs do Symbian OS é a questão da declaração de dados estáticos, sejam em variáveis simples ou em classes. Por exemplo: TInt var1; void foo1 () {} void foo2 () { static TInt var2; } class ABC { static TInt field; }; const TInt c1 = 0;

Para as versões do Symbian OS anteriores à 9.0, todas essas variações não são permitidas, exceto a constante. Um dos argumentos para essa restrição era o de poupar memória devido ao potencial alto número de DLLs que podem estar em uso no sistema. 1.5. GUI O sistema de GUI no Symbian OS é composto por várias camadas. Nas camadas de baixo nível, existe um servidor de janelas que é responsável por prover acesso à tela e à entrada de dados. Outra função desse servidor é direcionar os eventos de janela para a aplicação correta. Acima do servidor de janela está o framework de controle da UI, também chamado de CONE (control environment). Essa camada provê funcionalidades de nível mais alto e se comunica com o servidor de janelas segundo o modelo cliente/servidor. Essa camada não provê controles de interface concretos para as aplicações. O UIKON é o framework de aplicações com interface gráfica do Symbian OS. Possui classes de interface concretas e outras responsáveis por tratar eventos. Acima do UIKON reside a camada de interface que é específica da plataforma utilizada (S60, S80, UIQ).

2. Ferramentas de desenvolvimento As aplicações para Symbian podem ser desenvolvidas com C++, C, Java, Python e Flash. Este trabalho foca no desenvolvimento em C++. O primeiro passo para se desenvolver aplicações para Symbian é obter o SDK (software development kit) apropriado. O SDK contém bibliotecas e documentação para a plataforma escolhida. Neste texto o SDK utilizado é o S60 3rd Ed MR (Maintenance Release), que é comum para os dispositivos baseados na Série 60, 3ª edição. As ferramentas podem ser obtidas no Forum Nokia, localizado atualmente em http://www.forum.nokia.com/main/resources/tools_and_sdks/. Entre algumas versões de SDK disponíveis, estão: ▪

3rd Edition, FP1: Versão para aparelhos baseados na plataforma S60 3rd Ed. FP1 (Feature Pack 1);



3rd Edition, Maintenance Release: Versão com correções para a plataforma S60 3rd Ed.;



2nd Edition, FP 3, FP 2 e FP 1: Versão para aparelhos baseados nas plataformas S60 2nd Ed. FP 3 (Feature Pack 3), S60 2nd Ed. FP 2 e S60 2nd Ed. FP1, respectivamente;

Na nomenclatura desses SDKs, pode existir siglas do tipo CW e WINS. Essas siglas indicam o tipo de ambiente de desenvolvimento para o qual o SDK foi construído. No caso, CW indica o ambiente CodeWarrior e WINS corresponde ao ambiente da Microsoft (Visual Studio). Os SDKs oferecem um ambiente mínimo para o desenvolvimento de aplicações, baseado em modo texto. Entretanto, para facilitar o trabalho de desenvolvimento, podem ser utilizadas algumas IDEs: Carbide.c++, Carbide.vs (Microsoft Visual Studio) e CodeWarrior. O Carbide.c++ [Nokia 2007a] é uma ferramenta desenvolvida pela Nokia a partir do ambiente Eclipse. Existem duas versões comerciais (Developer e Professional) e uma versão gratuita (Express) para fins não-comerciais. O Carbide.vs [Nokia 2007b] é um conjunto de ferramentas que se integram ao Microsoft Visual Studio .NET 2003, possibilitando o desenvolvimento de aplicações com essa ferramenta. O Codewarrior é uma ferramenta que era utlizada por desenvolvedores de Palm e que está sendo substituída pelo Carbide. O processo de desenvolvimento de aplicações Symbian ocorre inteiramente no PC. O SDK possui um emulador que pode ser utilizado para testar a aplicação, antes de transferí-la para o aparelho. Neste trabalho, é utilizado o Carbide.vs., versão 2.0.1. 2.1. Criando um projeto Para se criar um projeto utilizando o Carbide.vs, é preciso selecionar o menu File > New > Project e então escolher “Symbian OS Project”, conforme a Figura 1. É importante que o caminho utilizado para o projeto não contenha espaços, ou o processo de compilação irá falhar. A seguir, a janela “Create new Symbian OS Project” será exibida (Figura 2). Nessa janela é possível escolher que tipo de projeto será gerado, entre os modelos pré-definidos. O item “Project Type” indica se a plataforma será o Symbian 9 ou alguma versão anterior (“Classic”). Para aparelhos baseados em na Série 60 3ª Edição, é necessário escolher Symbian 9. A seção “Project Template” contém os modelos de projetos pré-definidos. Para uma aplicação normal, a opção correspondente é “S60 3rd Ed. Hello World Application”.

Figura 1: Criação de projeto.

Na parte de baixo da janela, é possível escolher para quais SDKs o projeto poderá ser compilado. No caso da figura, o SDK “S60 3.0 Maint” foi escolhido. Para cada SDK, pode-se escolher também quais configurações serão utilizadas. A configuração “WINSCW” indica que a aplicação será construída para ser executada no emulador. Já a configuração “GCCE” é a responsável por construir a aplicação para ser executada no aparelho, usando o compilador gcc. Caso existam outros SDKs instalados na máquina, é possível habilitá-los ou não para serem escolhidos através do botão “Enable/Disable SDKs”.

Figura 2: Escolha do SDK e tipo do projeto.

Na janela seguinte, é possível configurar características do projeto. Na parte “Basic Settings” (Figura 3), é possível indicar o nome base da aplicação (que será usado pelo assistente para nomear as classes) e também o UID (Universal Identification Number) da aplicação. No Symbian OS, toda aplicação deve possuir um UID (32 bits) e este deve ser único no sistema. Para se obter um UID válido, é necessário requisitar ao consórcio Symbian através do endereço [email protected]. Entretanto, para o processo de desenvolvimento é possível utilizar qualquer identificador entre 0x01000000 e 0x0fffffff. No caso do Carbide.vs, o assistente irá gerar um identificador automaticamente.

Figura 3:

Na parte “Project Directories”, é possível especificar quais serão os diretórios usados no projeto. Cada diretório possui uma funcionalidade. Os diretórios gerados pelo Carbide.vs, por padrão, são: ▪

data;



gfx;



group;



inc;



sis;



src;



tsrc.

O diretório data guarda guarda arquivos de recursos (.rss) que são usados para definir elementos da interface gráfica e strings de texto. O diretório gfx armazena arquivos de imagens (ícones) da aplicação.

O diretório group guarda o arquivo bld.inf e um arquivo .mmp. O arquivo bld.inf é obrigatório e contém instruções para o processo de construção da aplicação. O arquivo .mmp corresponde a um arquivo com intruções de compilação (makefile) do projeto. O diretório inc armazena os arquivos .h (cabeçalhos) utilizados no projeto. Os arquivos fontes (.c, .cpp) são armazenados no diretório src. No diretório tsrc, são armazenados fontes para classes de teste. Finalmente, o diretório sis contém um arquivo .pkg que é usado na geração do arquivo de instalação .sis, para um determinado aparelho. Os arquivos .sis são armazenados nesse diretório. O

assistente

irá

criar

quatro

classes: CHelloSymbianApplication, CHelloSymbianDocument, CHelloSymbianAppView e CHelloSymbianAppUi. Essas classes são derivadas das classes básicas do framework Avkon, que é o framework de interface gráfica da plataforma S60. Esse framework segue o padrão MVC – Model, View, Controller [Wikipedia 2007b]. Por isso, uma aplicação simples possui os seguintes componentes: ▪

Application View (CHelloSymbianAppView): Representa a janela principal da aplicação, que contém todos os outros controles;



Application UI (CHelloSymbianAppUi): Corresponde ao Controller do MVC, sendo responsável por criar a View e tratar eventos disparados pelos controles de interface;



Application Document (CHelloSymbianDocument): Responsável por armazenar dados de aplicação e por criar o Application UI;



Application (CHelloSymbianApplication): Representa a aplicação principal, que cria o Documento. Esse componente também é responsável por determinar o UID da aplicação.

2.2. Importando projetos Muitos exemplos de projetos em Symbian OS são preparados para serem compilados e executados diretamente pela linha de comando, o que pode tornar o processo complicado. Dessa forma, é comum que as IDEs ofereçam um assistente para se importar projetos. No Carbide.vs, esse assistente poderá ser invocado através do menu File > Import Symbian Project. A figura 4 exibe a janela inicial do assistente. No campo “Project File” é necessário especificar o arquivo bld.inf ou .mmp do projeto que deve ser importado. O campo “Project Type” indica qual é o tipo de projeto a ser importado, e o assistente tentará deduzir o tipo baseado no arquivo informado. A próxima janela pode possuir uma seção (“General settings”), caso um arquivo .mmp tenha sido especificado, ou duas seções (“Solution Settings” e “Project Settings”) caso tenha sido usado o arquivo bld.inf. Para arquivos bld.inf, os SDKs e suas configurações a serem usadas podem ser escolhidas na seção “Solution Settings”. Modificar o campo “Solution Root” não possui efeito já que o assistente salva os arquivos de projeto no diretório raiz do projeto original. Na seção “Project Settings” é possível escolher o nome do arquivo de projeto a ser salvo. Essa seção contém uma entrada para cada projeto existente no arquivo bld.inf. A seção “General Settings”, combina elementos das seções anteriores.

Figura 4: Assistente de importação de projetos.

2.3. Especificando bibliotecas Algumas funcionalidades da API do Symbian OS são implementadas em DLLs. Dessa forma, é preciso especificar os arquivos de ligação (.lib) para o compilador. No Carbide.vs, isso pode ser feito acessando-se o menu Project > properties. A seguir, acessar a pasta Linker > Input. Na entrada “Additional Dependencies”, colocar o nome da biblioteca requerida. Esse procedimento funciona quando se está gerando um executável para o compilador. A configuração que gera o executável para o aparelho utiliza as mesmas bibliotecas especificadas na outra configuração. 2.4. Distribuição da aplicação Para instalar a aplicação no telefone celular, é preciso primeiro gerar o pacote .sis correspondente. Para isso, é necessário trocar a configuração de compilação para que o executável seja gerado para o aparelho. No Carbide.vs, isso pode ser feito ao se acessar o menu Project > SDK Configuration Manager e escolher a opção GCCE. A seguir, é necessário acessar o menu Build > Configuration Manager e escolher a configuração apropriada para o aparelho (GCCE, no caso), como a figura 5 ilustra.

Figura 5: Janela para selecionar configuração.

A partir da versão 9.0 do Symbian OS, é necessário assinar digitalmente o arquivo .sis para que seja possível instalá-lo no aparelho. Dependendo do tipo de funcionalidade requerida pela aplicação, pode ser precido adquirir um certificado diretamente com o consórcio Symbian (“Developer Certificate”). Entretanto, se a aplicação não fizer uso de funcionalidades restritas, pode-se simplesmente “auto-assinar” (“self-sign”) a aplicação. Para isso, é preciso criar um certificado digital e uma chave privada. O SDK do Symbian OS possui ferramentas para realizar essa tarefa: makekeys e signsis. A ferramenta makekeys pode ser utlizada para se criar uma chave privada e um certificado digital. A seguir, seque um exemplo de criação de uma chave privada chamada minhaChave.key e um certificado meuCertificado.cer. makekeys -cert -password minhasenha -len 2048 -dname "CN=Usuario de Teste OU=Development OR=Empresa XPTO CO=FI [email protected]" minhaChave.key meuCertificado.cer

Para assinar o certificado, o SDK possui o programa signsis (que normalmente está no diretório Epoc32\tools do SDK. No Carbide.vs, pode-se usar o seguinte procedimento: No menu Tools > Manage Certificates, o certificado criado pelo makekeys pode ser incluído na lista de certificados, o que deve ser feito primeiro. Para se assinar o .sis, é necessário clicar com o botão direito sobre o arquivo .sis no “Solution explorer” do projeto, e escolher a opção “SignSis”. Quando essa opção for escolhida, uma janela será aberta e pedirá que se escolha um certificado, a partir da lista de certificados cadastrados. Escolhendo-se um certificado, o pacote .sis original será assinado e um novo arquivo .sis será gerado e armazenado no mesmo diretório do original. De posse do arquivo .sis assinado, é necessário instalá-lo no dispositivo, o que pode ser realizado através do Nokia PC Suite ou copiando diretamente para o dispositivo. Na primeira opção, se o Nokia PC Suite estiver instalado, é possível clicar no arquivo diretamente na IDE para que o Nokia PC Suite seja invocado. Nesse caso, o Nokia PC Suite irá perguntar se o aplicativo deve ser instalado. Caso assim se deseje, o telefone será contactado e o Nokia PC Suite irá orientar para que se complete a operação no aparelho.

Na segunda opção, o telefone deverá estar funcionamento em modo de transferência de dados. Nesse caso, é preciso copiar o arquivo .sis para o telefone, através do Windows Explorer ou do Phone Browser do Nokia PC Suite, por exemplo. Uma vez copiado, é necessário procurar o arquivo no telefone, e pedir para instalá-lo. Pode acontecer de o telefone se recusar a instalar a aplicação e informar que o certificado está expirado. Quando isso acontece, a data e hora atual do aparelho estão fora da validade do certificado. Nesse caso é preciso alterar a data e hora do telefone, ou usar outro certificado cuja validade esteja de acordo com a configuração do aparelho. Isso é particularmente importante quando se importa para uma IDE um projeto que foi gerado em outra ferramenta, e que já foi executado alguma vez pelo emulador. 2.5. Como assinar automaticamente o pacote No Carbide.vs, é possível configurar o ambiente para gerar o arquivo .sis já assinado. Para isso é preciso acessar o menu Project > Carbide.vs properties. Uma janela será aberta, e na opção “Security properties” é possível escolher o certificado padrão a ser usado marcar a opção para assinar automaticamente o arquivo.

3. Programação no Symbian em C++ A programação em C++ no Symbian OS possui uma série de peculiaridades. Características como herança, polimorfismo, funções virtuais e templates podem ser utilizadas sem problemas. Entretanto, o Symbian OS possui um sistema próprio para tratamento de exceções e não possui uma versão oficial da STL, até o momento, entre outros aspectos. 3.1. Tipos de dados Existe uma série de convenções para os tipos de dados usados na API e funções. Segundo a literatura, os objetivos dessa abordagem seriam: ▪

Melhorar a legibilidade do código;



Dar pistas sobre o comportamento das classes;



Dar pistas sobre o comportamento das funções e métodos.

A convenção é batizar os tipos com prefixos que dão dicas de sua funcionalidade. Os prefixos disponíveis são: ▪

T: Tipos de dados simples;



C: Classes alocadas dinamicamente e que são derivada da CBase;



R: Classes que representam recursos do sistema;



M: Interfaces (como em Java).

3.1.1. Classes T Essas classes representam tipos simples e tipos que são alocados normalmente na pilha. Geralmente são estruturas compostas (“plain old data”), como structs no C. Tipos definidos através de typedef também entram nessa categoria. No Symbian OS são definidos diversos typedefs para tipos simples do C++, conforme a tabela 1. Tipo

Significado

TTnt, TUint

Inteiro com e sem sinal, cujo tamanho depende da máquina (em máquinas 32 bits, tamanho = 32 bits)

TInt8, TInt16, TInt32, TInt64

Inteiro com sinal de tamanhos específicos

TUint8, TUint16 e TUint32

Inteiro sem sinal de tamanhos específicos

TReal32, TReal64

Ponto flutuante de tamanho específicos

TReal

Sinônimo de TReal64

TText8, TText16

Representam um caracter simples, de 8 ou 16 bits

TBool

Booleano, que na verdade é um inteiro, como no C. Valores iguais a 0 são EFalse e 1 é ETrue. Cuidado ao comparar com ETrue porque qualquer valor diferente de zero é considerado como verdadeiro.

TAny

Sinônimo de void.

Tabela 1: Tipos básicos do Symbian OS.

As classes do tipo T podem ser criadas dinamicamente, mas normalmente são alocadas na pilha. É comum também que essas classes não possuam construtor nem destrutor.

Em relação ao destrutor, existe um fato a considerar. O mecanismo de exceções do Symbian OS (Leaves) não funciona de maneira análoga ao mecanismo de exceções padrão. Por exemplo, quando ocorre uma exceção Symbian, os destrutores de objetos alocados na pilha não eram chamados (pelo menos até a versão 9). Dessa forma, os destrutores das classes não eram executados e isso poderia gerar um problema caso se fosse necessário liberar algum recurso no destrutor. Por essa razão, essas classes não poderiam conter referências ou ponteiros para recursos alocados dinamicamente. Entretanto, a partir da versão 9 esse mecanismo passou a ser implementado internamente com exceções C++. Assim, os destrutores das classes são executados quando ocorre um leave, tanto no emulador quanto no aparelho. 3.1.2. Classes C As classes do tipo C são aquelas que precisam ser alocadas dinamicamente. O prefixo C é uma convenção para indicar que a classe é derivada de CBase. O sistema assegura que as classes desse tipo possuem as seguintes características: ▪

Todos os membros da classes são inicializados com zero;



O destrutor é virtual, de modo que é possível desalocar corretamente uma classe derivada através de um ponteiro para o CBase.

A primeira característica é implementada pelo operador new, de modo que isso só ocorrerá caso a classe seja alocada dinamicamente. Caso a classe seja declarada na pilha esse comportamento não irá ocorrer. Uma outra peculiaridade é que o operador de atribuição e o construtor de cópia são declarados como privados em CBase, de modo a evitar cópias acidentais. 3.1.3. Classes R As classes R encapsulam recursos que estão localizados em outra parte do sistema, como nos servidores. Essas classes podem ser usadas tanto na pilha como na heap. É importante notar que ao desalocar uma instância dessa classe, o recurso encapsulado não é liberado. Por isso é necessário liberar explicitamente todos os recursos antes de desalocar o objeto. Frequentemente classes R possuem métodos do tipo Open e Close que são responsáveis por manipular o recurso encapsulado. 3.1.4. Classes M As classes M representam interfaces como as que existem em Java e C#. No C++, são apenas classes abstratas puras. O uso de classes M é a alternativa para a implementar herança a partir de várias classes ao mesmo tempo. Herança múltipla a partir de várias classes concretas não é permitida. 3.2. Mecanismo de tratamento de erros O Symbian OS possui o seu próprio sistema de tratamento de erros, que lembra o método oficial de tratamento com exceções do C++. Uma das justificativas para a criação desse sistema é que na época em que o sistema foi projetado, o modelo de tratamento de exceções do C++ não estava bem definido. Entretanto, na versão 9, o Symbian OS utiliza internamente exceções C++ (vide arquivo e32cmn.h). Esse mecanismo introduz os conceitos de leaves (abandonos) e traps (capturas). Um abandono é mais ou menos equivalente a um exceção e a captura é equivalente ao try/catch. Quando ocorre

um erro, o sistema gera um abandono, e a função é encerrada de imediato. O abandono é propagado para as funções chamadoras até que seja encontrado uma captura. Exemplo: void FooL () { TInt res; res = Func(); if (res) { User::Leave (); } }

// provoca o abandono

// este trecho não é executado no caso de um leave ocorrer

void MyL () { FooL (); // este trecho não é executado no caso de um abandono ocorrer } void Bar () { TInt error; TRAP(error, MyL() ); if (error) { // tratar o erro caso tenha ocorrido um abandono } // este trecho é executado normalmente }

O sufixo L no final das funções é uma convenção do Symbian OS para indicar que uma função pode gerar um abandono. A macro TRAP é a responsável por capturar o abandono da função, caso ocorra. Uma variação é usar a macro TRAPD para declarar automaticamente a variável que recebe o erro. void Bar() { TRAPD(error, MyL()); // error é declarado automaticamente como um TInt TInt res; TRAPD(error2, res = My2L() );

// maneira de receber o valor de retorno // res só tem valor válido caso não ocorra // abandono

}

Existem várias funções na classe User para gerar abandono. Na prática, elas geram um abandono com um código de erro específico. Por exemplo, User::LeaveNoMemory é o mesmo que User::Leave (KerrNoMemory). Existiam vários problemas nessa abordagem, pelo menos até a versão 9. Um deles é que quando ocorre um abandono, objetos alocados dinamicamente não são devolvidos. Outro problema é que os destrutores dos objetos na pilha também não são executados quando ocorre o abandono.

Para resolver o problema da liberação de recursos, o Symbian OS adota a chamada CleanupStack. Trata-se de uma pilha que pode guardar objetos que são desalocados quando ocorre um abandono. Por exemplo, uma função aloca um objeto dinamicamente e coloca nessa pilha. Depois chama uma função que gera um abandono. Então, antes da função retornar, a CleanupStack desaloca aquele objeto. Os objetos precisam ser desempilhados manualmente quando já for seguro. Alguns dos métodos mais usados da CleanupStack são PushL, Pop e PopAndDestroy. O método PushL é usado para se empilhar algum objeto, e pode também gerar abandonos caso a operação falhe. O método Pop desempilha objetos e o PopAndDestroy desempilha e desaloca objetos. Exemplo: void Abc() { CMyObject * obj = new CMyObj(); CleanupStack::PushL (obj); TInt * array = new TInt [10]; CleanupStack::PushL (array); dangerousFuncL ();

// -> pode gerar abandonos!

// se chegou até aqui então está ok // desempilha e desalocada os dois últimos empilhados // caso Pop() fosse usado, seria necessário chamar invocar delete para cada // ponteiro CleanupStack::PopAndDestroy (2); }

Existem outras funções para, por exemplo, desempilhar e devolver outros tipos de recursos (classes R). O sufixo LC serve para indicar que a função pode gerar um abandono e deixa o valor de retorno empilhado na CleanupStack, ao retornar. Por exemplo: void FooL () { TInt array = User::AllocLC (10);

// função do sistema, o array alocado // estará na CleanupStack ao retornar

func1L (); }

CleanupStack::PopAndDestroy ();

3.2.1. Importante Como os dispositivos móveis possuem pouca memória, é preciso ter mais cuidado com a alocação dinâmica de memória. Em outras palavras, é preciso checar com frequência se uma alocação foi bem sucedida. CMyObj * obj = new CMyObj ();

Se a alocação falhar, o ponteiro retornado será NULL. Nesse caso, é comum querer gerar um erro (abandono) e para isso existe uma variante do operador new: CMyObj * obj = new (Eleave) CMyObj ();

Essa variante irá gerar um abandono caso a operação falhe.

No Symbian OS, quando o operador new é executado, primeiro o sistema aloca a memória para o objeto e então o construtor é executado. Outro problema existente no Symbian OS é quando um abandono ocorre dentro de um construtor. Quando isso acontece, a execução do construtor é abortada, e o abandono é propagado. Dessa forma, a memória alocada para o objeto é perdida, pois não é possível guardar esse ponteiro (a chamada ao operador new não retorna nesse caso). No mecanismo oficial do C++, quando ocorre uma exceção no construtor, toda a memória alocada é devolvida, assim como são destruídos todos os objetos contidos na classe, que foram construídos corretamente. Isso não ocorre no Symbian OS. Na versão 9, os objetos construídos completamente são destruídos, mas a memória alocada para o objeto inicial fica perdida. Nas versões anteriores, nenhuma das duas coisas ocorre. Para resolver esse problema, é necessário recorrer à construção em duas fases. Assim, a receita é: ▪

Criar construtor que não tem funcionalidade;



Criar uma função ConstructL que faz a inicialização e alocação de recursos;



Fazer construtor e o ConstructL privados, de modo a não permitir o uso direto;



Criar uma função estática NewL que retorna um objeto construído.

Exemplo: CMyObj * CMyObj::NewL () { CMyObj * obj = new (Eleave) CMyObj(); CleanupStack::PushL (obj); obj->ConstructL (); CleanupStack::Pop (); }

return obj;

Uma consequência importante ao se usar esse método é que o destrutor não pode assumir que o objeto foi criado corretamente, ou seja, ele pode ser chamado no caso de algum abandono ser gerado durante esse processo. 3.2.2. Panics Um panic (pânico) representa um erro fatal da aplicação, irrecuperável. Quando esse tipo de evento ocorre, a aplicação é encerrada imediatamente. Um erro de pânico possui uma mensagem (texto) e um número que indica qual é o motivo de ter ocorrido o pânico. A API para gerar um pânico é: void User::Panic (const TDesc & aMsg, TInt reason);

3.2.3. Macros de depuração Assim como no C e C++ tradicionais, existem três macros para ASSERTs, que são ASSERT, ASSERT é uma forma compacta para

__ASSERT_DEBUG e __ASSERT_ALWAYS. __ASSERT_DEBUG. Exemplo de uso: ASSERT (condição)

As duas últimas macros aceitam dois parâmetros. O primeiro indica a expressão de teste. O segundo corresponde a uma função que será chamada caso o teste falhe. Exemplo de uso: __ASSERT_ALWAYS (value >= 0, User::Panic (KMyPanicText));

Nesse exemplo, caso a condição falhe um pânico será gerado. A diferença entre as duas últimas macros é que a __ASSERT_DEBUG é considerada somente no modo de depuração (debug) e a última é considerada em qualquer modo. Essas macros podem ser usadas com a inclusão do arquivo e32std.h. 3.3. Outras convenções As variáveis membros de classe são precedidas de “i”, enquanto os parâmetros de função são precedidos de “a”. class CMyClass { public: void foo (TInt aValue) { iField = aValue; } } private: TInt iField; };

As constantes são precididas de um “K” maiúsculo. const TInt KMyError = 1;

Os tipos enumerados começam com “T” e seus elementos com “E”. enum TColors { ERed, EGreen, EBlue };

As macros são escritas em maiúsculas, como __ASSERT_DEBUG. As funções podem terminar em L, LC ou D. Relembrando, L é para indicar que a função pode gerar um abandono, LC para indicar que pode gerar um abandono e deixa o valor de retorno empilhado na CleanupStack. O sufixo D indica que a função irá desalocar o objeto ao seu término. Por exemplo, a função CEikDialog::ExecuteLD irá exibir uma caixa de diálogo e depois irá desalocar o objeto associado a essa caixa de diálogo quando esta for fechada.

4. Descritores Descritores são a alternativa existente no Symbian OS para representar strings de caracteres. Ao contrário do C padrão, não existe um tipo char* para representar strings, nem uma classe string como no C++. Basicamente, os descritores são classes que encapsulam duas informações: um buffer de dados e o tamanho desse buffer. Esse valor é usado para detectar erros de buffer overrun (escrever fora da área de memória permitida). Quando isso acontece, o sistema sinaliza com um erro. Ao contrário do C, as strings não precisam terminar com NULL para sinalizar o final da string. Existem diversos tipos de descritores, que podem ser modificáveis ou não, e podem ser alocados na pilha ou na heap. Essas estruturas podem armazenar caracteres (strings) ou dados binários. Por padrão, os caracteres possuem 16 bits (Unicode), mas quando usados para armazenar dados binários, o descritor usará normalmente 8 bits, pelo fato de funcionar apenas como um buffer de bytes. Em termos gerais, os descritores são agrupados como: ▪

literais;



descritores baseados em buffer local;



descritores baseados em ponteiros;



descritores baseados na heap.

Todas as classes possuem versões para armazenar caracteres de 8 e 16 bits, como TBuf8 e TBuf16. Por padrão os descritores armazenam caracteres de 16 bits, ou seja, a classe TBuf é mapeada para TBuf16, por exemplo. 4.1. Base A classe base para descritores é TDesC, que representa um descritor não-modificável. Dessa forma, essa classe possui métodos somente para a leitura de informações sobre descritores. Dois desses métodos são Length, que retorna o número de caracteres armazenados e Size, que retorna o total de bytes ocupados por esses caracteres. A classe TDes representa um descritor modificável. Uma propriedade importante existente nessa classe é um campo que indica o tamanho máximo do buffer encapsulado pela classe, que pode ser acessada pelos métodos MaxLength e MaxSize. Essas classes não podem ser usadas diretamente, mas muitas vezes são usadas como parâmetros de funções no Symbian OS. 4.2. Literais Os literais são usados para se armazenar cadeias de caracteres (strings). São criados com as macros _LIT e _L. Exemplo: _LIT(KHello, “Hello ”); _LIT(KWorld, “World”);

Internamente, a macro _LIT cria um objeto da classe TLitC, que não tem relação com as classes base de descritores. Entretanto, é possível usá-la como se fosse um descritor não-modificável, ou seja, pode-se passar um literal para funções que aceitem um TDesC ou atribuí-la a um TDesC.

Já a macro _L cria um descritor temporário em cada parte onde é utilizada. Internamente, é expandida para um TPtrC (dinamicamente). Já com a outra macro, a expansão é em tempo de compilação. A macro _L foi depreciada e seu uso é recomendado apenas em testes no desenvolvimento. 4.3. Descritores baseados em buffer Esses tipos de descritores são representados pelas classes TBuf e TBufC. Esses descritores armazenam internamente o buffer para armazenar os dados. Exemplos de uso: _LIT(KHello, “Hello ”); _LIT(KWorld, “World”); TBuf str; str.Copy (KHello); str.Append (KWorld); // str será igual a “Hello World” TBuf b1 (KHello);

No exemplo, str representa um descritor modificável que tem tamanho máximo igual a 100. O equivalente em C seria declarar como char[100]. Mas ao contrário do C padrão, o valor 100 é usado para se detectar tentativas de escrever além da memória reservada para o descritor. Esses descritores também podem ser inicializados diretamente a partir de literais. Os descritores TBufC representam a versão não-modifiável, e são usados de maneira parecida. TBufC b1 (KWorld); TBufC b2; b2 = KHello;

O conteúdo desses descitores não pode ser modificado, mas pode ser substituído em uma única operação. Em ambas as versões, caso se tente armazenar uma string maior que o tamanho máximo, um erro será gerado. 4.4. Descritores baseados em pointeiros São representados pelas classes TPtr e TPtrC. Ao contrário dos descritores baseados em buffer, esses descritores armazenam um ponteiro que indica onde o buffer de dados está localizado. Esses descritores funcionam de maneira semelhante àqueles baseados em buffer. Para se construir um descritor desse tipo, é preciso criar um outro descritor (como TBuf) e associá-lo, de modo parecido como se faz com ponteiros. Exemplos: // 1 TBufC b1 (KWorld); TPtrC p1 (b2); // 2 TUint16 b2 [50]; TPtr p2 (b2, sizeof (b2) ); _LIT(KHello, “Hello ”); _LIT(KWorld, “World”); p2.Copy (KHello);

p2.Append (KWorld); // b2 terá “Hello World”

O primeiro exemplo cria um descritor não-modificável e o associa com um TBufC. O segundo exemplo aloca um array e o utiliza para criar um descritor modificável, para então modificar o buffer. Existem outros métodos na API para alterar o buffer que é referenciado pelo descritor (como Set ). 4.5. Descritores baseados na heap A classe HBufC representa esse tipo de descritor, e como a convenção de nomes indica, ele é não-modificável, a princípio. É possível modificá-lo usando métodos que retornam um objeto TDes a partir de um HBufC. Existem várias maneiras de se criar esse descritor. Algumas delas: // 1 HBufC* b1 = HBufC::NewL (100); // 2 TBuf buffer (...); HBufC* b2 = buffer.AllocL(); // 3 _LIT(KHello, “Hello ”); HBufC * b3 = KHello.AllocL();

A primeira versão aloca na heap uma sequência de 100 elementos de 16 bits, ou seja, 200 bytes. A segunda maneira aloca uma nova área de memória e copia o conteúdo da variável. A terceira alternativa faz a mesma coisa, só que a partir de um literal. A classe HBufC possui um método Des que retorna um objeto do tipo TPtr. Dessa forma, é possível alterar os dados desse descritor. Uma outra função útil é Realloc, que altera o tamanho original da área de memória alocada. 4.6. Outras notas Existem vários métodos úteis nas classes dos descritores, como por exemplo: ▪

Copiar dados a partir de um outro descritor. Existem variações que permitem, por exemplo, copiar os caracteres e já converter para maiúsculas ou minúsculas, por exemplo;



Comparação de strings, extração de substrings, apagar strings dentro de uma outra string;



Adicionar caracteres ao final de uma string;



Apagar uma sequência de caracteres existente em uma string;



Converter para uma string padrão C (com caracter terminador NULL);



Acessar caracteres do descritor como um array, usando colchetes, como é feito em strings do C.

4.7. Convertendo descritores entre 8 bits e 16 bits Para converter strings de 8 bits para 16 bits, pode ser usado o método Copy (TDesC8 & c) a partir do descritor de 16 bits. Exemplo: // declaração de um descritor de 8 bits _LIT8(name, "Name"); // declaração de um descritor de 16 bits TBuf16 name16; // copiando name16.Copy (name);

Entretanto, se o texto original estiver codificado em UTF-8, essa conversão poderá funcionar incorretamente. Para resolver esse problema, existe uma classe denominada CnvUtfConverter que possui diversos métodos estáticos para se realizar essas conversões. Para se converter de UFT-8 para Unicode, a classe possui este método: CnvUtfConverter::ConvertToUnicodeFromUtf8 (dest16, src8)

O parâmetro src8 representa o descritor de 8 bits e dest16 representa o descritor que receberá o texto convertido para Unicode. Analogamente, o método a seguir faz a conversão inversa: CnvUtfConverter::ConvertFromUnicodeToUtf8 (dest8, src16)

Para utilizar essa classe é necessário incluir o arquivo utf.h e utilizar a biblioteca charconv.lib.

5. Programação de GUI Relembrando, o framework de aplicação do Symbian OS segue o padrão MVC (“Model, View, Controller”). Por isso, uma aplicação simples possui os seguintes componentes: ▪

Application View: Representa a janela principal da aplicação, que contém todos os outros controles;



Application UI: Corresponde ao Controller do MVC, sendo responsável por criar a View e tratar eventos disparados pelos controles de interface;



Application Document: Responsável por armazenar e carregar dados de aplicação. É o objeto que cria o Application UI;



Application: Representa a aplicação principal, que cria o Documento. Esse componente também é responsável por determinar o UID da aplicação. O UID é um identificador único universal e é requerido para todas as aplicações.

O assistente do Carbide.vs cria uma aplicação funcional que contém essas classes. Nas próximas seções, a relação entre essas classes será investigada com mais detalhes. 5.1. Inicialização Ao contrário de aplicações C++ comuns, não existe uma função main que contém um laço principal de execução. Em vez disso, é necessário implementar duas funções: E32Main e NewApplication.

A função E32Main corresponde ao ponto de entrada da aplicação. A função é implementada como no trecho seguinte efetivamente iniciando a execução da aplicação. GLDEF_C TInt E32Main() { return EikStart::RunApplication (NewApplication); }

Já a função NewApplication deve retornar uma instância para a aplicação: LOCAL_C CApaApplication* NewApplication() { return new CMyApplication; }

Lembrando que essas funções são geradas automaticamente pela IDE, assim como as classes iniciais que usam o framework de aplicação. 5.2. Classes iniciais As principais responsabilidades da classe de aplicação são determinar o identificador único de aplicação (UID) e criar uma instância da classe de documento. A primeira tarefa é realizada pelo método AppDllUid, e a segunda pelo método CreateDocumentL. Exemplo: CApaDocument* CMyApplication::CreateDocumentL() { return (static_cast (CMyDocument::NewL (*this) ) ); } TUid CMyApplication::AppDllUid() const {

// um ID declarado em algum lugar return KUidBCApp; }

A classe de documento representa o modelo de dados da aplicação e é derivada a partir de CAknDocument. É preciso redefinir o método CreateAppUiL para poder instanciar o Application

UI. Também existem diversos métodos relacionados com operações de persistência. A classe Application UI deriva de CAknAppUi e precisa implementar o método ConstructL para inicializar-se internamente. A inicialização da classe base deve ser realizada chamando-se o método BaseConstructL. Esse método carrega o arquivo de recursos e constrói os elementos da interface gráfica. Os eventos originados da interface gráfica podem ser tratados através do método HandleCommandL. Os eventos são representados por valores inteiros e estão definidos em arquivos

.h do sistema. Essa classe também é responsável por criar os objetos do tipo View que a aplicação venha a utilizar. As classes que representam janelas de aplicação (Views) derivam de CCoeControl. Uma aplicação pode ter vários objetos do tipo View, se for o caso. A classe CCoeControl possui um método chamado Draw que é invocado quando é necessário redesenhar a janela. Esse método deve ser redefinido nas classes derivadas. 5.3. Tratamento de eventos e entrada Em aplicações S60, o usuário pode interagir através de menus ou pelo teclado. Para tratar esses eventos, podem ser usadas as classes de UI (menus e teclado) e as Views (teclado). Para outros eventos, a classe de UI possui vários métodos que deve ser redefinidos para se propiciar a resposta adequada. Entre os mais importantes, estão: ▪

HandleKeyEventL: Chamado quando o usuário pressiona uma tecla;



HandleForegroundEventL: Chamado quando a aplicação perde o foco;



HandleSystemEventL: Chamado para tratar eventos gerados pelo servidor de janelas;



HandleApplicationSpecificEventL: Tratar eventos personalizados, definidos pela

aplicação; ▪

HandleCommandL: Tratar comandos de menus, barras de ferramentas, teclas de atalho e

teclas de acesso rápido (“soft keys”). 5.3.1. Teclas Para responder a um evento de teclas, usando o framework de aplicações, existem duas alternativas: ▪

Usar o método HandleKeyEventL da classe de UI;



Usar o método OfferKeyEventL da classe de View.

A diferença entre as alternativas é que o método da classe de UI atua como um tratador de eventos global da aplicação, enquanto que a última trata apenas eventos que ocorram na View associada com a classe. Para que as classes de View recebam automaticamente o evento de teclas, é necessário que a classe de UI invoque seu método AddToStackL para para cada View. Exemplo para se tratar eventos de teclas a partir da classe de UI: TKeyResponse CMyAppUi::HandleKeyEventL (const TKeyEvent& aKeyEvent,

TEventCode aType)

{ switch (aKeyEvent.iCode) { case EKeyUpArrow: // seta “para cima” break; case EKeyDownArrow: // seta “para baixo” break; default: return EKeyWasNotConsumed; } return EKeyWasConsumed; }

Cada evento de tecla pode resultar em três tipos de eventos: EEventKeyDown, EEventKey e EEventKeyUp, nesta ordem. Esses valores correspondem ao parâmetro TEventCode. Quando uma tecla é pressionada, o evento EEventKeyDown é disparado. Nesse caso, o campo iCode do TKeyEvent será zero. Caso o usuário continue pressionando a tecla, eventos do tipo EEventKey serão disparados. Nesse caso o campo iCode conterá o código ASCII da tecla, possivelmente processado por um Front End Processor (FEP). Esses processadores podem fazer com que, por exemplo, um código correspondente à letra “b” seja gerado caso o usuário pressione a tecla “2” duas vezes. Nesse evento e no EEventKeyDown, o campo iScancode contém valores definidos pelo tipo enumerado TStdScanCode, para o caso de teclas que não tenham um código ASCII. Para teclas que tenham código ASCII (por exemplo, teclas de 1 a 9), o valor do campo será o código ASCII correspondente. Por exemplo, se a tecla pressionada foi “1”, então o valor retornado será 49. As constantes para as teclas estão definidas no arquivo e32keys.h. Finalmente, quando a tecla deixa de ser pressionada, o evento EEventKeyUp é invocado. O método HandleKeyEventL retorna uma valor que indica se o evento foi processado pelo método ou não. Caso o evento não tenha sido processado, o framework poderá passar o evento adiante para que outra classe o processe. Os eventos das teclas de acesso rápido são tratados pelo método HandleCommandL. Os comandos correspondes a cada tecla já estão pré-definidos nos arquivos de inclusão do sistema. O valor EKeyWasConsumed retornado pelo método indica que o evento já foi processado. Nesse caso, não será passado adiante para outro objeto tratar (que seria o caso se fosse retornado EKeyWasNotConsumed). 5.3.1.1. Detectando teclas para jogos

Para detectar eventos de teclas em jogos, também pode-se usar o evento HandleKeyEventL, mas com uma diferença. Nesses casos, em vez de se usar o parâmetro aKeyEvent.iCode, deve-se inspecionar o campo iScanCode dessa classe. Se for o caso, é possível usar um array para armazenar o estado das teclas e então consultá-lo depois: TKeyResponse CMyAppUi::HandleKeyEventL (const TKeyEvent& aKeyEvent, TEventCode aType) {

// TBool iKeyState [256]; // é um campo da classe TInt key = aKeyEvent.iScanCode; if (aType == EEventKeyDown) { iKeyState [key] = 1; } if (aType == EEventKeyUp) { iKeyState [key] = 0; } }

return EKeyWasConsumed;

O array iKeyState no exemplo é um campo da classe CMyAppUi, e é declarado como está no exemplo. Para consultar o estado de uma tecla, é preciso usar as constantes definidas pelo tipo enumerado TStdScanCode que está em e32keys.h. Um exemplo: void CMyAppUi::ProcessInput () { if (iKeyState [EStdKeyLeftArrow] ) // a seta esquerda foi pressionada }

É importante ter cuidado para não usar, por descuido, as constantes de TKeyCode (como EKeyLeftArrow), pois possivelmente irá resultar em um erro de acesso inválido (porque os valores são maiores que 256). 5.3.2. Comandos de menu Os comandos provenientes dos menus (e também de alguns outros tipos de componentes e das teclas de acesso rápido) são tratados pela Application UI através do método HandleCommandL. void CMyAppUi::HandleCommandL (TInt aCommand) { switch (aCommand) { case EMenuCmdAbrir: // tratar evento ... break; case EMenuCmdSalvar: // tratar evento ... break; case EEikCmdExit: Exit(); break; default: break; } }

As constantes são tipos enumerados definidos em arquivos de recurso. O comando EEikCmdExit representa uma ordem para encerrar a aplicação, sem discussões. Por exemplo, pode ocorrer quando o aparelho estiver sendo desligado. Esse comando é pré-definido no sistema e todas aplicações devem tratá-lo. Um comando relacionado é EAknCmdExit, que também é prédefinido. Esse é o comando que pode ser usado com menus “Sair da aplicação”, que podem pedir confirmação ao usuário, se for o caso. 5.4. Parte gráfica A classe CCoeControl representa a classe base para todos os controles de interface gráfica. Por xemplo, a classe de View é derivada de CCoeControl. Os objetos derivados de CCoeControl possuem uma área retangular que pode ser desenhada. O Symbian OS provê uma camada gráfica de sistema denominada GDI (Graphics Device Interface). A GDI define métodos para desenhar texto, formas geométricas e bitmaps. Os componentes de interface gráfica do Symbian OS dependem da GDI. Esses objetos são criados como no exemplo seguinte: void CHelloSymbianAppView::ConstructL (const TRect& aRect) { // criar a janela CreateWindowL(); // determinar o retângulo onde será possível desenhar SetRect (aRect);

}

// ativar a janela, ou seja, informar ao framework que a janela está // pronta para uso ActivateL();

Normalmente a classe de UI é a responsável por invocar indiretamente esse método no momento de criação da View. Em aplicações comuns, é comum que se passe para esse método o retângulo correspondente à área cliente da tela (área total da tela menos a área reservada para controles do sistema). Na classe de UI, o método responsável por obter essa informação é ClientRect. Entretanto, para o caso de querer que a janela ocupe a tela toda (como no caso de jogos), pode-se usar o método ApplicationRect. Para desenhar alguma coisa na área designada para o controle, é necessário redefinir o método Draw. Esse método recebe um parâmetro que representa o retângulo onde é possível desenhar. No Symbian OS, as operações de desenho são realizadas através de contextos gráficos e dispositivos gráficos. Um contexto gráfico representa uma configuração para certas propriedades de desenho, como canetas, cor atual e tipo de tracejado. A GDI possui uma classe base abstrata denominada CGraphicsContext, da qual todos os contextos gráficos são derivados. As operações de desenho propriamente ditas são realizadas pelos dispositivos gráficos, que são derivados da classe base CGraphicsDevice. 5.4.1. Desenhando O método Draw é invocado pelo framework quando é necessário atualizar a janela. Esse método não deve ser invocado diretamente. Para forçar a atualização da janela, é necessário utilizar os métodos DrawNow ou DrawDeferred. A diferença entre os métodos é que DrawDeferred não

causa o redesenho imediato, porque tem prioridade menor que outros eventos. Dessa forma, múltiplas chamadas a DrawDeferred podem ser acumuladas e resultar em menos atualizações efetivas. Isso permite que outros eventos do sistema (como eventos de entrada) possam ser processados normalmente. Para se desenhar qualquer coisa é necessário utilizar um contexto gráfico. O código a seguir realiza essa tarefa: void CHelloSymbianAppView::Draw(const TRect& aRect) const { CWindowGc& gc = SystemGc(); gc.Clear (aRect); }

O método SystemGc recupera um contexto gráfico que pode ser usado para se desenhar na janela. De acordo com as convenções do Symbian OS, o método Draw não pode gerar abandonos e é constante, ou seja, não pode modificar os dados da classe. 5.4.1.1. Texto

Antes de se desenhar texto, é necessário selecionar uma fonte para uso. O método UseFont dos contextos gráficos são responsáveis por determinar uma fonte: void UseFont (const CFont *aFont);

Existem algumas fontes pré-definidas, disponibilizadas pela classe CEikonEnv. As derivadas de CCoeControl possuem acesso a essa classe através do campo iCoeEnv. Os métodos que retornam as fontes são: NormalFont, DenseFont, AnnotationFont, TitleFont, LegendFont e SymbolFont. Exemplo de uso: gc.UseFont (iCoeEnv->NormalFont() );

Para se desenhar texto, os contextos gráficos definem a função DrawText que recebe como parâmetros um texto e a posição onde deve ser desenhado. O ponto de referência (0,0) localiza-se no canto superior esquerdo do retângulo de desenho. _LIT(texto, "Hello")); gc.DrawText (texto, TPoint(10,40));

O texto é desenhado na cor de caneta atual, que pode ser alterada através de SetPenColor. As cores são especificadas pelo tipo TRgb: // de cor vermelha gc.SetPenColor (TRgb (255, 0, 0) );

Existem diversas constantes para representar cores conhecidas, como KRgbBlack, KRgbWhite, KRgbRed, entre outras.

5.4.2. Formas geométricas Podem ser desenhadas primitivas como elipses, linhas, polígonos e retângulos. Outras formas podem ser adaptadas (como círculos) ajustando-se parâmetros das funções (de elipse, por exemplo). O contorno das formas geométricas é desenhado de acordo com a configuração atual da caneta (cor e estilo de traço). Para mudar o estilo de traço, pode ser usada o método SetPenStyle: // usar traço pontilhado

gc.SetPenStyle (CGraphicsContext::EDottedPen); // desenhar um retângulo TRect rect (0, 0, 10, 20); gc.DrawRect (rect);

O preenchimento das formas geométricas é realizado de acordo com as configurações de brush atuais. Por exemplo, para especificar que o preenchimento será sólido e de cor vermelha: gc.SetBrushStyle (CGraphicsContext::ESolidBrush); gc.SetBrushColor (KRgbRed);

5.5. Recursos O arquivo de recursos da aplicação contém definições para controles da interface gráfica, menus e strings de texto, entre outras. Esses arquivos possuem a extensão .rss e são compilados durante o processo de construção da aplicação, gerando um arquivo binário e um arquivo de inclusão .rsg. O arquivo possui o mesmo nome base da aplicação. A versão compilada dos recursos é carregada pelo sistema no momento em que a aplicação for executada. Assim, os recursos não estão embutidos no executável da aplicação. Algumas vantagens dessa abordagem: ▪

Possibilidade de se alterar a aparência da aplicação, sem precisar compilá-la novamente. Basta alterar o arquivo de recursos e compilá-lo em separado;



Facilita o processo de localização de aplicações, usando o mesmo procedimento citado no item anterior.

A sintaxe do arquivo de recursos é parecida com a da linguagem C++. O pré-processador do C++ é utilizado pelo compilador de recursos, de modo que é possível utilizar diretivas como #define, #include e #ifdef. Os estilos de comentários são os mesmos usados em C++. Os arquivos de recursos são formados por um conjunto de declarações, que utilizam algumas palavras reservadas, que são escritas sempre em caixa alta. As aplicações podem ter mais de um arquivo de recursos, mas cada arquivo deve possuir um nome único. Em termos de organização, os arquivos podem ser divididos em duas seções: cabeçalho e corpo. A parte do cabeçalho contém declarações que são usadas pelo compilador e pelo framework de aplicação. Na parte do corpo, os recursos propriamente ditos são definidos. A parte definida como cabeçalho possui os seguintes elementos: ▪

NAME: Usado para se determinar o identificador único do arquivo. Esse nome possui entre

um e quatro letras. Esse valor é usado pelo compilador para se identificar unicamente os recursos definidos no arquivo, entre os outros recursos presentes na aplicação. É importante evitar usar nomes que possam entrar em conflito com recursos do sistema, como EIK; ▪

Arquivos de inclusão: Definições de recursos existentes em outros arquivos (como recursos de sistema) podem ser importados através da diretiva #include;



Assinatura: Correspondente à declaração RSS_SIGNATURE, que é requerida pelo framework de aplicação. Na ausência dela, um erro será gerado. Na prática, o conteúdo dessa declaração é vazio;



Nome do documento: Determina o nome padrão para o arquivo de documentação da aplicação, através de um TBUF. Na prática, muitas aplicações não fazem uso desse recurso, mas o framework de aplicação requer essa declaração;



Recurso de informações sobre a aplicação: Trata-se de uma estrutura EIK_APP_INFO que especifica controles-padrão da aplicação, como o painel de status da aplicação e as “soft keys”.

Exemplo da parte de cabeçalho: // identificador NAME ABCD // arquivos de inclusão (outros recursos) #include #include #include #include // declarações requeridas RESOURCE RSS_SIGNATURE {} RESOURCE TBUF {buf = "MyApplication"} RESOURCE EIK_APP_INFO { status_pane = r_controls_status_pane; }

Na parte do corpo, os recursos usados pela aplicação são definidos, através da palavra reservada RESOURCE, na forma geral: RESOURCE TIPO_ESTRUTURA nome_recurso { // lista com os itens }

O identificador TIPO_ESTRUTURA representa alguma estrutura previamente declarada, possivelmente em outros arquivos de inclusão. A palavra reservada STRUCT é usada para se declarar essas estruturas. STRUCT TIPO_ESTRUTURA { WORD w = 0; LTEXT name; LONG num[]; }

Nesse exemplo, o valor padrão para o campo w será zero, no caso de o usuário não especificar nenhum. As variáveis também podem ser arrays, como é o caso da variável num. Normalmente essas declarações são armazenadas em arquivos .rh (“resource include”) para posterior reuso. A tabela 2 contém alguns tipos comuns usados nessas estruturas.

Tipo

Significado

BYTE

Inteiro 8 bits (com ou sem sinal)

WORD

Inteiro 16 bits (com ou sem sinal)

LONG

Inteiro 32 bits (com ou sem sinal)

LTEXT

String em Unicode com informação de tamanho

BUF

String em Unicode sem informação de tamanho

LLINK

Um ponteiro para um id de recurso, 16 bits

STRUCT

Criar uma estrutura aninhada

Tabela 2: Tipos básicos para recursos.

Um ponto importante é como se usa um recurso no código C++. Por exemplo, tem-se o seguinte recurso definido: RESOURCE TIPO_ESTRUTURA meu_resource { ... }

No código C++, esse recurso será acessível através da constante MEU_RESOURCE. Essa constante será criada no arquivo .rsg, resultante do processo de compilação dos recursos. O arquivo .rsg usualmente é gerado no diretório \epoc32\include. O recurso EIK_APP_INFO pode ser usado para se determinar quais são os recursos dos componentes a serem exibidos na interface. Essa estrutura é declarada desta forma: STRUCT EIK_APP_INFO { LLINK hotkeys=0; LLINK menubar=0; LLINK toolbar=0; LLINK toolband=0; LLINK cba=0; LLINK status_pane=0; }

Cada componente da estrutura representa um ponteiro para um recurso que pode ser definido pelo usuário. Por exemplo, é possível determinar como será o menu principal definindo-se um recurso MENU_BAR e atribuindo-o ao campo menubar. As teclas de acesso rápido podem ser definidas através do campo cba. Existem alguns recursos já definidos para representar pares de teclas. Exemplos: R_AVKON_SOFTKEYS_OPTIONS_BACK R_AVKON_SOFTKEYS_OPTIONS_CANCEL R_AVKON_SOFTKEYS_OK_CANCEL R_AVKON_SOFTKEYS_OK_BACK R_AVKON_SOFTKEYS_MENU_LIST R_AVKON_SOFTKEYS_SELECTION_LIST

5.5.1. Strings O texto a ser usado na interface gráfica deve ser definido em recursos, em vez de se definir diretamente no código-fonte. No S60 SDK 3rd Edition, o arquivo onde são definidas as strings é o arquivo .rls. Essa abordagem é usada para se facilitar o processo de localização da aplicação.

Uma string pode ser definida através de #define ou da diretiva rls_string (ambas funcionam). Exemplos: #define str_hello “Hello world!” rls_string string_teste “Start”

Para se usar várias línguas, podem ser definidos vários arquivos de localização. Por exemplo, suponha que a aplicação deva funcionar em Inglês (EN) ou Português (PT), e o arquivo de localização se chame MyApp.rls. Nesse caso, definem-se dois novos arquivos, MyApp_en.rls, MyApp_pt.rls, para cada língua. O arquivo principal escolheria a língua apropriada: #ifdef LANGUAGE_EN #include “MyApp_en.rls” #elif LANGUAGE_PT #include “MyApp_pr.rls” #endif

Em cada arquivo, seriam definidas as strings: // arquivo MyApp_en.rls #define str_hello “Hello” // arquivo MyApp_pt.rls #define str_hello “Oi”

Para que o compilador tome conhecimento das diversas línguas a serem usadas pela aplicação, é necessário inserir no arquivo .mmp (makefile) uma diretiva indicando essas opções. LANG EN PT

Assim, no processo de compilação a constante LANGUAGE_XXX será definida para cada item encontrado na diretiva LANG. Para se usar as strings localizáveis no código C++, é preciso criar um recurso TBUF e associar com o identificador definido no arquivo de localização (no caso de a string ser usada isoladamente). // arquivo .rss RESOURCE TBUF r_my_str { buf = str_hello; } // arquivo .cpp #include #include HBufC* textResource = StringLoader::LoadLC (R_MY_STR);

O arquivo .rsg, cujo nome é o nome base do projeto, contém as definições geradas para se acessar recursos (como R_MY_STR, no exemplo). Esse arquivo é gerado no processo de compilação. Uma outra maneira de se usar essas strings é como legenda dos componentes de interface. Nesse caso, ao se definir o recurso correspondente ao componente basta associar a legenda (caption) a uma string localizável. // estrutura hipotética com uma string e um inteiro RESOURCE MYSTRUCT my_struct { caption = str_hello; width = 10;

}

5.5.2. Definições de comandos Os arquivos de recursos também podem definir enumerações (enums). Esses tipos enumerados podem ser usados, por exemplo, para representar comandos de menus. De forma a possibilitar o reuso dessas definições entre arquivos de recurso e código C++, essas enumerações são escritas em um arquivo separado de extensão .hrh. #ifndef MENU_HRH #define MENU_HRH enum { EMenuCmdAbrir = 0, EMenuCmdSalvar, EMenuCmdSair, }; #endif

É importante observar que o processo de compilação não irá funcionar caso esse arquivo contenha outros tipos de declarações que não sejam tipos enumerados. 5.5.3. Menus Os menus são criados a partir de estruturas de recurso MENU_BAR, MENU_PANE, MENU_TITLE e MENU_ITEM. Por exemplo: RESOURCE MENU_BAR r_app_menubar { titles = { MENU_TITLE { menu_pane = r_app_menu; } }; }

A estrutura MENU_BAR representa uma barra de menus. O recurso pode ser efetivado como a barra de menus da aplicação caso seja atribuída ao campo menubar da EIK_APP_INFO. Os menus são definidos no campo titles. No exemplo, a barra de menus possui um menu e este é descrito pelo recurso r_app_menu. RESOURCE MENU_PANE r_app_menu { items = { MENU_ITEM { command = EMenuCmdAbrir; txt = str_menu_abrir; } MENU_ITEM { command = EMenuCmdSalvar; txt = “Salvar”; } MENU_ITEM

{ };

cascade = r_app_submenu; txt = str_submenu;

}

}

O exemplo anterior define um menu com três itens. O campo command do exemplo determina qual valor será usado para representar um comando vindo do menu. Esses valores são tipos enumerados, definidos no arquivo .hrh. O campo txt serve para determinar qual é a legenda do menu. No primeiro exemplo, a legenda é carregada a partir de uma string de localização, enquanto no segundo o texto é colocado diretamente no código. Essa abordagem não é recomendada, pois pode dificultar manutenção e localização. O último exemplo representa um submenu. O campo cascade indica qual é o menu a ser exibido no submenu, que também é definido a partir de uma estrutura MENU_PANE. 5.5.4. Mensagens de alerta e caixas de diálogo pré-definidas Embora a maioria dos componentes de interface precisem ser definidos como recursos nos arquivos .rss, existem alguns deles já pré-definidos no Symbian OS, disponibilizados pela classe CEikonEnv. Exemplos: static void InfoWinL (const TDesC& aFirstLine1, const TDesC& aSecondLine2); static TBool QueryWinL (const TDesC &aFirstLine, const TDesC &aSecondLine);

O primeiro exibe uma janela modal para informações, com duas linhas. Cada linha é especificada por cada parâmetro. O segundo método exibe uma janela modal com duas linhas de texto e opções Sim/Não. O valor de retorno corresponde à escolha do usuário, ETrue para “sim” e EFalse para “não”. Em ambas as funções, a janela não possui ícones. Existe uma outra versão de cada função que aceita identificadores de recursos em vez do texto. Exemplo de uso: _LIT (KLine1, “linha 1”); _LIT (KLine2, “linha 2”); CEikonEnv::Static()->InfoWinL (KLine1, KLine2);

Existe uma outra alternativa para se exibir mensagens de informações ao usuário (S60 1 st Edition), exemplificadas a seguir: #include CAknErrorNote* errorNote = new (ELeave) CAknErrorNote; errorNote->ExecuteLD (_L(“Erro”)); CAknInformationNote* infoNote = new (ELeave) CAknInformationNote; infoNote->ExecuteLD (_L(“Info”)); CAknWarningNote* warningNote = new (ELeave) CAknWarningNote; warningNote->ExecuteLD (_L(“Aviso”));

A diferença entre as classes é o ícone exibido na mensagem. Não é preciso liberar a memória usada pelo objeto, porque isso será feito automaticamente. As mensagens exibidas por essa classe desaparecem depois de um certo tempo.

5.6. Painéis Painéis são componentes de interface capazes de conter outros componentes. Usualmente, uma aplicação Série 60 possui alguns painéis, por exemplo: ▪

Painel de status;



Painel principal;



Painel com as “soft keys”.

A figura 6 ilustra um exemplo de aplicação simples.

Figura 6: Aplicação simples e seus painéis

O painel de status ocupa o topo da tela (janela da aplicação), e normalmente exibe informações sobre o estado da aplicação e do dispositivo. Esse painel contém cinco painéis: painel indicador de carga de bateria, painel indicador de sinal, painel de contexto, painel de título e painel de navegação. Os três últimos podem ser alterados pela aplicação. O painel de contexto exibe inicialmente o ícone da aplicação. Pode ser alterado para outro, se for o caso. O painel de título exibe o título da aplicação. Pode ser alterado para exibir outro título ou uma bitmap. O painel de navegação serve para exibir o estado atual da aplicação, e também ajudar em sua navegação. Inicialmente é vazio, mas pode ser preenchido com texto, bitmaps, indicadores ou componentes personalizados. O painel principal é onde a aplicação exibe os seus dados, e possui diagramação livre. O painel com “soft keys” exibe as legendas dessas teclas. Inicialmente, essas legendas são determinadas na estrutura EIK_APP_INFO, definida no arquivo de recursos.

6. Objetos ativos O Symbian OS é um sistema operacional multitarefa preemptivo. Isso quer dizer que o sistema permite o uso de várias threads por aplicação, e a execução das threads pode ser interrompida a qualquer momento pelo escalonador do sistema, para dar vez a uma outra thread. Entretanto, escalonar uma outra thread possui um custo. Esse procedimento, conhecido como troca de contexto, involve chamadas ao kernel do sistema e, possivelmente, gestão de memória (por exemplo, cópia dos dados do contexto de uma thread para ela retomar a execução). Em dispositivos como telefones celulares, que possuem recursos de memória e CPU mais restritos, esse custo pode ser grande. Dessa forma, o Symbian OS possui uma outra altenativa implementar aplicações multitarefa, de maneira cooperativa. Nesse modo, pode-se simular o comportamento de uma aplicação multithread utilizando-se apenas uma thread. Essa altenativa é representada pelos objetos ativos. Os objetos ativos são usados para simplificar a programação de tarefas que devem ser executadas de maneira assíncrona. Eles provêm maneiras de se fazer requisições assíncronas, detectar o término de alguma tarefa e processar os resultados. São indicados para programação orientada a eventos. A classe base para objetos ativos é CActive. É necessário criar uma classe derivada de CActive, definir o método que representará a chamada assíncrona, e implementar alguns métodos da classe base que são requeridos para o funcionamento dos objetos ativos. O escalonador ativo (active scheduler) é um componente do sistema responsável por gerir a execução dos objetos ativos. Cada thread possui uma instância desse componente. Basicamente, essa entidade possui um laço de execução que verifica se algum evento de algum objeto ativo termina. Nesse caso, o escalonador invoca um método do objeto ativo para avisá-lo desse evento. Esse método corresponde ao RunL de CActive, que as classes derivadas devem implementar (é requerido). O escalador volta ao modo de espera depois que objeto ativo executa a resposta ao evento. É importante observar que o modo de multitarefa executado pelos objetos ativos é cooperativo. Em outras palavras, o escalonador não pode interromper um objeto ativo para executar um outro objeto ativo, ou seja, uma vez que um método RunL é executado, outros eventos devem esperar que o primeiro se encerre, para que possam ser executados. Isso pode ser um problema no caso de um objeto ativo executar indefinidamente, o que deve ser evitado. O sistema pode interromper a thread que executa os objetos ativos para dar vez a uma outra, o que corresponde à gestão de processos global do sistema. Mas isso não interfere na execução dos objetos ativos, ou seja, o objeto ativo que estava sendo executado antes da interrupção continuará o seu processamento. 6.1. Roteiro para a criação de objetos ativos Para se criar um objeto ativo, é preciso criar uma classe derivada de CActive e implementar os métodos obrigatórios, que são RunL e DoCancel. O trecho de código seguinte define uma classe de objeto ativo. class CMyActive : public CActive { public: static CMyActive* NewL(); CMyActive(); ~CMyActive(); // Representa a chamada assíncrona, ou pedido para iniciar algum serviço, // por exemplo void InvokeService();

public: // declaradas em CActive // É executada quando a chamada assíncrona se completa (obrigatória) void RunL(); // Define o que fazer para cancelar uma chamada em andamento (obrigatória) void DoCancel(); // Chamada para se tratar leaves que venha a ocorrer em RunL (opcional) TInt RunError (TInt err); private: void ConstructL(); }

TRequestStatus iStatus;

1. Definir funções para se criar o objeto ativo (ConstructL, NewL, construtor); 2. Registrar o objeto ativo com o escalonador ativo; 3. Definir e implementar as funções que representam as chamadas assíncronas (uma ou várias); 4. Redefinir o RunL, que será executado ao término do processamento da chamada assíncrona. O nome talvez não seja dos melhores, já que esse método corresponde à resposta ao evento de terminar o processamento do serviço; 5. Redefinir o DoCancel, para poder cancelar uma chamada assíncrona que ainda está em andamento 6. Definir o destrutor, que deverá chamar o método Cancel da classe CActive, para que todas as chamadas possam ser canceladas caso o objeto seja destruído; 7. (Opcional) Tratar abandonos, redefinindo o método RunError. 6.1.1. Construção Assim como threads, os objetos ativos podem ter prioridades de execução. Essa propriedade pode ser usada no caso de existirem vários objetos ativos esperando a sua vez de serem executados. O tipo enumerado TPriority, da classe CActive, define os valores possíveis. enum TPriority { EPriorityIdle=-100, EPriorityLow=-20, EPriorityStandard=0, EPriorityUserInput=10, EPriorityHigh=20, };

O construtor da classe CActive requer que uma prioridade seja determinada, de modo que as classes derivadas devem satisfazer esse requisito. CMyActive::CMyActive() : CActive (CActive::EPriorityStandard) {}

O método NewL é responsável por construir uma instância desse objeto, ao chamar o ConstructL, como é feito em várias classes do Symbian OS. 6.1.2. Registro no escalonador O escalonador de objetos ativos é representado pela classe CActiveScheduler. Para se registrar os objetos com o escalonador, é necessário chamar o método estático Add dessa classe. CMyActive::CMyActive() : CActive (CActive::EPriorityStandard) { CActiveScheduler::Add (this); }

Essa chamada poderia ser realizada também no ConstructL ou no NewL. O objeto ativo será removido do escalonador quando for destruído, de modo que não é preciso fazer isso explicitamente. 6.1.3. Chamada assíncrona O método que representa a chamada assíncrona é responsável por iniciar a requisição relacionada com a chamada. O roteiro para se realizar a chamada é como este: 1. Antes de realizar a chamada, é importante verificar se já existe alguma pendente, porque na prática cada objeto ativo pode ter apenas uma requisição em andamento. Caso se tente submeter uma outra, algumas situações podem acontecer dependendo a implementação. Exemplos: gerar um erro do tipo panic, recusa em submeter outra requisição e cancelar a pendente e submeter uma nova; 2. Submeter a requisição, passando a variável iStatus para a função assíncrona. A função assíncrona irá usar essa variável para informar o status da requisição; 3. No caso da requisição ser bem-sucedida, o método SetActive deve ser chamado para indicar que existe uma requisição pendente. Aqui está um exemplo: _LIT (KErrorStr, "Requisição já realizada"); void CMyCActive::InvokeService () { // checando por requisições em andamento __ASSERT_ALWAYS (!IsActive(), User::Panic (KErrorStr, KErrInUse)); // chamada assíncrona RequestServiceProcessing (iStatus);

}

// informando que a requisição está pendente SetActive();

6.1.4. Resposta ao término do serviço Quando o processamento relacionado com a requisição terminar, o escalonador irá chamar o método RunL do objeto ativo. O status atual da requisição pode ser conferido através do campo iStatus, aquele mesmo que foi passado para a função assíncrona no momento da chamada. O objeto ativo pode, se for o caso, submeter outra requisição a ele mesmo ou a outro objeto. void CMyActive::RunL() {

if (iStatus == KErrNone) { // processamento normal do evento } else { // tratamento de algum erro ocorrido no processo } }

É importante lembrar que o método RunL não pode ser interrompido pelo escalonador ativo. Assim, caso essa função trave, os outros objetos ativos ficarão impedidos de responderem a eventos. 6.1.5. Cancelamento de requisições Os objetos ativos precisam implementar o método DoCancel, para interromper requisições pendentes. O sistema chama esse método através do Cancel de CActive. Esse método não pode gerar leaves, já que é potencialmente chamado no destrutor dos objetos ativos. Não é preciso verificar se existe alguma requisição pendente para poder continuar o procedimento de cancelamento, porque isso já é realizado pelos métodos que chamam DoCancel. Um ponto importante a se destacar é que quando um objeto ativo é cancelado, o método RunL não é executado. Dessa forma, o método DoCancel deve ser usado para se fazer liberação de recursos que ainda possam estar sendo usados, no caso de um cancelamento. 6.1.6. Tratamento de erros Os abandonos que ocorrerem em RunL podem ser tratadas pelo método RunError. parâmetro representa o código de erro da exceção.

O

O método deverá retornar KErrorNone caso o erro tenha sido tratado. Se outro valor for retornado, o escalador ativo será responsável por tratar o erro. 6.2. Possíveis erros Algumas vezes o erro (panic) “E32USER-CBASE 46” pode ocorrer quando se usa objetos ativos. Esse erro também é conhecido como “stray signal panic”. Essa exceção é gerada pelo escalonador ativo quando uma requisição termina mas não é possível identificar o objeto ativo responsável por tratá-la. Algumas possíveis causas para isso são: ▪

O objeto ativo não foi registrado no escalador através do CActiveScheduler::Add. Se isso não ocorrer, o escalonador não saberá que o objeto ativo existe;



O método SetActive não foi chamado após a chamada assíncrona. Se isso não ocorrer, o objeto aparecerá sempre como inativo para o escalonador;



A requisição foi completada mais de uma vez, seja por ter feito a requisição mais de uma vez ao objeto ativo (antes da primeira ter acabado), ou por algum outro erro de programação;



Cancelar uma requisição que não está associada com algum objeto ativo. Assim, quando o escalonador receber o evento de confirmação de cancelamento, ele não conseguirá identificar um objeto ativo associado com a requisição, e irá gerar o erro.

Outro erro que pode acontecer é o “E32USER-CBASE 42”, que indica uma tentativa de se fazer uma nova requisição pelo serviço quando ainda existe uma requisição pendente. Em outras palavras, o erro é gerado ao se invocar o método SetActive quando ainda existe uma requisição anterior não satisfeita (ou seja, ainda está ativo). 6.3. Exemplo Em vários casos as entidades interessadas em responder às requisições assíncronas não são os objetos ativos em si. Por exemplo, esta seção apresenta um exemplo de objeto ativo que representa um temporizador. Quando o tempo designado para o temporizador passar, o objeto ativo irá acionar um outro objeto interessado em tratar esse evento. Isso representa uma aplicação prática do padrão de projeto “Observador” [Gamma et. al 1995]. Nesse padrão, existe uma classe que gera eventos (observado) e uma ou mais classes que estão interessadas em saber quando o evento ocorre (observadores). No exemplo, o observado será o temporizador e os observadores serão definidos através de uma interface. class MTimerListener { public: virtual void OnTimer () = 0; };

Dessa forma, todas as classes interessadas em serem notificadas deverão implementar essa interface. A classe que representa o temporizador é definida a seguir. class CMyTimer : public CActive { public: CMyTimer (); ~CMyTimer(); static CMyTimer * NewL (MTimerListener & aListener); public: // função assíncrona void StartL (TTimeIntervalMicroSeconds32 aInterval); public: // requeridas pela API do CActive void RunL (); void DoCancel (); private: void ConstructL (MTimerListener & aListener);

};

private: RTimer iTimer; TRequestStatus iStatus; MTimerListener * iListener; TTimeIntervalMicroSeconds32 iInterval;

A implementação é como segue: CMyTimer:: CMyTimer () : CActive (EPriorityStandard)

{} void CMyTimer::ConstructL (MTimerListener & aListener) { // criar o temporizador User::LeaveIfError (iTimer.CreateLocal()); // guardar o interessado em tratar o evento iListener = & aListener; // registrar no escalonador CActiveScheduler::Add (this); } CMyTimer* CMyTimer::NewL (MTimerListener & aListener) { CMyTimer* obj = new (ELeave) CMyTimer; CleanupStack::PushL (obj); obj->ConstructL (aListener); CleanupStack::Pop(); } void CMyTimer::RunL () { // avisar ao interessado em receber o evento iListener->OnTimer (); // reiniciar o temporizador iTimer.After (iStatus, iInterval); // novamente, o objeto está ativo SetActive(); } void CMyTimer::DoCancel () { iTimer.Cancel (); } CMyTimer:: ~CMyTimer () { Cancel (); iTimer.Close (); } void CMyTimer::StartL (TTimeIntervalMicroSeconds32 aInterval) { // verifica se já existe algum timer __ASSERT_ALWAYS(!IsActive(), User::Panic (KErrorStr, KErrInUse)); // iniciar o temporizador iInterval = aInterval; iTimer.After (iStatus, aInterval); // indica que o objeto está ativo SetActive(); }

Implementar o temporizador utilizando-se diretamente o RTimer pode não ser muito prático. A API do Symbian OS possui a classe CTimer, que implementa um temporizador como um objeto ativo. Segue aqui a versão baseada no CTimer. class CCustomTimer : public CTimer { public: static CCustomTimer* NewL (MTimerListener & iListener); // responsável por inicializar o temporizador void StartL (TTimeIntervalMicroSeconds32 aInterval); // o mesmo método do CActive void RunL(); CCustomTimer(); private: void ConstructL (MTimerListener & iLstener); TTimeIntervalMicroSeconds32 MTimerListener * iListener;

iInterval;

};

Agora, a parte de implementação: CCustomTimer::CCustomTimer() : CTimer (EPriorityStandard) {} CCustomTimer* CCustomTimer::NewL (MTimerListener & iListener) { CCustomTimer* obj = new (ELeave) CCustomTimer; CleanupStack::PushL (obj); self->ConstructL (iListener); CleanupStack::Pop(); return obj; } void CCustomTimer::StartL (TTimeIntervalMicroSeconds32 { iInterval = aInterval;

aInterval)

// iniciar o temporizador, que já faz o objeto ficar como ativo CTimer::After (iInterval); } void CCustomTimer::ConstructL (MTimerListener & iListener) { iListener = & iListener; CActiveScheduler::Add (this); CTimer::ConstructL(); } void CCustomTimer::RunL() { iListener->OnTimer (); // reiniciar o temporizador

}

CTimer::After (iInterval);

7. Temporização para jogos Jogos para computador apresentam normalmente um laço de execução que realiza algumas tarefas que se enquadram numa dessas categorias: processamento de dados de entrada, atualização do estado da aplicação, e apresentação (desenho). No caso do Symbian OS, não é possível burlar a arquitetura do sistema para implementar o laço de execução dos jogos. Nesse caso, é preciso utilizar alguns dos recursos de temporização disponíveis no sistema. Entre esses recursos, encontram-se: RTimer, CPeriodic e CIdle. A classe RTimer representa um serviço de temporização assíncrono que deve ser implementado através de objetos ativos. Esse temporizador gera um evento dentro de um determinado período prédeterminado. A classe CTimer representa um objeto ativo que encapsula esse recurso. A classe CPeriodic produz eventos regulares, dentro de um período pré-determinado, e os trata através de uma função do tipo callback (TCallback). É implementada como um objeto ativo. A classe CIdle representa um objeto ativo que é executado quando não existe mais nenhum outro objeto com prioridade maior a ser executado. Nos experimentos realizados durante a confecção deste trabalho, a alternativa CIdle revelou-se melhor do que utilizar RTimer e CTimer. Quando essas últimas alternativa foram utilizadas, observou-se que a aplicação tornava-se bastante lenta, com interferência na parte de captação de eventos de entrada. Uma classe baseada em CIdle para temporização é declarada como a seguir: class CCustomTimer : public CBase { public: static CCustomTimer* NewL (MTimerListener & aListener); ~CCustomTimer (); void Start (); void Cancel (); private: // Callback do CIdle static TInt IdleCallBack (TAny* aPtr); // Delegar o evento do timer TInt DoCall(); private: CCustomTimer (MTimerListener & aListener) : iListener (& aListener) {} void ConstructL (); private:

};

MTimerListener * iListener; CIdle* iIdle;

A classe MTimerListener representa uma interface que possui apenas um método: OnTimer. Esse método não possui parâmetros nem valores de retorno, sendo invocado a cada vez que CIdle gera um evento. Essa interface deve ser implementada por classes interessadas em receber os eventos de tempo. O temporizador pode ser iniciado pelo método Start e interrompido pelo método Cancel. Internamente, o método IdleCallBack é usado para se delegar o evento para a classe interessada em tratá-lo. A implementação da classe é realizada como a seguir: void CCustomTimer::ConstructL () { iIdle = CIdle::NewL (CIdle::EPriorityIdle); } CCustomTimer::~CCustomTimer () { if (iIdle) { iIdle->Cancel (); delete iIdle; } } void CCustomTimer::Start () { if (!iIdle->IsActive () ) iIdle->Start (TCallBack (IdleCallBack, this) ); } void CCustomTimer::Cancel () { iIdle->Cancel(); } TInt CCustomTimer::IdleCallBack (TAny* aPtr) { return ((CCustomTimer*)aPtr)->DoCall(); } TInt CCustomTimer::DoCall() { iListener->OnTimer (); return ETrue; }

É importante notar que a classe não define um intervalo de tempo mínimo para os eventos. Dessa forma, os eventos serão disparados assim que estiverem disponíveis. Uma maneira para uniformizar a execução do laço de execução (por exemplo, a movimentação), é definir uma taxa de atualização alvo e acumular atualizações para satisfazer esse objetivo. Por exemplo: // taxa alvo: 30 frames por segundo // time step = tempo entre dois quadros, em microsegundos const TInt KTargetTimeStep = 1000000 / 30; void MyGame::OnTimer () { // processamento de atualização ProcessInput (); // compensar eventos de atualização atrasados TTime time;

time.HomeTime(); // tempo que passou desde a ultima vez TInt elapsed = I64LOW (time.MicroSecondsFrom (iStartTime).Int64()); // armazenar o tempo total iTotalTime += elapsed; // calcular quantas atualizações devem ser realizadas para o jogo não // ficar atrasado (divisão inteira) elapsed = iTotalTime / KTargetTimeStep; for (TInt i = 0; i < elapsed; ++i) Update (); // descontar do tempo total o número de atualizações efetivamente realizado iTotalTime -= elapsed * KTargetTimeStep; iStartTime = time.Int64();

}

// desenho Render ();

As variáveis iStartTime e iTotalUpdates são membros privados dessa classe. Utilizando essa abordagem, é possível evitar a utilização do tempo nos cálculos de atualização, colaborando para o determinismo da aplicação.

8. OpenGL ES Esta seção apresenta uma introdução ao uso de OpenGL no ambiente Symbian OS. Assume-se que o leitor possui alguma familiaridade com a biblioteca original. 8.1. Introdução OpenGL (Open Graphics Library) é uma biblioteca (API) para Computação Gráfica 3D e 2D que foi desenvolvida inicialmente por Silicon Graphics Inc. (SGI). Atualmente, é administrada pela ARB (Architecture Review Board). A ARB é uma entidade que congrega vários representantes da indústria, como SGI, Intel, NVIDIA e Sun Microsystems. OpenGL foi projetado para ser utilizado em várias plataformas e sistemas operacionais, como Mac OS, Unix, Windows e consoles (PlayStation 2), entre outros. A biblioteca original é baseada em C, mas existem versões para linguagens de programação diversas como Java e Python, entre várias outras. Trata-se de uma biblioteca de baixo nível, que oferece algumas primitivas básicas (como pontos, linhas, triângulos, quadriláteros e polígonos), operações para manipulação do sistema de coordenadas e operações com matrizes (translação, rotação e escala) e efeitos como mapeamento de textura, entre outros comandos. OpenGL ES [Munshi e Leech 2007] é uma versão reduzida da OpenGL original, destinada a dispositivos embarcados. A versão 1.0 da OpenGL ES é baseada na versão 1.3 da OpenGL, enquanto a versão 1.1 (atual) é baseada na versão 1.5 da OpenGL. Atualmente, o grupo Khronos [Khronos Group 2007] é o responsável pelas especificações da OpenGL ES. Devido à grande variedade de dispositivos móveis disponíveis que poderiam utilizar a OpenGL ES, foi definido um conceito denominado de profile (perfil). Um perfil define um subconjunto de funcionalidades de alguma versão completa da OpenGL, acrescido de propriedades específicas para a versão embarcada, como extensões. Atualmente, existem dois perfis disponíveis: ▪

Common Profile: Destinado a dispositivos de entretenimento em massa como telefones celulares, PDAs e consoles, entre outros;



Common-Lite Profile: Destinado a dispositivos em que a confiabilidade e segurança são os fatores mais importantes da aplicação. Dessa forma, define um subconjunto mínimo necessário para que seja viável construir esse tipo de aplicação.

Assim como em todas as versões da OpenGL, a versão embarcada também define uma camada que é específica ao sistema onde é executada. Essa camada é definida como EGL, e define uma API comum para ser usada com OpenGL ES. A implementação dessa camada, entretanto, depende do fabricante do hardware. 8.2. Arquivos necessários Os arquivos necessários para se desenvolver aplicações para OpenGL ES já estão presentes a partir do SDK S60 2nd Edition FP2. A versão presente nesses SDKs é a 1.0. A versão 1.1 está presente a partir do SDK S60 3rd Edition FP1. Os arquivos de inclusão são: #include #include

Os arquivos de ligação com a DLL necessário é: libgles_cm.lib

ws32.lib

O primeiro arquivo corresponde ao perfil Common Profile. O perfil Common-Lite Profile não está disponível nos dispositivos S60. O segundo arquivo é específico para o Symbian OS, correspondente ao servidor de janelas. Quando a aplicação é construída para o telefone, os arquivos de ligação possuem extensão .dso no Carbide.c++.

8.3. Roteiro – Inicializando a OpenGL ES Para se inicializar a OpenGL ES, é necessário utilizar a EGL e algumas de suas estruturas. O roteiro é o seguinte: 1. Recuperar a tela padrão do dispositivo; 2. Inicializar a OpenGL ES; 3. Escolher uma configuração para a OpenGL ES; 4. Criar o contexto OpenGL ES; 5. Criar uma superfície de desenho; 6. Fazer com o que o contexto criado em 4 seja o corrente. O trecho de código a seguir declara uma classe que irá implementar esse roteiro: #include #include #include "GLES/egl.h" #include "GLES/gl.h" class CGLRender: public CBase { public: // método para se criar uma instância da classe static CGLRender* NewL (RWindow & window); public: // destrutor ~CGLRender (); // double buffering void SwapBuffers (); private: // construtor CGLRender (RWindow& window); // segunda parte do construtor em duas fases void ConstructL(); private: RWindow EGLDisplay

iWindow; iEglDisplay;

};

EGLConfig EGLContext EGLSurface

iEglConfig; iEglContext; iEglSurface;

O campo iEglDisplay representa o dispositivo físico do dispositivo. O campo iEglConfig representa a configuração da OpenGL ES. O campo iEglContext representa o contexto da OpenGL ES, e iEglSurface representa uma superfície de desenho. 8.3.1. Recuperar a tela padrão do dispositivo Para se recuperar a tela, pode ser usado este trecho de código: // recuperar dispositivo físico padrão iEglDisplay = eglGetDisplay (EGL_DEFAULT_DISPLAY); if (iEglDisplay == EGL_NO_DISPLAY) User::Panic (_L("Unable to find a

suitable EGLDisplay"), 0);

A constante EGL_DEFAULT_DISPLAY é usada para se referenciar a tela padrão do aparelho (muitas vezes só existe uma). Caso tenha ocorrido algum erro, a função irá retornar EGL_NO_DISPLAY. 8.3.2. Inicializar a OpenGL ES Após recuperar a tela do dispositivo, é necessário inicializar a OpenGL ES através deste comando: // inicialização do OpenGL ES if (!eglInitialize (iEglDisplay, 0, 0) ) User::Panic (_L("Unable to initialize EGL"), 0);

Além do objeto correspondente à tela, os outros dois parâmetros aceitos pela função servem para que a EGL informa qual é a sua versão. Por exemplo: EGLint major, minor; eglInitialize (iEglDisplay, & major, &minor);

Se a versão fosse 1.0, major conteria 1 e minor conteria 0. 8.3.3. Escolher uma configuração para a OpenGL ES A seguir, é necessário escolher a configuração mínima que a aplicação deve usar. Para isso, é necessário usar este comando: EGLint numConfigs; if (!eglChooseConfig (iEglDisplay, attribList, &iEglConfig, 1, &numConfigs) ) User::Panic (_L("Unable to choose EGL config"), 0);

O parâmetro attribList representa uma lista de atributos que a aplicação deseja usar. O parâmetro iEglConfig corresponde a uma lista de configurações disponíveis no sistema, que são compatíveis com a descrição desejada. O tamanho máximo dessa lista é determinado no quarto parâmetro (que no caso pede para retornar apenas 1). O número total de configurações que são compatíveis com a descrição desejada é retornado através do parâmetro numConfigs.

A lista de atributos desejados para a aplicação é definida como um array de pares de inteiros, na forma de (atributo, valor). A EGL define constantes para todos os atributos possíveis de serem escolhidos. Por exemplo, dois deles que são comumente determinados são o modo de cores e o tamanho do z-buffer: // lista de atributos desejados para a configuração da OpenGL EGLint attribList [] = { EGL_BUFFER_SIZE, 0, EGL_DEPTH_SIZE, 15, EGL_NONE }; // determinação da quantidade de bits por pixel, dependendo do modo // de vídeo do aparelho switch (iWindow.DisplayMode() ) { case EColor4K: attribList [1] = 12; break; case EColor64K: attribList [1] = 16; break;

}

case EColor16M: attribList [1] = 24; break; default: attribList [1] = 32;

A constante EGL_BUFFER_SIZE corresponde ao modo de cores (RGBA, em bits) e EGL_DEPTH_SIZE corresponde ao tamanho do z-buffer (bits). A lista de atributos deve ser terminada com EGL_NONE. A parte final do exemplo consulta o modo de vídeo, a partir de um objeto RWindow. 8.3.4. Criar o contexto OpenGL ES O contexto da OpenGL ES é criado desta forma: // criar contexto OpenGL iEglContext = eglCreateContext (iEglDisplay, iEglConfig, EGL_NO_CONTEXT, 0); if (iEglContext == 0) User::Panic (_L("Unable to create EGL context"), 0);

O terceiro parâmetro serve para indicar um outro contexto a ser usado para se compartilhar objetos de textura, se desejado. No caso, EGL_NO_CONTEXT é usado para se indicar que não há compartilhamento. O último parâmetro representa uma lista de atributos a ser mapeada para o contexto. No exemplo, não existe essa lista. 8.3.5. Ativar o contexto criado Para que o contexto possa ser usado efetivamente, é necessário torná-lo ativo. Na OpenGL ES, apenas um contexto pode estar ativo por vez. O trecho a seguir realiza essa operação: // usar o contexto eglMakeCurrent (iEglDisplay, iEglSurface, iEglSurface, iEglContext);

A partir desse momento, todas as operações realizadas com OpenGL ES afetam esse contexto. 8.3.6. Finalização da OpenGL ES Após utilizar a OpenGL ES, é necessário liberar os recursos. Essa parte é muito importante, especialmente por se tratar de dispositivos com poucos recursos. CGLRender::~CGLRender() { eglMakeCurrent (iEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface (iEglDisplay, iEglSurface); eglDestroyContext (iEglDisplay, iEglContext); eglTerminate (iEglDisplay); }

A primeira linha faz com que nenhum contexto esteja ativo. Depois, são destruídas a superfície de desenho e o contexto. A última linha finaliza a OpenGL ES. 8.4. Algumas diferenças entre OpenGL e OpenGL ES Devido às limitações dos dispositivos embarcados, existem uma série de diferenças entre a OpenGL ES e a OpenGL original. Em muitos casos, foram removidas maneiras redundantes de se fazer uma mesma operação. A OpenGL ES utiliza double-buffering por padrão. Para trocar os buffers, deve-se proceder como no trecho seguinte: void CGLRender::SwapBuffers () { eglSwapBuffers (iEglDisplay, iEglSurface); }

Outro exemplo, em OpenGL ES não é possível utilizar o modo imediato para desenhar. Ou seja, código como este a seguir, não existe em OpenGL ES: glBegin (GL_TRIANGLES); glVertex3f (0,1,0); glVertex3f (-1,0,0); glVertex3f (1,0,0); glEnd();

Para se desenhar primitivas em OpenGL ES, é necessário utilizar vertex arrays. Dessa forma, a seguir têm-se um exemplo de como desenhar um triângulo em OpenGL ES: const GLbyte KVertices []= { 0,1,0, -1,0,0, 1,0,0 }; ... glEnableClientState (GL_VERTEX_ARRAY); glVertexPointer (3, GL_BYTE , 0, KVertices); glDrawArrays (GL_TRIANGLES, 0, 3);

Devido ao fato de muitos dispositivos não possuírem FPUs (floating point units) nem hardware gráfico dedicado, os perfis da OpenGL ES definem funções que aceitam parâmetros como pontofixo. Em particular, o perfil Common-Lite Profile não aceita ponto-flutuante. Matemática em ponto-fixo é uma técnica para se simular números de ponto flutuante utilizando números inteiros. Assim, define-se uma faixa de bits para representar a parte inteira, e outra faixa restante é usada para representar a parte fracionária. A OpenGL ES trabalha com ponto-fixo 16:16, ou seja, um inteiro de 32 bits é dividido em 16 bits para parte inteira e 16 para a parte fracionária. Devido também à essa limitação do ponto-flutuante, o tipo double foi removido da OpenGL ES. Exemplo de uso da função de translação em ponto-fixo: glTranslatex (20 Application()->AppFullName(); // construir um objeto que vai inspecionar o texto para extrair a // letra do disco onde está a aplicação TParse parse; parse.Set (appFullName, NULL, NULL); // extrair a informação sobre o disco (Drive() ) e inserí-la no // atalho privado // ex: d:\private\ed054200 path.Insert (0, parse.Drive()); #endif }

return path;

9.1.1.1. Copiando arquivos de dados para o emulador

Para se utilizar arquivos externos na aplicação é necessário primeiro copiá-los para o diretório privado da aplicação, no emulador. A seção PRJ_EXPORTS do arquivo bld.inf pode ser usada para se especificar arquivos que devem ser copiados para algum diretório do emulador. Exemplo: PRJ_EXPORTS "..\data\alien1.png"

"\Epoc32\winscw\c\private\eb4e3a98\alien1.png"

Esse exemplo informa que o arquivo “alien1.png” localizado no diretório “data” do projeto deve ser copiado para o diretório especificado. Entretanto, o Carbide.vs atual (2.0.1) ignora essa instrução. Um possível paliativo para essa questão é compilar o projeto pela linha de comando (que faz a cópia corretamente) quando for necessário copiar os arquivos. Segue o roteiro:

Primeiramente, achar o diretório onde ficam os arquivos bld.inf e .mmp do projeto. A partir da linha de comando, submeter: bldmake bldfiles

Será criado um arquivo chamado abld.bat no mesmo diretório, que será usado no restante do processo. A seguir é necessário submeter o comando: abld build winscw udeb

Esse comando gera o executável para o emulador, no modo winscw que corresponde ao Carbide. O valor udeb indica que é a versão Unicode em modo de depuração. Essa solução evita a cópia manual dos arquivos. 9.1.1.2. Copiando arquivos de dados para o dispositivo

O arquivo .pkg do projeto especifica uma série de informações para a criação do arquivo de instalação .sis. Na parte final do arquivo, depois das outras instruções específicas, podem ser informados os arquivos a serem instalados no dispositivo. Entretanto, devido às normas de segurança do Symbian OS (versão 9 em diante), existem restrições quanto à localização desses arquivos no dispositivo. Essa política tem o nome de “data caging” [Nokia 2006]. As regras são: ▪

Código executável (ex: myapp.exe): São instalados no diretório \sys\bin do disco escolhido pelo usuário. Apenas aplicações consideradas confiáveis pelo Symbian OS têm acesso a esse local;



Arquivos de recurso da interface gráfica (ex: myapp.rsc) e ícones da aplicação: São instalados no diretório \resource\apps do drive escolhido pelo usuário. Esse local é de livre acesso mas somente para leitura;



Arquivos



Arquivos privados da aplicação são armazenados no diretório \private\. Esse id corresponde à UI3 da aplicação, definida no campo SECUREID do arquivo .mmp.



Outros locais que não sejam esses são públicos para leitura e escrita.

de registro (ex: myapp_reg.rsc) \private\10003a3f\import\apps\;

são

instalados

no

diretório

A seguir, um exemplo de como especificar um arquivo para ser copiado para o dispositivo: "..\data\floor.png" - "!:\private\ed054200\floor.png"

Nesse exemplo, o arquivo floor.png deverá ser copiado para o diretório privado da aplicação. O ponto de exclamação funciona como um curinga, permitindo que o usuário escolha um disco onde o arquivo será instalado. 9.1.2. Modo console Para executar uma aplicação em modo console no SDK S60 3 rd Edition, é preciso editar o arquivo epoc.ini em \epoc32\data e acrescentar a linha “textshell 1”, que fará com que o emulador entre em modo console.

9.1.3. Como trocar o UID do projeto Primeiro, crie um UID. Se o nome base do projeto for “MyProject”, o UID antigo poderá estar nos seguintes arquivos: MyProject.hrh, MyProject.mmp e MyProject.uid.cpp. Uma vez encontradas todas as ocorrências para o UID antigo, basta trocá-las pelo novo UID. No Carbide.vs isso pode ser realizado através do menu Edit > Find and replace > Replace in files. 9.1.4. Códigos de erro No endereço http://www.newlc.com/article.php3?id_article=117 pode ser encontrada uma lista extensa dos erros possíveis em Symbian (a constante que representa o erro, seu valor e significado). 9.1.5. Como manter acesa a luz da tela A luz da tela dos dispositivos permanece acesa enquanto o usuário está interagindo com o aparelho, e apaga-se após um certo tempo de inatividade. Em alguns casos pode ser desejável manter a luz da tela acessa (por exemplo, jogos, livros eletrônicos, etc). Dessa forma, para manter sempre acessa a tela é preciso chamar periodicamente esta função: User::ResetInactivityTime ();

A temporização pode ser realizada com o uso de objetos ativos ou uma outra classe que implemente essa funcionalidade.

10. Referências Babin, S. 2006. Developing Software for Symbian OS. Chichester: John Wiley & Sons. Coulton, P. e Edwards, R. 2007. S60 Programming: A Tutorial Guide. Chichester: John Wiley & Sons. Edwards, L. e Barker, R. 2004: Developing Series 60 Applications: A Guide for Symbian OS C++ Developers. Reading: Addison Wesley. Gamma, E., Helm, R., Johnson, R. e Vlissides, J., 1995. Elements of Reusable Object Oriented Software. Reading: Addison-Wesley. Khronos Group 2007. The Khronos Group [online]. Disponível em: http://www.khronos.org [Acessado em 27/06/2007]. Munshi, A. (Ed) e Leech, J. (Ed). 2007. OpenGL ES Common/Common-Lite Profile Specification, version 1.1.10 (Full Specification) [online] Disponível em: http://www.khronos.org/registry/gles/specs/1.1/es_full_spec.1.1.10.pdf [Acessado em 27/06/2007]. Nokia, 2007a. Forum Nokia – Carbide Development Tools for Sumbian OS C++ [online]. Disponível em: http://www.forum.nokia.com/main/resources/tools_and_sdks/carbide_cpp/index.html [Acessado em 26/06/2007]. Nokia, 2006. S60 3rd Edition SDK for Symbian OS, Maintenance Release. Documentação. Nokia, 2007b. Carbide.vs [online]. Disponível http://www.forum.nokia.com/info/sw.nokia.com/id/9124f1f0-1fc0-405e-9c60facf7b337702/Carbide_vs_2_0_1.html [Acessado em 26/06/2007].

em:

Symbian 2007. Symbian OS – The open mobile operation system [online]. Disponível em : http://www.symbian.com [Acessado em 22/03/2007]. Wikipedia, 2007a. EPOC (computing) [online]. Disponível http://en.wikipedia.org/wiki/EPOC_%28computing%29 [Acessado em 27/06/2007].

em:

Wikipedia, 2007b. Model-view-controller [online]. Disponível http://en.wikipedia.org/wiki/Model-view-controller [Acessado em 27/06/2007].

em: