Итак, я готов показать вам еще кусочик черной магии. Сегодня мы будем с вами работать с железом при помощи Java Native Interface.
Задача для меня типовая. Есть некоторая плата АЦП(хотя это может быть любое другое устройство). Для нее есть драйвер под линукс. Обмен данными с платой происходит при помощи сообщений, передаваемых через ioctl. Необходимо эти сообщения получать в ПО, обрабатывать и посылать что-нибудь в ответ через те же ioctl.
Итак, как вы знаете, Java предоставляет средство для работы с внешними системными библиотеками, это средство – JNI. Воспользовавшись этим мощнейшим средством, мы напишем небольшую библиотеку, которая обеспечит нам взаимодействие с ioctl, который является системным вызовом и напрямую из Java недоступен.
Подготовим класс-обертку:
public class LL {
public static native int Ioctl(int descr, int cmd, int[] data);
public static native int Open(String linkName);
public static native int Close(int fd);
}
Как описано в прошлой статье сгененируем заголовочный файл для C:
$ javac LL.java
$ javah -jni LL
Не забудьте, что я убрал пути из командной строки. Вам надо будет разобраться с ними вручную.
Полученый LL.h:
/* DO NOT EDIT THIS FILE – it is machine generated */
#include
/* Header for class LL */
#ifndef _Included_LL
#define _Included_LL
#ifdef __cplusplus
extern “C” {
#endif
/*
* Class: LL
* Method: Ioctl
* Signature: (II[I)I
*/
JNIEXPORT jint JNICALL Java_LL_Ioctl
(JNIEnv *, jclass, jint, jint, jintArray);
/*
* Class: LL
* Method: Open
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_LL_Open
(JNIEnv *, jclass, jstring);
/*
* Class: LL
* Method: Close
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_LL_Close
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
Теперь напишем реализацию этих функций:
#include "LL.h"
#include
#include
#include
#define MAXDRVMSG 32
JNIEXPORT jint JNICALL Java_LL_Ioctl(JNIEnv *env, jclass jc, jint fd, jint cmd, jintArray data)
{
int res = -1;
if(data == NULL)
{
fprintf(stderr,"no param ioctl");
res = ioctl(fd, cmd);
}
else
{
fprintf(stderr,"with param ioctl %p\n",data);
unsigned int *x = (unsigned int *) calloc(MAXDRVMSG, sizeof(unsigned int));
res = ioctl(fd, cmd, x);
(*env)->SetIntArrayRegion(env, data, 0, MAXDRVMSG, (jint *) x);
}
fprintf(stderr,"LL_Ioctl: fd %d result %d\n",fd, res);
return res;
}
JNIEXPORT jint JNICALL Java_LL_Open(JNIEnv *env, jclass jc, jstring linkname)
{
jboolean iscopy;
const char *mfile = (*env)->GetStringUTFChars(env, linkname, &iscopy);
fprintf(stderr,"LL_Open: filename %s\n",mfile);
int fd = open(mfile, O_RDWR);
fprintf(stderr,"LL_Open: descr %d\n",fd);
return fd;
}
JNIEXPORT jint JNICALL Java_LL_Close(JNIEnv *env, jclass jc, jint fd)
{
fprintf(stderr,"LL_Close: fd %d\n",fd);
return close(fd);
}
Отличие от предыдущего эксперимента в том, что теперь мы стали получать данные из функций, описанных в нашей библиотеке. В простых случаях Close и Open проблем с возвратом не возникает, а вот в случае с Ioctl, где нам надо получать\отправлять блоки данных, а не примитивные типы без хитростей не обойтись. В данном случае хитрость в том, что для обмена с драйвером мы выделим временный буффер, в который скопируем данные из data, переданного в качестве параметра функции. После системого вызова нам надо будет скопировать данные из ответа назад в нашу виртуальную машину.
Makefile для собрки этого безобразия:
SRC=LL.c
OBJ=libll.o
LIB=libll.so
GCC=gcc
default:
$(GCC) -c $(SRC) -o $(OBJ)
$(GCC) -shared -Wl,-soname,$(LIB) -o $(LIB) $(OBJ) -lc
jni:
javac -d ../out/production/jni_hw/ ../src/LL.java
javah -jni -classpath ../out/production/jni_hw/ LL
И небольшой тестик:
public class LLtest {
public static void main(String[] args)
{
System.load(“/home/toch/src/java/jni_hw/jni/libll.so”);
int x = LL.Open(“/dev/char_dev”);
int[] msg = new int[32];
LL.Ioctl(x, 1, msg);
for(int i = 0;i<32;i++) System.out.println(String.format("0x%x",msg[i]));
LL.Close(x);
}
}