Laura Lemay e Charles L. Perkins
Até este ponto no livro tem aprendido especificamente sobre a programação na língua de Java e com as bibliotecas de classe de Java. Por isso este livro chama-se Ensinam-se Java, no fim de tudo. Hoje vou digressionar um bocado e falar sobre métodos nativos e bibliotecas.
Os métodos nativos e as bibliotecas são os bits do código executável que se escrevem do modo tradicional: escrevem-se em uma língua como C ou C ++ e compilam-se em uma biblioteca específica para a plataforma como um DLL ou uma biblioteca compartilhada. Dentro das suas aplicações de Java pode ganhar o acesso às funções dentro daquelas bibliotecas, permitir-lhe criar uma espécie de Java híbrido e natural codifica a aplicação. Embora usar métodos nativos possa dar-lhe alguns extra benefícios que Java não fornece (como execução mais rápida ou acesso a um grande corpo do código existente), há desvantagens significantes na utilização de métodos nativos também.
Novo termo |
Os métodos nativos e as bibliotecas nativas são os bits do código executável específico para a plataforma (escrito em línguas como C ou C ++) contido em bibliotecas ou DLLs. Pode criar uma aplicação de Java híbrida que tem o acesso àquelas bibliotecas nativas. |
A lição de hoje cobre vários tópicos que se relacionam com métodos nativos, inclusive o seguinte:
Observar |
Na lição de hoje aprenderá as técnicas básicas para escrever métodos nativos na versão atual de Java. Para Java 1,1 lançamento, o Sol publicará novas linhas guias para escrever que a métodos nativos ajudem a assegurar-se que as implementações nativas trabalharão entre versões diferentes do tempo de execução de Java. Estas linhas guias serão além da técnica que aprenderá na lição de hoje e se baseará nas habilidades que aprende aqui. |
Antes que entre nos detalhes de âmago da questão de criar métodos nativos, deve estar sabendo primeiro o que os métodos nativos lhe dão - e o que levam. Embora os métodos nativos forneçam algumas vantagens, aquelas vantagens podem não parecer demasiado excitantes quando examinado na luz de desvantagens de métodos nativos. Esta seção descreve ambos.
Há várias razões que poderia querer considerar métodos nativos usam nos seus próprios programas Java. De muito as melhores destas razões são
O primeiro, e de muito o melhor, a razão de implementar métodos nativos consiste em porque tem de utilizar uma capacidade especial do seu sistema de computador ou sistema operacional que a biblioteca de classe de Java já não o provê. Tais capacidades incluem inter-relacionar em novos dispositivos periféricos ou cartões de plug-in, acesso de um tipo diferente da ligação em rede ou utilização de uma característica única, mas valiosa do seu determinado sistema operacional. Dois exemplos mais concretos adquirem a entrada de áudio em tempo real de um microfone ou usando o hardware "de acelerador" 3D em uma biblioteca 3D. Nenhum destes se fornece você pelo ambiente de Java atual, portanto deve implementá-los do lado de fora de Java, em alguma outra língua (atualmente C ou qualquer língua que pode ligar-se com C).
O segundo, e muitas vezes ilusório, a razão de usar métodos nativos é velocidade. O argumento fez-se isto porque bytecode interpretado é terrivelmente lento em comparação com como o código rapidamente nativo corre (e é muito mais lento, até 25 vezes mais devagar), o código de Java é impróprio para a maior parte de aplicações. Em muitos casos isto simplesmente não é verdade, ou pode ser capaz de extrair um montante justo da velocidade fora do seu programa Java sem recorrer a métodos nativos (como exploraremos no maior detalhe depois na lição de hoje). Se, contudo, a sua aplicação de Java usar cálculos muito intensivos pelo processador (por exemplo, número interpretação que tritura ou 3D), usando métodos nativos para as funções críticas da velocidade e Java das interfaces mais gerais cria um sistema com mais benefícios do que um sistema escrito no código nativo puro ou em Java puro. De fato, a biblioteca de classe de Java usa esta aproximação para muitas classes de sistema críticas para levantar o nível total da eficiência no sistema. Como um usuário do ambiente de Java, até não sabe (ou vê) qualquer efeito de lado disto (exceto, possivelmente, algumas classes ou métodos que são final que não poderia ser de outra maneira).
A terceira razão de usar classes nativas consiste em se o seu projeto tiver um grande corpo do código existente (o que chamou o código de legado, que pode ser centenas de linhas do código escrito e mantido por outras pessoas durante os anos). Como um bom programador de Java e advogado quereria naturalmente, transportar este grande corpo em diagonal do código a Java. Contudo, as considerações da vida real de tempo e recursos muitas vezes não permitem esta opção. Os métodos nativos permitem-lhe escrever uma interface única para aquele código por Java e link no código existente como é necessário.
Depois de ler as vantagens de usar métodos nativos, pode fazer-se tudo para pular para a seção em como usá-los e omitir esta seção. Não faça. Para cada boa coisa os métodos de natural provêem no seu código de Java, levam um benefício que Java fornece em primeiro lugar: a capacidade do seu código para correr em qualquer lugar e facilmente transportar-se em diagonal de um sistema ao outro.
Usando Java puro, uma aplicação ou applet podem dirigir-se em qualquer ambiente de Java no mundo carregando dele via a Web ou carregando simplesmente o arquivo de classe naquele sistema. Qualquer nova arquitetura criada - ou os novos sistemas operacionais escritos - são inaplicáveis ao seu código. Tudo do qual precisa é que (muito pequeno) Java Máquina Virtual (ou um browser que tem um interior ele) estar disponível, e pode correr em qualquer lugar, a qualquer hora agora e no futuro.
Com Java híbrido e programa de método nativo, contudo, abandonou aquela capacidade de transversal plataforma. Em primeiro lugar, os programas Java que usam métodos nativos não podem ser applets. Período. Por razões de segurança, o applets não pode carregar o código nativo. Assim, se usa métodos nativos, acaba de retirar o número enorme de usuários em World Wide Web do seu mercado.
Mesmo se somente estiver criando uma aplicação de Java, contudo, e não destinar o seu código a dirigir-se na Web, a utilização de métodos nativos também nega a capacidade do seu programa de correr em qualquer plataforma. O código nativo é, por definição, plataforma específica. O código nativo deve existir na plataforma o seu programa Java continua para aquele programa para trabalhar. Para o seu programa para trabalhar em plataformas diferentes, terá de transportar o seu código nativo em diagonal àquela plataforma específica - que pode não ser uma tarefa trivial. E como os novos sistemas ou as novas versões de sistemas operacionais aparecem, deveria atualizar ou re-lançar novas versões daquele código nativo de cada sistema. Escrevê-lo uma vez dirigi-lo em todo lugar a vantagem de Java deixa de existir quando usa métodos nativos.
Vamos digressionar por um momento e conversação sobre o conceito da velocidade e a eficiência de programas Java - ou os supostos necessitam de disso, que pode dirigi-lo à utilização de código nativo nos seus programas Java.
Java bytecode adquiriu a reputação de ser extraordinariamente lento para correr em comparação com o código executável nativo. E, examinando as marcas de referência, Java bytecode de fato é muito mais lento até 25 vezes mais devagar. Contudo, isto não necessariamente faz um programa Java insuportável para usar. applets simples ou as aplicações que confiam em elementos de interface de usuário parecerão correr tão rápido como os seus equivalentes nativos. Os cliques de botão são tão rápidos em Java como estão no código nativo, e os seus usuários são muito lentos em comparação com computadores modernos. Só é em caso de operações muito intensivas pelo processador que Java começa a subir curto em comparação com o código nativo.
Pelo menos, incomodar-se sobre a velocidade dos seus programas Java antes que lhes escreva muitas vezes é um rathole que pode distrai-lo das questões maiores. Nesta seção olharei tanto para aquelas questões maiores como nas soluções que podem fazer os seus programas Java correr mais rápido.
Quando projeta o seu programa, toda a sua energia e a criatividade devem dirigir-se para o desenho de um jogo apertado, conciso, mínimo de classes e métodos que são maximamente gerais, abstratos, e reutilizáveis. (Se pensar que é fácil, olhe em torno durante alguns anos e ver como mal a maior parte de software é.) Se passa a maioria do seu tempo de programação em pensamento e reconsideração destas metas fundamentais e como realizá-los, prepara-se para o futuro-a futuro onde o software se reúne como necessário de pequenos componentes que nadam em um mar de facilidades de rede, e cada um pode escrever um componente visto por milhões (e reutilizado nos seus programas) durante minutos. Se, em vez disso, passar a sua energia que se incomoda com a velocidade o seu software correrá agora mesmo em algum computador, o seu trabalho será inaplicável depois dos 18 para 36 meses tomará o hardware para ser bastante rápido para esconder aquela falta de eficiência menor no seu programa.
Portanto deve ignorar a eficiência completamente? Claro que não! Alguns grandes algoritmos do negócio das Ciências da Computação com solução de problemas difíceis ou "impossíveis" em períodos de tempo razoáveis - e escrita dos seus programas descuidadamente podem levar a resultados notavelmente lentos. O descuido, contudo, pode levar como facilmente a resultados incorretos, frágeis, ou não-reutilizáveis. Se corrigir todos estes últimos problemas primeiro, o software resultante será limpo, refletirá naturalmente a estrutura do problema que tenta resolver, e assim será acessível "à aceleração" depois.
Observar |
Sempre há os casos onde deve ser fanático sobre a eficiência em muitas partes do grupo de classes. A própria biblioteca de classe de Java é tal caso, como é algo que deve correr em tempo real atrás de alguma aplicação do mundo real crítica (como voo de um avião). Tais aplicações são raras, de qualquer modo. Falando de uma nova espécie da programação que deve emergir logo, Bill Joy gosta de invocar quatro S de Java: pequeno, simples, seguro, e seguro. A "sensação" da própria língua de Java estimula a perseguição da claridade e a redução da complexidade. A perseguição intensa da eficiência, que aumenta a complexidade e reduz a claridade, é antitética a estas metas. |
Uma vez que constrói uma fundação sólida, depura as suas classes e o seu programa (ou applet) trabalhos como você gostaria dele a, então é tempo de começar a otimizá-lo.
A primeira coisa a ter em mente sobre a velocidade de execução de Java consiste em que muitas pessoas trabalham na fixação dele. E o mais prometedor destes avanços técnicos é o compilador just-in-time (JIT).
Os compiladores just-in-time traduzem Java bytecode para o código de máquina nativo durante o voo quando o bytecode corre. Dependendo do que bom o compilador JIT é, muitas vezes pode vir muito perto de velocidades de execução nativas fora de um programa Java padrão - sem precisar de usar o código nativo e sem precisar de fazer qualquer modificação ao seu programa Java - somente trabalha.
A desvantagem, contudo, é que para adquirir a velocidade aumentam o seu programa Java deve dirigir-se em uma plataforma que manda instalar um compilador JIT. No momento desta escrita, os compiladores de JIT ainda são novos. Muitas companhias trabalham em compiladores JIT, contudo, e a maioria deles têm o trabalho de versões ou embrulhado em com instrumentos de desenvolvimento portanto pode experimentar com o seu poder. O Navegador da Web de Internet Explorer de Microsoft, por exemplo, manda incorporar um compilador JIT nele. (Aprenderá mais sobre os compiladores JIT disponíveis esperam-se no Dia 22, "Instrumentos de Programação de Java".) espera-se que os compiladores de JIT fiquem muito mais populares e comuns sobre o próximo ano.
Além da confiança em tecnologia de JIT de acelerar os seus programas Java, há truques de otimização normalmente simples que pode fazer para fazer os seus programas correr mais eficientemente. O seu ambiente de desenvolvimento pode até fornecer um proenchedor, que lhe diz onde os mais lentos ou mais frequentemente correm as porções do seu programa ocorrem. Mesmo se não tiver um proenchedor, muitas vezes pode usar instrumentos de depuração para encontrar os gargalos nos seus programas e começar a fazer modificações visadas das suas classes.
Os livros inteiros escreveram-se para otimizar vários bits do código em qualquer língua, e podem descrevê-lo muito melhor do que podemos. Mas há alguns truques simples pode aspirar ao primeiro passo.
Em primeiro lugar, identifique os poucos métodos cruciais que tomam a maior parte do tempo (quase sempre há somente alguns, e muitas vezes somente um, que tomam a maioria de tempo do seu programa). Se aqueles métodos contêm laços, examinam os laços interiores para ver se eles
Se notar que uma cadeia longa de, por exemplo, quatro ou mais chamadas de método são necessárias para conseguir um código de método de destino, e este caminho de execução está em uma das seções críticas do programa, pode "entrar em curto-circuito" diretamente àquele método de destino no método o mais alto. Isto pode necessitar a soma que uma nova variável de exemplo para referir o objeto daquele método chama diretamente. Isto bastante muitas vezes viola layering ou constrangimentos encapsulation. Esta violação e qualquer complexidade acrescentada, são o preço que paga pela eficiência.
Se, depois de todos estes truques, o seu código de Java ainda for somente demasiado lento, é tempo de considerar métodos nativos usam. Nesta seção aprenderá os passos que deve tomar para escrever o seu código de Java para que use métodos nativos, como escrever que ao código nativo implemente aqueles métodos nativos, e como compilar e ligar tudo isso em conjunto portanto trabalha. Isto implica quatro passos básicos:
Observar |
Esta discussão - e, de fato, o próprio JDK - supõe que esteja escrevendo o seu código nativo em C e C ++. Outros ambientes de desenvolvimento de Java podem apoiar outras línguas. |
O primeiro passo à implementação de métodos nativos deve decidir que métodos nos quais as classes do seu programa Java serão nativas. O mapeamento entre Java e bibliotecas nativas é por métodos (funções), projetando então o seu código de Java e guardando a pista da qual os métodos são nativos é o primeiro passo mais importante.
Para declarar que um método será nativo dentro do seu código de Java, acrescenta o modificador de native àquela assinatura de método, como isto:
public native void goNative(int x, int y);
Observar |
O modificador de native pode usar-se com muitos dos modificadores sobre os quais aprendeu no Dia 15, "Modificadores, Controle de acesso e Desenho de Classe", inclusive public, private, protected, final, e assim por diante. Não pode usar-se com abstract porque os métodos abstratos não têm definições, natural ou de outra maneira. |
Também observe que o método nativo no seu código de Java não tem corpo de método. Como isto é um método nativo, a sua implementação vai se fornecer pelo código nativo, não por Java. Somente acrescente um ponto-e-vírgula ao fim da linha.
Outra modificação que terá de fazer ao seu código de Java deve carregar explicitamente a biblioteca nativa que conterá o código nativo destes métodos. Para fazer isto, acrescenta o seguinte código de boilerplate à sua classe de Java:
static { System.loadLibrary("libmynativelibrary.so"); }
Este bit do código, chamado um initializer estático, usa-se para dirigir o código só uma vez quando a classe se carrega primeiro no sistema. Neste caso, o initializer estático realiza o método de System.loadLibrary() para carregar na sua biblioteca nativa como a própria classe se está carregando. Se a biblioteca nativa não conseguir carregar por alguma razão, o carregamento da classe de Java falha também, garantindo que nenhuma versão de meioorganização da classe pode criar-se alguma vez.
Pode escolher qualquer nome que quer para a sua biblioteca aqui nativa usamos a convenção UNIX que as bibliotecas começam com a palavra lib e terminam na extensão .so. Para sistemas de Windows, as bibliotecas tipicamente terminam na extensão .DLL.
Também pode usar o método de System.load() para carregar as suas bibliotecas nativas. A diferença é que o argumento único a load() é o nome de caminho completo à sua biblioteca nativa, ao passo que o argumento a loadLibrary() é somente o nome de biblioteca, e Java usa o modo padrão de encontrar que bibliotecas do seu sistema localizam aquela biblioteca (normalmente variáveis de ambiente como LD_LIBRARY_PATH). O último é mais flexível e de uso geral, portanto recomendou que o use em vez disso.
E isto é tudo que tem de fazer no seu código de Java para criar métodos nativos e bibliotecas. As subclasses de qualquer classe que contém os seus novos métodos de native ainda podem ignorar eles e estes novos (Java) que os métodos se chamam para exemplos das novas subclasses (como esperaria).
A listagem 20.1 mostra um exemplo de um programa Java chamado SimpleFile que se escreveu para usar métodos nativos. Este programa poderia usar-se em uma versão do ambiente de Java que não fornece a entrada de arquivo ou a produção (entrada-saída). Como a entrada-saída de arquivo é tipicamente dependente do sistema, os métodos nativos devem usar-se para implementar aquelas operações.
Observar |
Este exemplo combina versões simplificadas de duas classes de biblioteca de Java reais, java.io.File e java.io.RandomAccessFile. |
A listagem 20.1. SimpleFile, um programa Java que usa métodos nativos.
1: public class SimpleFile { 2: public static final char separatorChar = '>'; 3: protected String path; 4: protected int fd; 5: 6: public SimpleFile(String s) { 7: path = s; 8: } 9: 10: public String getFileName() { 11: int index = path.lastIndexOf(separatorChar); 12: 13: return (index < 0) ? path : path.substring(index + 1); 14: } 15: 16: public String getPath() { 17: return path; 18: } 19: 20: public native boolean open(); 21: public native void close(); 22: public native int read(byte[] buffer, int length); 23: public native int write(byte[] buffer, int length); 24: 25: static { 26: System.loadLibrary("simple"); // runs when class first loaded 27: } 28: }
A primeira coisa que nota sobre a implementação de SimpleFile consiste em que não notável os primeiros dois terços do seu código de Java são! Olha como qualquer outra classe, com uma classe e uma variável de exemplo, um construtor e duas implementações de método normais (getFileName() e getPath()). Então, em linhas 20 para 23, há quatro declarações de método de native, que são declarações de método somente normais com o bloco de código substituído por um ponto-e-vírgula e o modificador native acrescentado. Estes são os métodos que tem de implementar no código de C depois.
Finalmente, observe a chamada a System.loadLibrary() na linha 26, que carrega uma biblioteca nativa chamada simple. (Violamos intencionalmente padrões denominam a biblioteca aqui para fazer este exemplo mais simples.)
Observar |
O separatorChar excepcional ('>') usa-se simplesmente para demonstrar a que uma implementação poderia parecer em algum computador estranho cujo sistema de arquivos não usou nenhuma das convenções de separador do caminho mais comuns. Os primeiros computadores de Xerox usaram '>' como um separador, e vários sistemas de computador existentes ainda usam separadores estranhos hoje, portanto isto não é tudo que forçado. |
Depois que escreve a parte nativa do seu programa Java, os objetos de SimpleFile podem criar-se e usar-se do modo habitual:
SimpleFile f = new SimpleFile(">some>path>and>fileName"); f.open(); f.read(...); f.write(...); f.close();
O segundo passo à implementação de código nativo deve gerar um jogo especial de cabeçada e arquivos de toco do uso pelo seu C ou C ++ arquivos que implementam aqueles métodos nativos. Para gerar estes cabeçada e arquivos de toco, usa o programa javah, que é parte do JDK (chamou JavaH em Mac JDK).
Em primeiro lugar, precisará de compilar o seu programa Java como ia qualquer outro programa Java, usando o compilador de Java.
Para gerar as cabeçadas precisa para os seus métodos nativos, usa o programa javah. Para a classe de SimpleFile enumerada na seção prévia, use um do seguinte:
Para gerar arquivos de cabeçada de uma classe, use o programa javah com o nome do arquivo de classe, menos a extensão de .class. Por exemplo, para gerar o arquivo de cabeçada da classe de SimpleFile, use esta linha de comando:
javah SimpleFile
Gerar o arquivo de cabeçada da classe de SimpleFile, arrasto-e-baixa o arquivo de classe para o ícone JavaH.
O arquivo SimpleFile.h vai se criar no mesmo diretório que o arquivo de SimpleFile.class.
Observe que se a classe tenha dado a javah é dentro de um pacote, javah preespera o nome completo do pacote ao nome de arquivo de cabeçada (e aos nomes de estrutura gera dentro daquele arquivo) com todos os pontos (.) substituído por sublinha (_). Se SimpleFile se tivesse contido em um pacote hipotético chamado acme.widgets.files, javah teria gerado um arquivo de cabeçada denominado acme_widgets_files_SimpleFile.h, e vários nomes dentro dele iriam se ter renomeado em uma maneira semelhante.
A listagem 20.2 mostra o arquivo de cabeçada que se gera por javah.
A listagem 20.2. SimpleFile.h (um arquivo de cabeçada).
1: #include <native.h> 2: /* Header for class SimpleFile */ 3: 4: #ifndef _Included_SimpleFile 5: #define _Included_SimpleFile 6: struct Hjava_lang_String; 7: 8: typedef struct ClassSimpleFile { 9: #define SimpleFile_separatorChar 62L 10: struct Hjava_lang_String *path; 11: long fd; 12: } ClassSimpleFile; 13: HandleTo(SimpleFile); 14: 15: #ifdef __cplusplus 16: extern "C" { 17: #endif 18: extern /*boolean*/ long SimpleFile_open(struct HSimpleFile *); 19: extern void SimpleFile_close(struct HSimpleFile *); 20: extern long SimpleFile_read(struct HSimpleFile *,HArrayOfByte *,long); 21: extern long SimpleFile_write(struct HSimpleFile *,HArrayOfByte *,long); 22: #ifdef __cplusplus 23: } 24: #endif 25: #endif
Há algumas coisas a observar sobre este arquivo de cabeçada. Em primeiro lugar, observe o struct ClassSimpleFile, que contém variáveis que põem as variáveis de exemplo em paralelo dentro da sua classe. Em segundo lugar, observe as assinaturas de método no fim do arquivo; estas são as definições de função que usará no seu C ou C ++ arquivo para implementar os métodos nativos reais no código de Java.
Para "dirigir a interferência" entre o mundo de Java de objetos, tabelas, e outros construtos de alto nível e o mundo de nível mais baixo de C, precisa de tocos, que traduzem argumentos e valores de retorno entre Java e C.
Os tocos são as partes do código "de cola" que atam em conjunto Java e C. Os tocos traduzem argumentos e valores e convertem vários construtos em cada língua a algo que pode entender-se no outro.
Os tocos podem gerar-se automaticamente por javah, como cabeçadas. Não há muito tem de saber sobre o arquivo de toco, somente que tem de compilar-se e ligar-se com o código de C que escreve permitir-lhe inter-relacionar propriamente com Java.
Para criar arquivos de toco, também usa o programa javah:
Use o programa javah com a opção de -stubs de criar o arquivo de toco:
javah -stubs SimpleFile
O arquivo SimpleFile.c vai se gerar no mesmo diretório que o arquivo de classe.
O arquivo de toco gerou-se ao mesmo tempo criou o arquivo de cabeçada.
A listagem 20.3 mostra o resultado do arquivo de toco da classe de SimpleFile.
A listagem 20.3. SimpleFile.c (um arquivo de toco).
1:/* DO NOT EDIT THIS FILE - it is machine generated */ 2:#include <StubPreamble.h> 3: 4:/* Stubs for class SimpleFile */ 5:/* SYMBOL: "SimpleFile/open()Z", Java_SimpleFile_open_stub */ 6:__declspec(dllexport) stack_item *Java_SimpleFile_open_stub(stack_item *_P_, 7: struct execenv *_EE_) { 8: extern long SimpleFile_open(void *); 9: _P_[0].i = (SimpleFile_open(_P_[0].p) ? TRUE : FALSE); 10: return _P_ + 1; 11:} 12:/* SYMBOL: "SimpleFile/close()V", Java_SimpleFile_close_stub */ 13:__declspec(dllexport) stack_item *Java_SimpleFile_close_stub(stack_item *_P_, 14: struct execenv *_EE_) { 15: extern void SimpleFile_close(void *); 16: (void) SimpleFile_close(_P_[0].p); 17: return _P_; 18:} 19:/* SYMBOL: "SimpleFile/read([BI)I", Java_SimpleFile_read_stub */ 20:__declspec(dllexport) stack_item *Java_SimpleFile_read_stub(stack_item *_P_, 21: struct execenv *_EE_) { 22: extern long SimpleFile_read(void *,void *,long); 23: _P_[0].i = SimpleFile_read(_P_[0].p,((_P_[1].p)),((_P_[2].i))); 24: return _P_ + 1; 25:} 26:/* SYMBOL: "SimpleFile/write([BI)I", Java_SimpleFile_write_stub */ 27:__declspec(dllexport) stack_item *Java_SimpleFile_write_stub(stack_item *_P_, 28: struct execenv *_EE_) { 29: extern long SimpleFile_write(void *,void *,long); 30: _P_[0].i = SimpleFile_write(_P_[0].p,((_P_[1].p)),((_P_[2].i))); 31: return _P_ + 1; 32:}
O passo último e o mais difícil, devem escrever o código de C dos seus métodos nativos.
O arquivo de cabeçada gerado por javah dá-lhe os protótipos das funções que tem de implementar para fazer o seu código nativo completo. Então escreve que a algum C codifique o que implementa aquelas funções e fornece as facilidades nativas de que a sua classe de Java precisa (em caso de SimpleFile, algumas rotinas de entrada-saída de arquivo de baixo nível).
Quererá incluir o seu arquivo de cabeçada como parte do include inicial s para a sua implementação nativa:
#include <SimpleFile.h>
Observar |
Esta descrição encobre muito do que poderia querer fazer para implementar de fato aqueles métodos. Especialmente, Java fornece várias funções de serviço que ajudam os seus métodos nativos a interagir com métodos de Java e classes e ajudar o mapa C e C ++ construtos aos seus equivalentes de Java. Descreveremos várias destas funções mais tarde na lição de hoje na seção "Instrumentos e Técnicas para Escrever Implementações nativas". |
A listagem 20.4 mostra a implementação nativa dos métodos da classe de SimpleFile.
A listagem 20.4. SimpleFileNative.c, uma implementação C de um método nativo de SimpleFile.
1: #include "SimpleFile.h" /* for unhand(), among other things */ 2: 3: #include <sys/param.h> /* for MAXPATHLEN */ 4: #include <fcntl.h> /* for O_RDWR and O_CREAT */ 5: 6: #define LOCAL_PATH_SEPARATOR '/' /* UNIX */ 7: 8: static void fixSeparators(char *p) { 9: for (; *p != '\0'; ++p) 10: if (*p == SimpleFile_separatorChar) 11: *p = LOCAL_PATH_SEPARATOR; 12: } 13: 14: long SimpleFile_open(struct HSimpleFile *this) { 15: int fd; 16: char buffer[MAXPATHLEN]; 17: 18: javaString2CString(unhand(this)->path, buffer, sizeof(buffer)); 19: fixSeparators(buffer); 20: if ((fd = open(buffer, O_RDWR | O_CREAT, 0664)) < 0) /* UNIX open */ 21: return(FALSE); /* or, SignalError() could "throw" an exception */ 22: unhand(this)->fd = fd; /* save fd in the Java world */ 23: return(TRUE); 24: } 25: 26: void SimpleFile_close(struct HSimpleFile *this) { 27: close(unhand(this)->fd); 28: unhand(this)->fd = -1; 29: } 30: 31: long SimpleFile_read(struct HSimpleFile *this, 32: HArrayOfByte *buffer, _ long count) { 33: char *data = unhand(buffer)->body; /* get array data */ 34: int len = obj_length(buffer); /* get array length */ 35: int numBytes = (len < count ? len : count); 36: 37: if ((numBytes = read(unhand(this)->fd, data, numBytes)) == 0) 38: return(-1); 39: return(numBytes); /* the number of bytes actually read */ 40: } 41: 42: long SimpleFile_write(struct HSimpleFile *this, 43: HArrayOfByte *buffer,_ long count) { 44: char *data = unhand(buffer)->body; 45: int len = obj_length(buffer); 46: 47: return(write(unhand(this)->fd, data, (len < count ? len : count))); 48: }
O passo final deve compilar todos os arquivos de .c, inclusive o arquivo de toco e os seus arquivos de método nativos. Use o seu compilador C favorito para compilar e ligar aqueles dois arquivos em uma biblioteca compartilhada (um DLL no Windows). Em alguns sistemas, precisaria de especificar bandeiras de compilação especiais que significam "fazem-no relocável e dinamicamente linkable". (Aquelas bandeiras, se se necessitarem, podem variar do sistema ao sistema; confira com a sua documentação de compilador de detalhes.)
Observar |
Se tiver várias classes com métodos de native, pode incluir todos os seus tocos no mesmo arquivo de .c, se você gostar. Naturalmente poderia querer denominá-lo algo mais, como Stubs.c, neste caso. |
A biblioteca resultante deve ser o mesmo nome que deu no seu arquivo de classe de Java original como o argumento a System.loadLibrary(). Na classe de SimpleFile, aquela biblioteca chamou-se libmynativelibrary.so. Quererá denominar a biblioteca que o mesmo nome e a instala onde quer que o seu determinado sistema precise de bibliotecas a instalar-se.
Com todo o código escrito e compilado e instalado no lugar certo, tudo que tem de fazer dirige-se o seu programa Java usando Java bytecode intérprete. Quando a classe de Java se carregar, também tentará carregar a biblioteca nativa automaticamente; se tiver sucesso deve ser capaz de usar as classes na sua classe de Java, e dirigirão transparentemente as bibliotecas nativas como são necessários.
Se adquire um erro que a biblioteca não se encontrou, o problema mais provável consiste em que não manda fundar o seu ambiente corretamente ou que não instalou a sua biblioteca no lugar certo.
Os arquivos de DLL localizam-se segundo o algoritmo de Windows padrão: o diretório a aplicação localizou-se em, o diretório atual, o diretório System no Windows 95 (System32 em NT), o diretório System em NT, o diretório de Windows, e logo diretórios enumerados na variável de ambiente de PATH.
Os sistemas de UNIX usam a variável de ambiente LD_LIBRARY_PATH para procurar bibliotecas. Esta variável de ambiente deve incluir os lugares padrão as bibliotecas compartilhadas guardam-se, bem como o diretório atual (.). Depois que LD_LIBRARY_PATH estabeleceu-se, Java será capaz de encontrar a sua biblioteca.
As bibliotecas compartilhadas de Java devem guardar-se na pasta System Folder: Extensions:JavaSoft Folder. Em vez de copiar a sua biblioteca nativa lá, também somente pode criar um pseudônimo à sua biblioteca nativa e pô-lo naquela pasta.
Escrevendo o código de implementações nativas, um jogo inteiro de macros úteis e funções está disponível para fazer o mapa entre C e C ++ e Java, e para acessar estruturas de tempo de execução de Java. (Vários deles usaram-se em SimpleFileNative.c.) Além disso, há várias regras e as técnicas para tratar com a conversão entre Java e C. Nesta seção aprenderá sobre aquelas funções e técnicas a fazer a escrita do seu código nativo mais fácil.
Os nomes de Java de classes, métodos e variáveis podem usar-se métodos nativos interiores com as seguintes modificações (se necessário):
Os objetos de Java passam-se a métodos nativos usando maçanetas a estruturas. O nome de maçaneta é o nome do objeto (inclusive qualquer nome de pacote), preesperado com a carta H. Deste modo, por exemplo, a classe SimpleFile teria uma maçaneta chamada HSimpleFile. A classe que java.lang.String converteria em Hjava_lang.String (se lembram, nomes de classe manda incluir nomes de pacote, com sublinha para separá-los).
As maçanetas são referências para estruturas que representam objetos de Java. Cada maçaneta tem o mesmo nome que a classe que refere, com a carta H preesperado.
Cada função nativa automaticamente passa-se pelo menos uma maçaneta na sua lista de parâmetro. Isto chama-se o parâmetro automático, e é uma maçaneta à classe que conteve o método nativo original. Mesmo se o método de nome original não tiver argumentos, o equivalente C daquele método passa-se uma maçaneta à classe portanto pode referir outras partes daquele objeto ou passar-lhe dados atrás. De fato, porque a maçaneta à classe original se comporta como se fosse o objeto de this, muitas vezes chamava this na assinatura de método do código nativo também.
O parâmetro automático é uma maçaneta à classe de Java original que chamou o método nativo. Como é rudemente equivalente a this em Java, o parâmetro automático também muitas vezes se chama this.
Observe a assinatura de método nativa para o método de open() em SimpleFileNative.c, que mostra o parâmetro automático:
long SimpleFile_open(struct HSimpleFile *this)
Para vir aos métodos ou variáveis dentro de uma classe, deve dereference que a maçaneta de classe. Para fazer isto, pode usar o unhand() macro (como em "Largam aquele objeto!"). O unhand() regressos macro um ponteiro para um struct. Deste modo, por exemplo, para alcançar as variáveis dentro da maçaneta de this, o referiria como isto:
unhand(this);
Depois que a maçaneta é dereferenced, pode acessar as suas variáveis como se fossem elementos de struct normais:
unhand(this)->path;
As referências para tabelas são ligeiramente diferentes do que referências para objetos, embora tanto se passem como maçanetas, como pode referir os seus elementos "largando-os" também. Em caso de tabelas, contudo, o nome da maçaneta inclui as palavras ArrayOf preesperado ao tipo da tabela e a carta H preesperado a isto. Deste modo, por exemplo, uma tabela de números inteiros, declarados como isto em Java:
int[] lotsOfInts;
pareceria a isto no lado nativo:
HArrayOfInt *lotsOfInts;
Na seção prévia aprendeu como tratar com referências para objetos de Java como maçanetas. Usando unhand(), pode dereference aquelas maçanetas e vir às variáveis do objeto. Mas que tal métodos? Do seu código nativo, pode chamar métodos dentro de objetos de Java usando várias funções de serviço com somente aquele objetivo.
Além disso, como passa dados para a frente e para trás entre o lado de Java e o lado nativo, precisará de saber como os tipos de dados convertem e como tratar com aqueles tipos em qualquer lado.
Chamar métodos dentro de Java objeta de dentro do código nativo, usa funções de serviço especiais. Para chamar um método de Java regular, use a função execute_java_dynamic_method(). Para chamar um método de classe, use a função execute_java_static_method(). Aqui está a assinatura destas funções (de Java arquivo de include interpreter.h, que define coisas como isto):
long execute_java_dynamic_method(ExecEnv *env, HObject *obj, char *method_name, char *signature, ...); long execute_java_static_method(ExecEnv *env, ClassClass *cb, char *method_name, char *signature, ...);
Ambas as funções tomam pelo menos quatro argumentos:
Qualquer argumento restante ao execute_java_static_method() e funções de execute_java_dynamic_method() é argumentos ao próprio método.
As assinaturas de método podem ser complexas, porque neste caso não são simplesmente a lista de argumentos e os tipos de regresso. As assinaturas de método, para esta função, são cadeias com o grupo de parênteses que contêm uma lista de parâmetros e um tipo de regresso somente depois dos parênteses finais. Tanto a lista de parâmetros como o tipo de regresso são cartas ou cadeias que representam um tipo.
Para os tipos primitivos, use códigos de carta única para a lista de parâmetros e o tipo de regresso (B é byte, I é int, V é void, e Z é boolean). Para tabelas, use um suporte de forma triangular quadrado aberto antes do tipo (por exemplo, [B denota uma tabela de byte). Mais códigos de carta de tipos diferentes contêm-se em Java arquivo de include signature.h. Deste modo, por exemplo, um método que não tem argumentos e devolve void teria uma assinatura de ()V. Aquele que tomam três argumentos de número inteiro e devolvem um número inteiro teria uma assinatura de (III)V.
Para argumentos de objeto, o código é a carta L, então o nome de classe (inclusive o pacote, com todos os elementos separados por golpes), seguido de um ponto-e-vírgula. Deste modo, por exemplo, uma referência para um objeto de String seria Ljava/lang/String;.
Adquiriu tudo isto? Aqui estão alguns exemplos:
execute_java_dynamic_method(0, this, "close", "()Z" execute_java_static_method(0, MyClass, "reverseString", "(Ljava/lang/String;)Ljava/lang/String;", "This is my string"); execute_java_dynamic_method(0, this, "open_speaker()", "(Lcom/lne/audio/Device;)Z", theDevice);
O FindClass() e as funções de FindClassFromClass() podem usar-se para adquirir uma referência para uma estrutura de classe (um ponteiro do tipo ClassClass) para o uso com a função de execute_java_static_method(). Aqui estão as suas assinaturas:
ClassClass *FindClass(ExecEnv *env, char *className, bool_t resolve); ClassClass *FindClassFromClass(ExecEnv *env, char *className, bool_t resolve, ClassClass *from);
Como com as funções para chamar métodos, o primeiro argumento deve ser 0 para indicar que esta função deve dirigir-se no ambiente atual. O segundo argumento é o nome de classe para encontrar. O argumento de resolve é um booleano que, se TRUE ou 1, indica que o método de resolve Class() deve convidar-se que a classe (a resolução de classe é uma função do carregador de classe; está provavelmente seguro usar TRUE para este argumento na maioria dos casos). Em caso de FindClassFromClass, o quarto argumento é uma classe já existente; o carregador de classe que carregou aquela classe também se usará para encontrar e carregar a nova classe.
Para passar parâmetros a métodos de Java do código nativo ou vice-versa, tem de entender como os tipos de dados convertem entre os dois lados do processo.
Os tipos de dados primitivos em Java convertem nos seus equivalentes mais próximos em C. Todos os tipos de número inteiro de Java (char, byte, short, int) e boolean convertem em tipos de long C; long converte em int64_t, e float e double permanecem bóias e dobra-se. Tenha em mente que por causa destas conversões, as suas definições de método nativas originais precisariam de devolver tipos que refletem os valores retornados do lado C do método nativo (por exemplo, todos os métodos que devolvem tipos de integer devem devolver de fato long).
Os tipos de objeto passam-se como maçanetas a estruturas, como aprendeu antes e deve ser dereferenced utilização de unhand() para usar-se.
Como pode acessar objetos de Java e chamar métodos do interior do seu código nativo, uma coisa deixada é a capacidade de criar novos objetos. Pode fazer isto também, usando a função de execute_class_constructor(). Esta função é muito semelhante às funções para chamar métodos; de fato, tem o mesmo jogo de argumentos que execute_java_static_method() faz:
HObject *execute_java_constructor(ExecEnv *, char *classname, ClassClass *cb, char *signature, ...);
A função de execute_java_static_method() tem quatro argumentos, mas pode ter mais. Quatro argumentos necessários são
Aqui estão alguns exemplos:
execute_java_constructor(0, "MyClass", NULL, "()"); execute_java_constructor(0, "MyOtherClass", NULL, "(II)", 10, 12);
O primeiro exemplo cria um exemplo da classe de MyClass, usando o construtor sem argumentos. O segundo cria um exemplo de MyOtherClass, no qual o construtor tem dois argumentos de número inteiro. Aqueles argumentos, 10 e 12, incluem-se no fim da lista de parâmetro.
Para tratar erros, Java tem exceções. No seu código de C nativo, pode fundar uma exceção de Java usando SignalError, como isto:
SignalError(0, JAVAPKG "ExceptionClassName", "message");
Aqui, o nome de classe de exceção é o nome de uma classe de exceção de Java, inclusive o seu nome de pacote, com a separação de nomes de pacote delineados com um golpe em vez de um período como em Java. Deste modo, por exemplo, a classe java.io.IOException seria "java/io/IOException" quando usado dentro de SignalError.
A exceção vai se lançar em Java quando o seu método nativo volta (que deve imediatamente depois do SignalError). Observe que como métodos regulares, deve declarar-se que os métodos nativos que lançam exceções lancem aquelas exceções usando a palavra-chave de throw.
Várias funções e as macros estão disponíveis no arquivo de include javaString.h para ajudar a dirigir cadeias. Para ganhar o acesso a estas funções, inclua aquela cabeçada como parte do seu código nativo:
#include <javaString.h>
A função de makeJavaString() cria Java objeto de String fora de uma cadeia de C. Para converter Java objeto de String em uma cadeia de C, pode usar makeCString() ou allocCString() (onde o antigo aloca a cadeia do armazenamento temporário e o último do montão). Aqui estão as suas assinaturas:
Hjava_lang_String *makeJavaString(char *string, int length) char *makeCString(Hjava_lang_String *s) char *allocCString(Hjava_lang_String *s)
Para copiar Java String s em buffers de ASCII C ou Unicode preexistentes, pode usar javaString2unicode() e javaString2CString():
unicode *javaString2unicode(Hjava_lang_String *s, unicode *buf, int len) char *javaString2CString(Hjava_lang_String *s, char *buf, int len)
Finalmente, a função de javaStringPrint() imprime Java objeto de String (como System.out.print()), e a função de javaStringLength() adquire o seu comprimento:
void javaStringPrint(Hjava_lang_String *s) int javaStringLength(Hjava_lang_String *s)
Hoje aprendeu sobre as vantagens e desvantagens de usar métodos de native, sobre muitos modos que Java (e você) pode fazer os seus programas correr mais rápido, e também sobre a necessidade muitas vezes ilusória da eficiência.
Finalmente, aprendeu o procedimento para criar métodos de native, tanto de Java como dos lados C, no detalhe - gerando arquivos de cabeçada e tocos, e compilando e ligando um exemplo cheio.
Depois de trabalhar do seu caminho pelo material difícil de hoje, dominou uma das partes mais complexas da língua de Java. Como uma recompensa, amanhã olharemos "abaixo do capuz" para ver um pouco do poder escondido de Java, e somente pode acomodar-se e gostar do passeio.
As suas descrições aqui são um tanto escassas. O que posso usar para complementar o que aprendi aqui? | |
A olhada no seminário de Java de Sol (online ou no CD-ROM incluído com este livro) para uma versão mais detalhada de como trabalhar com métodos nativos. | |
Java classifica a necessidade de biblioteca de chamar System.loadLibrary() para carregar as classes construídas? | |
Não, não verá nenhuma chamada de loadLibrary() na implementação de nenhuma classe na biblioteca de classe de Java. É porque a equipe de Java tinha o luxo de ser capaz de ligar estaticamente a maioria do seu código no ambiente de Java, algo que realmente só faz sentido quando está na posição única de fornecer um sistema inteiro, como são. As suas classes devem ligar dinamicamente as suas bibliotecas em uma cópia que corre já do sistema de Java. Isto é, a propósito, mais flexível do que a vinculação estática; permite-lhe desunir velho e religar novas versões das suas classes em qualquer momento, fazendo a atualização deles trivial. | |
Posso ligar-me estaticamente as minhas próprias classes em Java como a equipe de Java fizeram? | |
Sim. Se você gostar, pode pedir a Microsistemas de Sol as fontes ao próprio ambiente de tempo de execução de Java, e, enquanto obedece às (relativamente francas) restrições legais na utilização daquele código, pode religar o sistema de Java inteiro mais as suas classes. As suas classes então ligam-se estaticamente no sistema, mas tem de dar a todo o mundo que quer usar o seu programa esta versão especial do ambiente de Java. Às vezes, se tem exigências bastante fortes, isto é o único modo de ir, mas a maior parte do tempo, a vinculação dinâmica só não está bastante bem, mas preferível. |