Я внимательно следил за учебником JNI здесь, чтобы использовать некоторые собственные функции, которые работают с магнитными ленточными накопителями. Поэтому я хочу создать программу Java, которая открывает ленты с использованием этих родных библиотек.
Для достижения своей цели я использую Eclipse Kepler CDT на Linux 12.04
Ниже я упоминаю сделанные шаги:
Прежде всего, у меня есть три класса Java:
TestTape
BasicTapeDevice
LogicalEOMException
и, наконец, файл TapeLinux.c
TestTape содержит основную функцию.
public class TestTape {
public static void main(String[] args) throws IOException {
BasicTapeDevice d = new BasicTapeDevice("/dev/nst0");
System.out.print("Rewinding...");
System.out.flush();
d.rewind();
System.out.println("done!");
System.out.print("Spacing to end of data...");
System.out.flush();
d.spaceEOD();
System.out.println("done!");
}
}
Ниже класса BasicTapeDevice, который имеет реализацию Java этих встроенных функций:
class BasicTapeDevice {
private FileDescriptor fd;
private InputStream in;
private OutputStream out;
private boolean eof;
private boolean eom;
private boolean ignoreEOM;
public BasicTapeDevice(String pathName) throws IOException {
fd = new FileDescriptor();
tapeOpen(pathName);
in = new TapeInputStream();
out = new TapeOutputStream();
eof = false;
eom = false;
ignoreEOM = false;
}
public synchronized void close() throws IOException {
if (fd != null) {
try {
if (fd.valid()) {
tapeClose();
}
} finally {
fd = null;
}
}
}
public InputStream getInputStream() throws IOException {
ensureOpen();
return in;
}
public OutputStream getOutputStream() throws IOException {
ensureOpen();
return out;
}
public int getBlockSize() throws IOException {
ensureOpen();
return tapeGetBlockSize();
}
public void setBlockSize(int bs) throws IOException {
ensureOpen();
tapeSetBlockSize(bs);
}
public void rewind() throws IOException {
ensureOpen();
tapeRewind();
}
public void spaceEOD() throws IOException {
ensureOpen();
tapeSpaceEOD();
}
public void clearEOF() throws IOException {
ensureOpen();
if (eof) {
eof = false;
/* assume that the file mark has already been skipped */
} else {
throw new IOException("not at end of file");
}
}
public void clearEOM() throws IOException {
ensureOpen();
if (eom) {
ignoreEOM = true;
} else {
throw new IOException("not at logical end of media");
}
}
class TapeInputStream extends InputStream {
private byte[] temp = new byte[1];
public int read() throws IOException {
int n = read(temp, 0, 1);
if (n <= 0) {
return -1;
}
return temp[0] & 0xff;
}
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || off+len > b.length) {
throw new IndexOutOfBoundsException();
}
if (len == 0) {
return 0;
}
if (eof) {
return -1;
}
ensureOpen();
int n = tapeRead(b, off, len);
if (n <= 0) {
return -1;
}
return n;
}
public long skip(long numbytes) throws IOException {
return 0;
}
public void close() throws IOException {
BasicTapeDevice.this.close();
}
}
class TapeOutputStream extends OutputStream {
private byte[] temp = new byte[1];
public void write(int b) throws IOException {
temp[0] = (byte) b;
write(temp, 0, 1);
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || off+len > b.length) {
throw new IndexOutOfBoundsException();
}
if (eom && !ignoreEOM) {
throw new LogicalEOMException("logical end-of-media");
}
int n = tapeWrite(b, off, len);
while (n < len) {
n += tapeWrite(b, off + n, len - n);
}
}
public void close() throws IOException {
BasicTapeDevice.this.close();
}
}
protected void finalize() {
try {
close();
} catch (IOException ex) {
}
}
private void ensureOpen() throws IOException {
if (fd == null || !fd.valid()) {
throw new IOException("tape device is not open");
}
}
private static native void initFields();
private native void tapeOpen(String pathName) throws IOException;
private native void tapeClose() throws IOException;
private native int tapeRead(byte[] b, int off, int len) throws IOException;
private native int tapeWrite(byte[] b, int off, int len) throws IOException;
private native int tapeGetBlockSize() throws IOException;
private native void tapeSetBlockSize(int bs) throws IOException;
private native void tapeRewind() throws IOException;
private native void tapeSpaceEOD() throws IOException;
/* load the JNI library specific for this platform */
static {
StringBuffer buf = new StringBuffer("Tape");
String osName = System.getProperty("os.name");
if (osName.equals("Windows NT") || osName.equals("Windows 2000")) {
buf.append("WinNT");
} else {
buf.append(osName);
}
System.loadLibrary(buf.toString());
initFields();
}
}
Возможно, вы видели, что класс BasicTapeDevice имеет System.loadLibrary(), который должен импортировать библиотеку.so, которую я собираюсь создать.
Наконец, последний класс - LogicalEOMException:
class LogicalEOMException extends IOException {
public LogicalEOMException() {
super();
}
public LogicalEOMException(String s) {
super(s);
}
}
Ниже TapeLinux.c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/mtio.h>
#include <jni.h>
#include "BasicTapeDevice.h"
#define TRUE 1
#define FALSE 0
/* field IDs for commonly used object fields */
static jfieldID td_fdID;
static jfieldID td_eofID;
static jfieldID td_eomID;
static jfieldID IO_fd_fdID;
/* forward reference for utility functions */
static int getFD(JNIEnv* env, jobject obj);
static void setFD(JNIEnv* env, jobject obj, int fd);
static void throw(JNIEnv* env, int err);
/*
* Class: BasicTapeDevice
* Method: initFields
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_BasicTapeDevice_initFields
(JNIEnv *env, jclass cls)
{
/* retrieve field IDs for the fd, eof, and eom member variables */
td_fdID = (*env)->GetFieldID(env, cls, "fd", "Ljava/io/FileDescriptor;");
td_eofID = (*env)->GetFieldID(env, cls, "eof", "Z");
td_eomID = (*env)->GetFieldID(env, cls, "eom", "Z");
/* retrieve the field ID for the private fd member of FileDescriptor */
cls = (*env)->FindClass(env, "java/io/FileDescriptor");
IO_fd_fdID = (*env)->GetFieldID(env, cls, "fd", "I");
}
/*
* Class: BasicTapeDevice
* Method: tapeOpen
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_BasicTapeDevice_tapeOpen
(JNIEnv *env, jobject this, jstring path)
{
int fd;
const char* p;
p = (*env)->GetStringUTFChars(env, path, 0);
fd = open(p, O_RDWR);
(*env)->ReleaseStringUTFChars(env, path, p);
if (fd == -1) {
throw(env, errno);
}
setFD(env, this, fd);
}
/*
* Class: BasicTapeDevice
* Method: tapeClose
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_BasicTapeDevice_tapeClose
(JNIEnv *env, jobject this)
{
int fd = getFD(env, this);
if (close(fd) == -1) {
throw(env, errno);
}
fd = -1;
setFD(env, this, fd);
}
/*
* Class: BasicTapeDevice
* Method: tapeRead
* Signature: ([BII)I
*/
JNIEXPORT jint JNICALL Java_BasicTapeDevice_tapeRead
(JNIEnv *env, jobject this, jbyteArray buf, jint off, jint len)
{
int n, fd;
jbyte* bufp;
fd = getFD(env, this);
bufp = (*env)->GetByteArrayElements(env, buf, 0);
n = read(fd, &bufp[off], len);
(*env)->ReleaseByteArrayElements(env, buf, bufp, 0);
if (n < 0) {
throw(env, errno);
} else if (n == 0) {
(*env)->SetBooleanField(env, this, td_eofID, TRUE);
}
return n;
}
/*
* Class: BasicTapeDevice
* Method: tapeWrite
* Signature: ([BII)I
*/
JNIEXPORT jint JNICALL Java_BasicTapeDevice_tapeWrite
(JNIEnv *env, jobject this, jbyteArray buf, jint off, jint len)
{
int n, fd;
jbyte* bufp;
fd = getFD(env, this);
bufp = (*env)->GetByteArrayElements(env, buf, 0);
n = write(fd, &bufp[off], len);
(*env)->ReleaseByteArrayElements(env, buf, bufp, 0);
if (n < 0) {
throw(env, errno);
} else if (n == 0) {
(*env)->SetBooleanField(env, this, td_eomID, TRUE);
}
return n;
}
/*
* Class: BasicTapeDevice
* Method: tapeGetBlockSize
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_BasicTapeDevice_tapeGetBlockSize
(JNIEnv *env, jobject this)
{
int fd;
struct mtget mtget;
jint bs;
fd = getFD(env, this);
if (ioctl(fd, MTIOCGET, &mtget) == -1) {
throw(env, errno);
bs = -1;
} else {
bs = mtget.mt_dsreg & MT_ST_BLKSIZE_MASK;
}
return bs;
}
/*
* Class: BasicTapeDevice
* Method: tapeSetBlockSize
* Signature: ()I
*/
JNIEXPORT void JNICALL Java_BasicTapeDevice_tapeSetBlockSize
(JNIEnv *env, jobject this, jint bs)
{
int fd;
struct mtop mtop;
mtop.mt_op = MTSETBLK;
mtop.mt_count = bs;
fd = getFD(env, this);
if (ioctl(fd, MTIOCTOP, &mtop) == -1) {
throw(env, errno);
}
}
/*
* Class: BasicTapeDevice
* Method: tapeRewind
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_BasicTapeDevice_tapeRewind
(JNIEnv *env, jobject this)
{
int fd;
struct mtop mtop;
mtop.mt_op = MTREW;
mtop.mt_count = 1;
fd = getFD(env, this);
if (ioctl(fd, MTIOCTOP, &mtop) == -1) {
throw(env, errno);
}
}
/*
* Class: BasicTapeDevice
* Method: tapeSpaceEOD
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_BasicTapeDevice_tapeSpaceEOD
(JNIEnv* env, jobject this)
{
int fd;
struct mtop mtop;
mtop.mt_op = MTEOM;
mtop.mt_count = 1;
fd = getFD(env, this);
if (ioctl(fd, MTIOCTOP, &mtop) == -1) {
throw(env, errno);
}
}
/*
* Retrieves the internal file descriptor from the BasicTapeDevice object
*/
static int getFD(JNIEnv* env, jobject obj) {
jobject fdobj;
fdobj = (*env)->GetObjectField(env, obj, td_fdID);
return (*env)->GetIntField(env, fdobj, IO_fd_fdID);
}
/*
* Sets the internal file descriptor of the BasicTapeDevice object
*/
static void setFD(JNIEnv* env, jobject obj, int fd)
{
jobject fdobj = (*env)->GetObjectField(env, obj, td_fdID);
(*env)->SetIntField(env, fdobj, IO_fd_fdID, fd);
}
/*
* Throws a new IOException
*/
static void throw(JNIEnv* env, int err)
{
jclass cls = (*env)->FindClass(env, "java/io/IOException");
if (cls != NULL) {
(*env)->ThrowNew(env, cls, strerror(err));
}
}
Учитывая эту структуру программирования, я начну реализацию своей программы:
1) Прежде всего, я создаю проект Java (называемый Tape) тремя классами, а затем преобразую класс BasicTapeDevice в проект C/C++ (добавляет C/C++ Nature) со следующими параметрами:
Convert to C or C++ Project: C Project
Toolchains: LinuxGCC
Project Type: Makefile Project
Это создает следующее:
Пока все выглядит нормально. Теперь я создаю папку jni и создаю в ней файл makefile.
MAKEFILE
# Define a variable for classpath
CLASS_PATH = ../bin
# Define a virtual path for .class in the bin directory
vpath %.class $(CLASS_PATH)
# $* matches the target filename without the extension
BasicTapeDevice.h : BasicTapeDevice.class
javah -classpath $(CLASS_PATH) $*
Я создаю make-target для makefile BasicTapeDevice.h, а затем создаю его. Результат:
**** Build of configuration Default for project Tape ****
make BasicTapeDevice.h
javah -classpath ../bin BasicTapeDevice
Build Finished (took 775ms)
Теперь созданный ранее файл c помещается в папку jni в файле TapeLinux.c, и теперь можно создать библиотеку.so (помните, что мы находимся под Linux)
Это созданный мной файл makefile
MAKEFILE
# Define a variable for classpath
CLASS_PATH = ../bin
# Define a virtual path for .class in the bin directory
vpath %.class $(CLASS_PATH)
all : TapeLinux.so
# $@ matches the target, $< matches the first dependancy
TapeLinux.so : TapeLinux.o
gcc -Wl,--add-stdcall-alias -shared -o $@ $<
# $@ matches the target, $< matches the first dependancy
TapeLinux.o : TapeLinux.c TapeLinux.h
gcc -I"/home/tanio/DevelopmentEnvironment/jdk1.7.0_51/include/linux" -I"/home/tanio/DevelopmentEnvironment/jdk1.7.0_51/include" -c $< -o $@
# $* matches the target filename without the extension
BasicTapeDevice.h : BasicTapeDevice.class
javah -classpath $(CLASS_PATH) $*
clean :
rm TapeLinux.h TapeLinux.o TapeLinux.so
К сожалению, я не понял эту часть должным образом, и я застрял.
Компилятор дает мне эту проблему:
**** Build of configuration Default for project Tape ****
make all
make: *** No rule to make target 'all'. Stop.
Build Finished (took 74ms)
Не могли бы вы помочь мне выяснить, в чем проблема?
спасибо
РЕДАКТИРОВАТЬ
# Define a variable for classpath
CLASS_PATH = ../bin
# Define a virtual path for .class in the bin directory
vpath %.class $(CLASS_PATH)
all : BasicTapeDevice.so
# $@ matches the target, $< matches the first dependancy
BasicTapeDevice.so : BasicTapeDevice.o
gcc -Wl -shared -o $@ $<
# $@ matches the target, $< matches the first dependancy
BasicTapeDevice.o : TapeLinux.c BasicTapeDevice.h
gcc -I"/home/tanio/DevelopmentEnvironment/jdk1.7.0_51/include/linux" -I"/home/tanio/DevelopmentEnvironment/jdk1.7.0_51/include" -c $< -o $@
# $* matches the target filename without the extension
BasicTapeDevice.h : BasicTapeDevice.class
javah -classpath $(CLASS_PATH) $*
clean :
rm BasicTapeDevice.h BasicTapeDevice.o BasicTapeDevice.so
Этот make файл компилируется правильно, но я получаю сообщение об ошибке при запуске приложения, потому что главное не в BasicTapeDevice
Должен ли я компилировать каждый класс отдельно, а затем запускать программу или лучше разместить все в одном классе?
Спасибо, ребята, за вашу помощь. Я решил проблему с модификацией make файла
Здесь результат
# Define a variable for classpath
CLASS_PATH = ../bin
# Define a virtual path for .class in the bin directory
vpath %.class $(CLASS_PATH)
all : libTape.so
# $@ matches the target, $< matches the first dependancy
libTape.so : TapeJNI.o
gcc -shared -fpic -o $@ $<
# $@ matches the target, $< matches the first dependancy
TapeJNI.o : TapeJNI.c TapeJNI.h
gcc -fpic -I"/home/tanio/DevelopmentEnvironment/jdk1.7.0_51/include" -I"/home/tanio/DevelopmentEnvironment/jdk1.7.0_51/include/linux" -c $< -o $@
# $* matches the target filename without the extension
TapeJNI.h : TapeJNI.class
javah -classpath $(CLASS_PATH) $*
clean :
rm TapeJNI.h TapeJNI.o libTape.so
Я скомпилировал его с другим именем, извините, если оно не соответствует
TapeLinux.h
? Также ваше правилоBasicTapeDevice.h
использует функцию совместимости GNU make, а не первичную.$*
- это «основа» правила шаблона, которое соответствует. В не-шаблонном правиле (например, вашем) оно также определяется как имя цели без (распознанного) суффикса, но, согласно руководству, это функция совместимости, а не та, от которой следует зависеть.