piątek, 26 grudnia 2014

Wywołanie C z Javy - JNI

Ponieważ pracuję jako programista JAVA to właśnie ten język programowania znam najlepiej. Swego czasu kupiłem czujnik temperatury DHT 11. Podłączyłem go wg schematu ze strony:
następnie wykorzystałem kod w języku python z: http://www.uugear.com/portfolio/dht11-humidity-temperature-sensor-module/


Wszystko działało ok jednak postanowiłem napisać kod w Javie. Zwykła konwersja kodu z Pythona na Jave nie przyniosła żadnego pożądanego skutku. Thread.sleep czy inne znalezione w internecie metody nie dają wymaganej precyzji.


Poniżej opisuję jak wywołać kod z języka C w Javie. W tym celu wykorzystam JNI tj Java Native Interface. Biblioteka obecnie ma wyświetlać hello world, ale docelowo zostanie rozbudowana i posłuży do pobrania temperatury z czujnika DHT11.


W pierwszej kolejności musimy przygotować klasę w javie. Muszą być w niej zawarte metody, które później napiszemy w C. Klasę nazwałem tak, jak będzie się nazywać moja biblioteka. Poniżej przykład:

public class DHT11
{
   static
   {
      System.loadLibrary("DHT11"); // Load native library at runtime
                                        DHT11.dll (Windows) or libDHT11.so (Unixes)
   }

   public native void runTest();

}

Wgrywamy na Raspberry, przechodzimy do folderu gdzie wgralismy plik java i kompilujemy:
javac DHT11.java


Następnie za pomocą Javy generujemy plik nagłówkowy języka C.
javah DHT11


W wyniku powyższej operacji utworzy się plik DHT11.h. W moim przypadku wygląda on tak:
/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class DHT11 */



#ifndef _Included_DHT11

#define _Included_DHT11

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     DHT11

 * Method:    runTest

 * Signature: ()V

 */

JNIEXPORT void JNICALL Java_DHT11_runTest

  (JNIEnv *, jobject);





#ifdef __cplusplus

}

#endif

#endif


Utworzony plik *.h zalecam skopiować do:  /usr/local/include/. Nie jest to konieczne, ale dzięki temu nie trzeba będzie zmieniać ścieżek podczas kompilowania.

Kolejna rzecz którą musimy ustawić to zmienna środowiskowa JAVA_HOME. Najpierw należy sprawdzić czy jest ustawiona aby to zrobić należy wywołać:
echo $JAVA_HOME

Jeśli zostanie zwrócony pusty wynik to trzeba ustawić tą zmienną przez polecenie:
export JAVA_HOME="scieżkaDoJavy"



Następnie programujemy klasę w C. Musi się ona nazywać jak nasza biblioteka i kalsa Javy. Otwieramy wygenerowany plik nagłówkowy DHT11.h i kopiujemy metody:
JNIEXPORT void JNICALL Java_DHT11_runTest

  (JNIEnv *, jobject)
Następnie do skopiowanych metod dodajemy nazwy obiektów, nawiasy klamrowe i uzupełniamy co ma robić metoda, w tym wypadku wyświetlić na ekranie napis "hello world". Includujemy również potrzebne biblioteki:
#include <jni.h>
#include <stdio.h>
#include "DHT11.h"

JNIEXPORT void JNICALL Java_DHT11_runTest(JNIEnv *env, jobject thisObj)
{
           printf("Hello World\n");
           return;
}


Gdy kod w C jest już gotowy to musimy go skompilować
gcc -fPIC -c DHT11.c -I $JAVA_HOME/include


Jeśli będzie błąd fatal error: jni_md.h: No such file or directory to należy dodać powiązania symboliczne. Szczegóły tutaj: http://stackoverflow.com/questions/24996017/jdk-1-8-on-linux-missing-include-file a w moim przypadku wyglądało to tak:

sudo ln -s /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt/include/linux/jni_md.h /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt//include/jni_md.h

sudo ln -s /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt/include/linux/jawt_md.h /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt//include/jawt_md.h


Po poprawnym skompilowaniu pojawi się nam plik DHT11.so. Ostatni krok to utworzenie już pliku SO:
gcc DHT11.o -shared -o libDHT11.so -Wl,-soname,DHT11



Jeśli wszystko zostało wykonane prawidłowo to utworzy się plik libDHT11.so. Zostało napisanie klasy w javie która odwoła się do utworzonej biblioteki. Przykładowy kod javy:
public class DHT11
{

 static
 {
  System.loadLibrary("DHT11"); // Load native library at runtime
          // DHT11.dll (Windows) or libDHT11.so (Unixes)
 }

 public native void runTest();


 public static void main(String[] args) throws InterruptedException
 {
  new DHT11().runTest();
  Thread.sleep(4000);
  System.out.println("Wychodze");
 }
}


Zapisujemy i wgrywamy na Raspberry Pi. Do folderu z powyższą klasą wgrywamy bibliotekę libDHT11.so. Kompilujemy i uruchamiamy:
javac DHT11.java

java DHT11


niestety wyskoczy błąd:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no DHT11in java.library.path

Błąd wynika z tego, że domyślnie java nie wie gdzie jest plik libDHT11.so. Ponieważ skopiowaliśmy go do folderu gdzie jest skompilowana klasa to możemy dodać parametr do maszyny wirtualnej javy -Djava.library.path i wskazać bieżący folder:
java -Djava.library.path=. DHT11


Tym razem już powinno być ok i program wyświetli na ekranie hello world.

środa, 24 grudnia 2014

Kompilowanie klasy C

W pierwszej kolejnosci musimy utworzyc klasę w C.
mkdir testC

cd testC/

nano hello_world.c 

Ponizej screenshot ekranu z przykładowym kodem wyswietlającym napis "Hello world" na ekranie.


Kompilujemy:
gcc hello_world.c -o hello_worldT

-o oznacza outputFile czyli za tym argumentem podajemy nazwę pliku wynikowego. Jeśli pominiemy argument -o to zostanie utworzony podczas kompilowania domyślny plik o nazwie a.out.

Uruchamiamy na Raspberry:
./hello_worldT

sobota, 20 grudnia 2014

Cross-compilation na Raspberry Pi

Jeśli nie mamy w Linuxie zainstalowanego repozytorium GIT i narzędzi do kompilacji to robimy to poleceniem:
sudo apt-get install build-essential git
Następnie musimy ściągnąć toolchain - poniżej skupię się na tworzeniu plików SO na architekturze ARM dla procesora Raspberry Pi (BCM2708):
mkdir folderRepozytoriumNarzedza

cd folderRepozytoriumNarzedza

git clone git://github.com/raspberrypi/tools.git 
Jeśli nie chcemy się odwoływać po pełnej ścieżce do repozytorium to musimy w pliku .bashrc dodać:
export PATH=$PATH:$HOME/folderRepozytoriumNarzedza/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-ra$
Sprawdzamy czy działa:
arm-linux-gnueabihf-gcc -v