Em JNA (Java Native Access), é comum lidar com estruturas C que contêm vetores de ponteiros para outras structs. O gerenciamento correto de memória é essencial para evitar falhas. Vejamos um exemplo complexo.
Exemplo com Vetor de Ponteiros para Struct
Código C originla (adaptado):
struct EmpresaStruct {
long id;
wchar_t* nome;
UsuarioStruct* usuarios[100];
int quantidade;
};
void mostrarEmpresa(EmpresaStruct* pEmpresa);
Mapeamento em JNA:
public static class EmpresaStruct extends Structure {
public static class ByReference extends EmpresaStruct implements Structure.ByReference {}
public NativeLong id;
public WString nome;
public UsuarioStruct.ByReference[] usuarios = new UsuarioStruct.ByReference[100];
public int quantidade;
}
public void mostrarEmpresa(EmpresaStruct.ByReference pEmpresa);
Código de teste em Java:
EmpresaStruct.ByReference empresa = new EmpresaStruct.ByReference();
empresa.id = new NativeLong(2);
empresa.nome = new WString("Yahoo");
empresa.quantidade = 10;
UsuarioStruct.ByReference usuario = new UsuarioStruct.ByReference();
usuario.id = new NativeLong(90);
usuario.idade = 99;
usuario.nome = new WString("João");
usuario.write(); // Importante: fixa a memória do objeto Java
for (int i = 0; i < empresa.quantidade; i++) {
empresa.usuarios[i] = usuario;
}
MinhaBiblioteca.INSTANCE.mostrarEmpresa(empresa);
Explicação sobre o "Pin" (Fixação) de Memória
O Java possui gerenciamento automático de memória (GC). Objetos podem ser movidos, o que quebra a comunicação com código nativo que usa ponteiros. JNA "fixa" (pinned) os objetos durante chamadas nativas. No exemplo acima, a instância empresa é fixada automaticamente ao chamar mostrarEmpresa(), mas os ponteiros internos (usuario) não são fixados. Por isso, é necessário chamar usuario.write() antes, que fixa a memória daquele objeto. Sem isso, o C acessaria um endereço inválido.
A classe Structure oferece métodos como write() e writeField(String nome) para forçar a escrita dos campos na memória nativa e fixar o objeto.
Alternativas: Pointer vs ByReference
Em JNA, Pointer e ByReference podem ser usados para passar endereços. Pointer exige alocação/liberação manual de memória (ex.: new Memory(50)), enquanto ByReference delega ao GC. Para ponteiros multinível, Pointer pode ser mais flexível.
Pointer buffer = new Memory(50);
buffer.setString(0, "Exemplo");
Native.free(Pointer.nativeValue(buffer));
Pointer.nativeValue(buffer, 0); // evita double-free
Criando Vetores de Structs com Memória Contígua
Quando um struct C espera um vetor de structs (não ponteiros), a memória deve ser conntígua. O uso de new MinhaStruct[3] não garante isso. A solução é usar o método toArray() da classe Structure.
InfoStruct[] arr = (InfoStruct[]) new InfoStruct().toArray(3);
Mapeamento de Tipos Comuns
| C | Java |
|---|---|
bool |
int |
void* |
Pointer |
char* |
String ou Pointer |
int* |
IntByReference |
Alinhamento de Bytes Personalizado
Por padrão, JNA determina o alinhamento baseado na plataforma. Para sobrescrever, imlpemente o método getNativeAlignment na subclasse de Structure.
@Override
protected int getNativeAlignment(Class<?> type, Object value, boolean isFirstElement) {
int alinhamentoPadrao = super.getNativeAlignment(type, value, isFirstElement);
return Math.min(alinhamentoPadrao, 4); // alinhamento máximo de 4 bytes
}