Conectar programas C/C++ con aplicaciones Android

En esta entrada voy a describir la manera de comunicar aplicaciones escritas en Android con programas escritos en C/C++ vía JNI utilizando el NDK de Android.

El NDK es un conjunto de herramientas que permite incorporar los componentes que hacen uso de código nativo en las aplicaciones de Android. Permite implementar parte de tus aplicaciones usando código nativo con lenguajes como C y C ++. Esto puede proporcionar beneficios a ciertas clases de aplicaciones, en la medida que se puede reutilizar el código existente y en algunos casos obtener un aumento de la velocidad.

Java Native Interface (JNI) es un framework de programación que permite que un programa escrito en Java ejecutado en la máquina virtual java (JVM) pueda interactuar con programas escritos en otros lenguajes como C, C++ y ensamblador.

La estructura de un proyecto Android que utilice JNI y NDK para permitir la intercomunicación entre aplicaciones C/C++ y Android es la siguiente:
carpeta raíz del proyecto/

  • jni/
  • libs/
  • res/
  • src/
  • AndroidManifest.xml
  • default.properties
  • … otros ficheros …

Descripción de los ficheros:

  • La carpeta src contiene el código Java de la aplicación Android
  • La carpeta res contiene los recursos de la aplicación (imágenes, archivos XML que describen las capas de interfaz, etc)
  • La carpeta libs *contendrá* las librerías nativas *después* de una construcción exitosa
  • La carpeta jni *contiene* el código C/C++ de la aplicación con la que queremos comunicarnos, además de dos scripts importantes: Android.mk y Application.mk. Estos scripts son dos Makefiles típicos que controlan el proceso de construcción de la aplicación C++.

Veamos los pasos para ejecutar una sencilla aplicación Android que se comunique con un programa escrito en C++.

  • Descargar y descomprimir NDK
    Sé que hay versiones mucho más actuales, pero aún así, utilizo esta de Crystax que está bastante optimizada.
  1. wget http://www.crystax.net/en/download/android-ndk-r4-linux-x86-crystax-4.tar.bz2 -O /tmp/android-ndk-r4-linux-x86-crystax-4.tar.bz2
  2. cd ~ && tar xvjf /tmp/android-ndk-r4-linux-x86-crystax-4.tar.bz2

  • Crear la carpeta libs en el proyecto
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> Folder. Escribimos libs como nombre de carpeta.
  • Crear la carpeta jni en el proyecto
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> Folder. Escribimos jni como nombre de carpeta.
  • Crear el programa C++ con el que queremos comunicarnos
    Pulsamos el botón derecho sobre la carpeta JNI, y escogemos la opción New -> File. Escribimos code.cpp como nombre de fichero. Hacemos doble clic sobre el fichero y escribimos el siguiente código:
  1. #include <jni.h>
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <getopt.h>
  5. #include <string>
  6. #include <cassert>
  7. #include <stdexcept>
  8.  
  9. using namespace std;
  10.  
  11. extern "C"
  12. {
  13. JNIEXPORT jstring JNICALL Java_blog_neonigma_AndroidActivity_SayHello(JNIEnv *env, jobject thiz, jstring name)
  14. {
  15.   try
  16.   {
  17.     const char *myName = env->GetStringUTFChars(name, 0);
  18.     std::string nameCPP(myName);
  19.     nameCPP = "Hello: " + nameCPP;
  20.     return env->NewStringUTF(nameCPP.c_str());
  21.   }
  22.   catch (exception &ex)
  23.   {
  24.     const char *error = "Failed";
  25.     return env->NewStringUTF(error);
  26.   }
  27. }
  28. }

  • Consideraciones sobre el código:
    • El bloque extern «C» indica que el programa está escrito en C++. Sí, en C++. Si queremos escribirlo en C, eliminamos el bloque extern.
    • La declaración de la función nativa tiene que coincidir con el namespace + nombre de actividad Android + nombre de función en el código Java/Android. En este caso, como nuestra actividad es AndroidActivity y se encuentra en el namespace blog.neonigma, la función debe nombrarse como Java_blog_neonigma_AndroidActivity_SayHello, siendo SayHello el nombre que damos a la función y «Java_» un texto estático.
    • La función siempre recibe un puntero al entorno (JNIEnv *env) y un objeto manejador (jobject thiz)
    • Para convertir const char * en jstring, usamos la función NewStringUTF del entorno
    • Más información en la API oficial de JNI

  • Crear los Makefiles para NDK
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> File. Escribimos Android.mk como nombre de fichero. El código para este fichero sería el siguiente:
  1. LOCAL_PATH := $(call my-dir)
  2.  
  3. include $(CLEAR_VARS)
  4.  
  5. LOCAL_MODULE    := mixed_sample
  6. LOCAL_SRC_FILES := code.cpp
  7. LOCAL_LDLIBS +=  -llog -ldl
  8.  
  9. include $(BUILD_SHARED_LIBRARY)

Los valores más importantes son code.cpp, que indica el fichero de código con el que queremos comunicar y mixed_sample, que es el nombre que damos a la librería que se va generar vía JNI.

Repetimos el proceso y creamos un fichero Application.mk.

  1. APP_STL := gnustl_static
  2. APP_CPPFLAGS := -frtti -fexceptions
  3. APP_ABI := armeabi
  4. APP_PROJECT_PATH := ~/workspace/blog-jni-c

En este caso, los valores más importantes son la arquitectura armeabi (puede cambiarse por la más moderna armeabi-v7a) y el path del proyecto, donde hay que indicar el path al workspace actual.

  • Crear un builder para la compilación
    Debe generarse un builder en Eclipse para la compilación del código C/C++ con NDK.

    1. Pulsamos en projects -> properties y escogemos la opción Builders. Pulsamos el botón New.
    2. En la ventana que aparece, escogemos la opción Program (sin escribir nada), y pulsamos OK.
    3. En la siguiente ventana configuramos el Builder en sí. En la pestaña Main, escribimos en el campo Location la ruta absoluta al programa ndk-build, en mi caso /home/neonigma/android-ndk-r4-crystax/ndk-build En el campo Working directory, pulsamos en el botón Browse workspace y escogemos el proyecto en el que estamos trabajando.
    4. Ahora vamos a la pestaña Refresh y marcamos Refresh upon completion, Specific resources y Recursively include sub-folders.

      Pulsamos en el botón Specify resources y en la ventana que aparece, escogemos el recurso libs.
    5. Por último, vamos a la pestaña Build Options, dejamos las casillas ya marcadas y marcamos During auto builds y Specify working set of relevant resources.

      Pulsamos en el botón Specify resources y escogemos el recurso jni de nuestro proyecto.
  • Escribir el código de la actividad Android
    Para enviar datos al programa en C++ y recogerlos, necesitamos escribir el código de la actividad principal de la siguiente manera:
  1. package blog.neonigma;
  2.  
  3. import src.blog.neonigma.R;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.widget.Toast;
  7.  
  8. public class AndroidActivity extends Activity {
  9.     @Override
  10.     public void onCreate(Bundle savedInstanceState) {
  11.         super.onCreate(savedInstanceState);
  12.         setContentView(R.layout.main);
  13.        
  14.         String greeting = SayHello("neonigma");
  15.         Toast.makeText(this, greeting, Toast.LENGTH_LONG).show();
  16.     }
  17.    
  18.     public native String SayHello(String name);
  19.    
  20.     static {
  21.         System.loadLibrary("mixed_sample");
  22.     }

  • Consideraciones sobre el código
    • La llamada a función nativa se define como public native.
    • String se equipara a jstring, e int a jint, a la hora de enviar y recibir parámetros.
    • Debe definirse como estática la carga del módulo de la librería generada y que especificamos en el código del Makefile Android.mk.

Referencias:
http://opencv.itseez.com/doc/tutorials/introduction/android_binary_package/android_binary_package_using_with_NDK.html#android-binary-package-with-ndk
http://zsoluciones.com/datos/?p=246

El código del ejemplo puede descargarse desde el repositorio creado en BitBucket.

A 1 persona le gusta esta entrada

3 pensamientos en “Conectar programas C/C++ con aplicaciones Android

  1. hola, estoy intentando los NDK descomprimidos porque en zip no soy capaz de hacerlo. no sale ejecutable al descomprimir. si tuvieras alguna solucion. gracias, un saludo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *