I am trying to post to a semaphore using JNA on a Linux machine. For some reason, I always receive a 22 error (invalid argument) even for this simple example. In my understanding, should the below code not open a POSIX semaphore, post to it and close it again?
public class Sample {
private static final int O_CREAT = 0x40;
public static void main(String[] args) throws Exception {
File notifier = new File("/tmp", "_test" + new Random().nextInt());
if (!notifier.isFile() && !notifier.createNewFile()) {
throw new IllegalStateException("Could not create notifier: " + notifier);
}
SempahoreLibrary library = Native.load("c", SempahoreLibrary.class);
Pointer semaphore = library.sem_open(notifier.getAbsolutePath(), O_CREAT, 666, 0);
try {
library.sem_post(semaphore);
} finally {
library.sem_close(semaphore);
}
}
interface SempahoreLibrary extends Library {
Pointer sem_open(String name, int flags, int mode, int value) throws LastErrorException;
int sem_post(Pointer pointer) throws LastErrorException;
int sem_close(Pointer pointer) throws LastErrorException;
}
}
I initially couldn't make it work with JNR either (strongly recommended over JNA), and got curious. Writing it in C helped.. :)
An strace on the C port made it clear you don't have to create a file upfront and
then "map" the semaphore to it. Also using the full path is wrong, because
semaphores are created in /dev/shm and the "/" in the path screws
up everything:
futex(0x7f731b1190d0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
openat(AT_FDCWD, "/dev/shm/sem.sema", O_RDWR|O_NOFOLLOW) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=32, ...}) = 0
So you should be able to remove the whole file/path creation and just use a regular non-path name for the semaphore in sem_open. Also the file mode should be octal,
and you should make sure to also load the pthread library - it's required.
Here is a working example in C:
// clang -Wall sema.c -lpthread
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char** argv)
{
sem_t* s = sem_open("notifier", O_CREAT, 0644, 0);
if (!s) {
perror("sem_open");
exit(errno);
}
printf("s: %p\n", s);
sem_post(s);
int value = -1;
sem_getvalue(s, &value);
printf("value: %d\n", value);
sem_wait(s);
sem_getvalue(s, &value);
printf("value: %d\n", value);
sem_close(s);
exit(EXIT_SUCCESS);
}
Here is a working Java version using JNR:
import jnr.ffi.LastError;
import jnr.ffi.LibraryLoader;
import jnr.ffi.Pointer;
import jnr.ffi.Runtime;
public class Semaphore
{
private static final int O_CREAT = 0x40;
public interface SempahoreLibrary
{
Pointer sem_open(String name, int flags, int mode, int value);
int sem_post(Pointer pointer);
int sem_close(Pointer pointer);
}
public static void main(String[] args) throws Exception
{
LibraryLoader<SempahoreLibrary> loader = LibraryLoader.create(SempahoreLibrary.class);
loader.library("c");
loader.library("pthread");
SempahoreLibrary library = loader.load();
jnr.ffi.Runtime runtime = Runtime.getRuntime(library);
Pointer semaphore = library.sem_open("notifier", O_CREAT, 0644, 0);
if (semaphore == null)
{
int errno = LastError.getLastError(runtime);
System.out.println("sem_open: " + errno);
System.exit(errno);
}
System.out.println("semaphore: " + Long.toHexString(semaphore.address()));
try
{
int error = library.sem_post(semaphore);
System.out.println("post: " + (error == 0 ? "OK" : LastError.getLastError(runtime)));
}
finally
{
int error = library.sem_close(semaphore);
System.out.println("close: " + (error == 0 ? "OK" : LastError.getLastError(runtime)));
}
}
}
Related
Goal
Obtaining the desktop coordinates of a desktop item/icon.
Attempt
I have obtained the SysListView32 window handle, which contains the desktop icons using:
HWND hWnd_Progman = User32.INSTANCE.FindWindow("Progman", "Program Manager");
HWND hWnd_SHELLDLL_DefView = User32.INSTANCE.FindWindowEx(hWnd_Progman, null, "SHELLDLL_DefView", null);
HWND hWnd_SysListView32 = User32.INSTANCE.FindWindowEx(hWnd_SHELLDLL_DefView, null, "SysListView32", "FolderView");
I have obtained the desktop item count:
LRESULT result = User32.INSTANCE.SendMessage(hWnd_SysListView32, LVM_GETITEMCOUNT, new WPARAM(), new LPARAM());
long desktopIconCount = result.longValue();
I have SET a desktop item position (validating that SysListView32 is the right list view for desktop items). The passed x and y coordinates corresponded with the offset from the left-top of my left-most monitor to the left-top corner of the desktop item. Code:
int itemIndex = 0; // Allows 0 to desktopIconCount - 1.
int x = ...;
int y = ...;
LRESULT res = User32.INSTANCE.SendMessage(hWnd_SysListView32, LVM_SETITEMPOSITION, new WPARAM(itemIndex), new LPARAM((x & 0xFFFF) | (y << 16)));
Now, to get a desktop item position, one needs to send LVM_GETITEMPOSITION to the SysListView32 and include a pointer to an address where it can write the position to. However, this pointer has to be a valid address in the memory of the process belonging to SysListView32. So what I tried to do is to:
Get the process belonging to SysListView32.
Allocate memory in that process.
Write a POINT object in this memory (used for the item position).
Send LVM_GETITEMPOSITION to SysListView32 with a pointer to this allocated memory.
Read this POINT object from memory. At this point the process should have written the desktop item position to it.
I've tried this with the following code:
// Get the SysListView32 process handle.
IntByReference processIdRef = new IntByReference();
User32.INSTANCE.GetWindowThreadProcessId(hWnd_SysListView32, processIdRef);
HANDLE procHandle = Kernel32.INSTANCE.OpenProcess(
Kernel32.PROCESS_VM_OPERATION | Kernel32.PROCESS_VM_WRITE | Kernel32.PROCESS_VM_READ,
false, processIdRef.getValue());
// Allocate memory in the SysView32 process.
int pointSize = Native.getNativeSize(POINT.class)); // 8 bytes.
LPVOID pMem = MyKernel32.INSTANCE.VirtualAllocEx(procHandle, new LPVOID(), new SIZE_T(pointSize),
MyKernel32.MEM_COMMIT, MyKernel32.PAGE_READWRITE);
// Put some POINT-sized object in the process its memory.
boolean success = Kernel32.INSTANCE.WriteProcessMemory(
procHandle, pMem.getPointer(), pMem.getPointer(), pointSize, null);
if(!success) {
System.out.println("Write error = " + Kernel32.INSTANCE.GetLastError());
System.exit(1);
}
// Send the LVM_GETITEMPOSITION message to the SysListView32.
int itemIndex = 0; // Allows 0 to desktopIconCount - 1.
LRESULT res = MyUser32.INSTANCE.SendMessage(
hWnd_SysListView32, LVM_GETITEMPOSITION, new WPARAM(itemIndex), pMem.getPointer());
System.out.println("Message result = " + res.longValue());
// Read the earlier POINT-sized written memory.
POINT point = new POINT();
success = Kernel32.INSTANCE.ReadProcessMemory(
procHandle, pMem.getPointer(), point.getPointer(), pointSize, null);
if(!success) {
System.out.println("Read error = " + Kernel32.INSTANCE.GetLastError());
System.exit(1);
}
System.out.println("Point found: x=" + pos.x + ", y=" + pos.y);
Here, MyUser32 is created as follows:
interface MyUser32 extends User32 {
static MyUser32 INSTANCE =
(MyUser32) Native.load("user32", MyUser32.class, W32APIOptions.DEFAULT_OPTIONS);
LRESULT SendMessage(HWND hWnd, int msg, WPARAM wParam, Pointer pointer);
}
and MyKernel32 is created as follows:
interface MyKernel32 extends Kernel32 {
static final MyKernel32 INSTANCE =
(MyKernel32) Native.load("kernel32", MyKernel32.class, W32APIOptions.DEFAULT_OPTIONS);
static int MEM_COMMIT = 0x1000;
static int PAGE_READWRITE = 0x04;
LPVOID VirtualAllocEx(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, int flAllocationType, int flProtect);
}
To be complete, the following additional static values are used:
static final int LVM_FIRST = 0x1000;
static final int LVM_GETITEMCOUNT = LVM_FIRST + 4;
static final int LVM_SETITEMPOSITION = LVM_FIRST + 15;
static final int LVM_GETITEMPOSITION = LVM_FIRST + 16;
Problem
The WriteProcessMemory call often fails with error code 299 ERROR_PARTIAL_COPY and even when it does not fail, the returned POINT is always (0,0). I expect the problem to be in either the SendMessage / VirtualAllocEx method declarations in MyUser32 or MyKernel32, or by me not understanding properly which object/pointer to pass to VirtualAllocEx or WriteProcessMemory.
I've done a lot of research and figured out how it should work in C/C++, but I could not find any working code example using JNA for my case.
Thank you for showing interest and trying to help if you made it all the way through my message.
The problem is that com.sun.jna.Native.getNativeSize(Class) is not the right function to use in this case. The problem is visible, when using a 32bit JVM (it is not visible a 64bit VM).
For structures the above mentioned function assumes that they are passed by reference (pointer to the structure) and thus the function returns the value of Native.POINTER_SIZE. On a 64Bit VM, this matches by luck the size of the POINT structure. On a 32Bit VM Native.POINTER_SIZE is 4 byte, and so can only hold parts of the result structure.
The most relevant parts: to determine the size of a Structure in JNA, use the Structure#size function. In this case it is also helpful to use the final parameter of ReadProcessMemory. The function returns the number of bytes that were read and shows the difference (4 vs. 8).
Further comments: remember to free the memory you allocated and also close the process handle to received.
Here is the full runnable sample (only missing imports, tested with JNA 5.2):
public class Test {
private interface Kernel32 extends com.sun.jna.platform.win32.Kernel32 {
Kernel32 INSTANCE = Native.load("kernel32", Kernel32.class, W32APIOptions.DEFAULT_OPTIONS);
public Pointer VirtualAllocEx(HANDLE hProcess, Pointer lpAddress, SIZE_T dwSize, int flAllocationType, int flProtect);
public boolean VirtualFreeEx(HANDLE hProcess, Pointer lpAddress, SIZE_T dwSize, int dwFreeType);
int MEM_COMMIT = 0x00001000;
int MEM_RESERVE = 0x00002000;
int MEM_RESET = 0x00080000;
int MEM_RESET_UNDO = 0x1000000;
int MEM_LARGE_PAGES = 0x20000000;
int MEM_PHYSICAL = 0x00400000;
int MEM_TOP_DOWN = 0x00100000;
int MEM_COALESCE_PLACEHOLDERS = 0x00000001;
int MEM_PRESERVE_PLACEHOLDER = 0x00000002;
int MEM_DECOMMIT = 0x4000;
int MEM_RELEASE = 0x8000;
}
private static final int LVM_FIRST = 0x1000;
private static final int LVM_GETITEMCOUNT = LVM_FIRST + 4;
private static final int LVM_GETITEMPOSITION = LVM_FIRST + 16;
public static void main(String[] args) throws IOException, InterruptedException {
// Find the HWND for the "desktop" list view
HWND hWnd_Progman = User32.INSTANCE.FindWindow("Progman", "Program Manager");
HWND hWnd_SHELLDLL_DefView = User32.INSTANCE.FindWindowEx(hWnd_Progman, null, "SHELLDLL_DefView", null);
HWND hWnd_SysListView32 = User32.INSTANCE.FindWindowEx(hWnd_SHELLDLL_DefView, null, "SysListView32", "FolderView");
// Fetch the icon count
int itemCount = User32.INSTANCE.SendMessage(hWnd_SysListView32, LVM_GETITEMCOUNT, new WPARAM(), new LPARAM()).intValue();
System.out.println("Desktop Icons: " + itemCount);
// Get the SysListView32 process handle.
IntByReference processIdRef = new IntByReference();
User32.INSTANCE.GetWindowThreadProcessId(hWnd_SysListView32, processIdRef);
HANDLE procHandle = Kernel32.INSTANCE.OpenProcess(
Kernel32.PROCESS_VM_OPERATION | Kernel32.PROCESS_VM_WRITE | Kernel32.PROCESS_VM_READ,
false, processIdRef.getValue());
// Allocate memory in the SysView32 process.
int pointSize = new POINT().size(); // 8 bytes.
Pointer pMem = Kernel32.INSTANCE.VirtualAllocEx(procHandle, null, new SIZE_T(pointSize),
Kernel32.MEM_COMMIT, Kernel32.PAGE_READWRITE);
for (int i = 0; i < itemCount; i++) {
// Send the LVM_GETITEMPOSITION message to the SysListView32.
LRESULT res = User32.INSTANCE.SendMessage(
hWnd_SysListView32, LVM_GETITEMPOSITION, new WPARAM(i), new LPARAM(Pointer.nativeValue(pMem)));
if(res.intValue() != 1) {
throw new IllegalStateException("Message sending failed");
}
// Read the earlier POINT-sized written memory.
POINT point = new POINT();
IntByReference read = new IntByReference();
boolean success = Kernel32.INSTANCE.ReadProcessMemory(
procHandle, pMem, point.getPointer(), pointSize, read);
if (!success) {
System.out.println("Read error = " + Kernel32.INSTANCE.GetLastError());
System.exit(1);
}
point.read();
System.out.println("Point found: x=" + point.x + ", y=" + point.y);
}
// Release allocated memory
Kernel32.INSTANCE.VirtualFreeEx(procHandle, pMem, new SIZE_T(0), Kernel32.MEM_RELEASE);
// Close Process Handle
Kernel32.INSTANCE.CloseHandle(procHandle);
}
}
I am trying to get Accelerometer data into my Java application from Arduino at its IMU. When I run my Arduino program on Arduino IDE and bring up Serial Monitor data flow is really good. Unfortunatelly when I try to run my Java code something strange happens. I have to wait for 10 seconds and then about 1000 lines appear immediately. Then i have to wait for another 10-20 seconds and another 1000 lines of data appears.. Is Arduino sending data in packages ? Can't I get a single value right after its given to me from IMU ?
Arduino code :
#include <MPU9250.h>
#include <quaternionFilters.h>
#define AHRS true // Set to false for basic data read
#define SerialDebug true // Set to true to get Serial output for debugging
// Pin definitions
int intPin = 12; // These can be changed, 2 and 3 are the Arduinos ext int pins
int myLed = 13; // Set up pin 13 led for toggling
MPU9250 myIMU;
void setup()
{
Wire.begin();
Serial.begin(38400);
pinMode(intPin, INPUT);
digitalWrite(intPin, LOW);
pinMode(myLed, OUTPUT);
digitalWrite(myLed, HIGH);
byte c = myIMU.readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250);
if (c == 0x71) // WHO_AM_I should always be 0x68
{
myIMU.MPU9250SelfTest(myIMU.SelfTest);
myIMU.calibrateMPU9250(myIMU.gyroBias, myIMU.accelBias);
myIMU.initMPU9250();
} // if (c == 0x71)
else
{
while(1) ;
}
}
void loop()
{
if (myIMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)
{
myIMU.readAccelData(myIMU.accelCount); // Read the x/y/z adc values
myIMU.getAres();
myIMU.ax = (float)myIMU.accelCount[0]*myIMU.aRes; // - accelBias[0];
myIMU.ay = (float)myIMU.accelCount[1]*myIMU.aRes; // - accelBias[1];
myIMU.az = (float)myIMU.accelCount[2]*myIMU.aRes; // - accelBias[2];
}
myIMU.delt_t = millis() - myIMU.count;
if (myIMU.delt_t>20) // waiting some time to get less data
{
if(SerialDebug)
{
Serial.println(myIMU.ay*1000);
}
myIMU.count = millis();
myIMU.sumCount = 0;
myIMU.sum = 0;
}
}
Java code :
package perkusja;
import com.fazecast.jSerialComm.*;
import java.util.Scanner;
import javax.swing.JFrame;
import javax.swing.JSlider;
public class Perkusja {
public static void main(String[] args) {
SerialPort[] ports = SerialPort.getCommPorts();
System.out.println("Select a port:");
int i = 1;
for(SerialPort port : ports)
System.out.println(i++ + ": " + port.getSystemPortName());
Scanner s = new Scanner(System.in);
int chosenPort = s.nextInt();
SerialPort serialPort = ports[chosenPort - 1];
if(serialPort.openPort())
System.out.println("Port opened successfully.");
else {
System.out.println("Unable to open the port.");
return;
}
serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
Scanner data = new Scanner(serialPort.getInputStream());
int counter =0;
while(data.hasNextLine()){
try{
// float kupa = data.nextFloat();
System.out.println(data.nextLine());
System.out.println("Line number : " + counter++);
}catch(Exception e){
System.out.println("ERROR");}
}
}
}
I've been following this tutorial : https://www.youtube.com/watch?v=8B6j_yr9H8g&t=392s
Im using Arduino ProMicro from sparkfun and IMU - MPU9250 also from sparkfun
I will run the program over very slow ssh connection. Will it slow down or block the
System.out.println();
on big loads of printing. So if it prints few gigabytes right into console, but my connection is slow - where undiplayed data will appear? what is the size of tty memory? If I will lose connection for a while - will it run still?
No. PrintWriter does not wait for confirmation of completion.
Will it block the program it tty will have latency
Java's console output is blocking, so potentially your code may block, especially when you writing a lot of data.
what is the size of tty memory?
I am pretty sure that it depends on your kernel, this old thread suggests that it was 4096 bytes at some moment:
I've looked in the kernel code (linux\drivers\char\serial.c) and there is a #define called SERIAL_XMIT_SIZE. At first I thought maybe I could change that but it seems that the transmit buffer is actually fixed to be a memory page (4k).
If I will lose connection for a while - will it run still?
Yes, and if there is no one connected to the tty, then it will run much faster, as it will be able to discard the data.
Also small test application that simulates your use-case.
Echo.java
import java.io.IOException;
public class Echo {
public static void main(String[] args) throws InterruptedException, IOException {
final byte[] data = new byte[Test.BODY_LENGTH + Test.END_MARKER.length];
int index = 0;
outer: while (true) {
data[index++] = (byte) System.in.read();
final int dataOffset = index - Test.END_MARKER.length;
if (dataOffset < 0) {
continue;
}
for (int i = 0; i < Test.END_MARKER.length; i++) {
if (data[dataOffset + i] != Test.END_MARKER[i]) {
continue outer;
}
}
System.out.print(new String(data, 0, index));
return;
}
}
}
Test.java
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class Test {
public static final byte[] END_MARKER = "$TERMINATE$".getBytes();
public static final int BODY_LENGTH = 1024768;
public static void main(String[] args) throws IOException, InterruptedException {
StringBuilder data = new StringBuilder();
for (int i = 0; i < BODY_LENGTH; i++) {
data.append((char) ('a' + ThreadLocalRandom.current().nextInt(('z' - 'a' + 1))));
}
final Process process = new ProcessBuilder("java", Test.class.getPackage().getName() + ".Echo")
.directory(new File("out/production/week 3")) // Change to your output directory
.start();
process.getOutputStream().write(data.toString().getBytes());
process.getOutputStream().write(END_MARKER);
process.getOutputStream().flush();
System.out.println("Written!");
final boolean exitedAfterWroteData = process.waitFor(5, TimeUnit.SECONDS);
System.out.println(exitedAfterWroteData ? "Complete" : "Running"); // Will print running after 5 seconds
int read = 0;
while (process.getInputStream().read() > -1) {
read++;
}
if (read != data.toString().getBytes().length + END_MARKER.length) {
throw new IllegalStateException("Expected echo to print exactly " + BODY_LENGTH + END_MARKER.length + " symbols!");
}
final boolean exitedAfterWeReadData = process.waitFor(50, TimeUnit.MILLISECONDS);
System.out.println(exitedAfterWeReadData ? "Complete" : "Running"); // Will print complete after a few milliseconds
}
}
I'm using QtCreator to deploy C++/Java applications on Android. But I think my problem may not be specific to the way QtCreator deploys the app.
I want to create a C++ library providing a specific functionnality. To do so, the library needs to instantiate a Java class, this last one will be used to do some SDK functions class (for stuff that are not available in the NDK/C++ API).
Creating and using java objects from a C++ program works fine. I package the .java file to the application environment during compilation/deployment and then I can use the Java class via two approachs:
Declare JNI_OnLoad, load class id, method id, and later call them using jni
Use Qt QAndroidJniObject objects (this is specific to QtCreator)
Now the problem comes when I want to create and use java objects from a C++ library. It only works if the .java file is packaged with the top-level application. I could not find a way to package the java with and only with the library itself. Meaning that anyone why needs to use my library will not only have to simply link with the library, but will also need to package the .java file(s) needed by my library. This breaks encapsulation and gives a hard time to the end developer writing programs and simply wanting to load a library and expecting it to embed all it needs to work on its own...
My question is: How can the library embed the java file, so that this java file does not need to be part of the top level program package to let the library use it?
Here is a quick sample: MainWindow constrctor calls 4 functions themselves trying to create and use Java objects. Only the first two calls work...
main.cpp:
#include <QApplication>
#include <QMainWindow>
#include "MyLib.h"
#include <QtAndroidExtras/QAndroidJniObject>
#include "jni.h"
#include <assert.h>
// load java classes from main program
JavaVM* s_javaVM = NULL;
jclass s_classID = 0;
jmethodID s_ctorMethodID = 0;
jmethodID s_callmethodID = 0;
bool loadJava( JNIEnv *env )
{
jclass clazz = env->FindClass("my/FooPrg");
if (!clazz)
{
qCritical("Can't find FooPrg class");
return false;
}
// keep a global reference to it
s_classID = (jclass)env->NewGlobalRef(clazz);
// search for its contructor
s_ctorMethodID = env->GetMethodID(s_classID, "<init>", "()V");
if (!s_ctorMethodID )
{
qCritical("Can't find class contructor");
return false;
}
// search for a method
s_callmethodID = env->GetMethodID(s_classID, "Mult", "(I)I");
if (!s_callmethodID )
{
qCritical("Can't find Mult method");
return false;
}
return true;
}
jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
{
s_javaVM = vm;
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
return -1;
loadJava( env );
return JNI_VERSION_1_4;
}
void callJavaFunctionFromPrgWithQt()
{
if ( QAndroidJniObject::isClassAvailable("my/FooPrg") )
{
QAndroidJniObject obj("my/FooPrg","()V");
if ( obj.isValid() )
{
jint res = obj.callMethod<jint>("Mult", "(I)I", 0x0002);
assert( res == 4 );
}
else
{
assert( false );
}
}
else
{
assert( false );
}
}
void callJavaFunctionFromPrgWithJniLoad()
{
if ( s_classID != 0 && s_ctorMethodID != 0 && s_callmethodID != 0 )
{
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
assert(false);
jobject j_object = env->NewGlobalRef( env->NewObject(s_classID, s_ctorMethodID ) );
jint res = env->CallIntMethod(j_object, s_callmethodID, 0x0002 );
assert( res == 4 );
}
else
{
assert( false );
}
}
class MainWindow : public QMainWindow
{
public:
MainWindow()
{
callJavaFunctionFromPrgWithQt(); // works
callJavaFunctionFromPrgWithJniLoad(); // works
callJavaFunctionFromLibWithQt(); // fails, assert
callJavaFunctionFromLibWithJniLoad(); // fails, because libraries JNI_OnLoad can't find FooLib.java!
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
MyLib.h:
#pragma once
void callJavaFunctionFromLibWithQt();
void callJavaFunctionFromLibWithJniLoad();
MyLib.cpp:
#include "MyLib.h"
#include <QtAndroidExtras/QAndroidJniObject>
#include "jni.h"
#include <assert.h>
// load java classes from main program
JavaVM* s_javaVM = NULL;
jclass s_classID = 0;
jmethodID s_ctorMethodID = 0;
jmethodID s_callmethodID = 0;
bool loadJava( JNIEnv *env )
{
jclass clazz = env->FindClass("my/FooLib");
if (!clazz)
{
qDebug("Can't find FooLib class");
return false;
}
// keep a global reference to it
s_classID = (jclass)env->NewGlobalRef(clazz);
// search for its contructor
s_ctorMethodID = env->GetMethodID(s_classID, "<init>", "()V");
if (!s_ctorMethodID )
{
qDebug("Can't find class contructor");
return false;
}
// search for a method
s_callmethodID = env->GetMethodID(s_classID, "Mult", "(I)I");
if (!s_callmethodID )
{
qDebug("Can't find Mult method");
return false;
}
return true;
}
jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
{
s_javaVM = vm;
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
return -1;
// uncommenting this makes the application crash upon load....
//loadJava( env );
return JNI_VERSION_1_4;
}
void callJavaFunctionFromLibWithQt()
{
if ( QAndroidJniObject::isClassAvailable("my/FooLib") )
{
QAndroidJniObject obj("my/FooLib","()V");
if ( obj.isValid() )
{
jint res = obj.callMethod<jint>("Mult", "(I)I", 0x0002);
assert( res == 4 );
}
else
{
assert( false );
}
}
else
{
assert( false ); // this assertion is reached!
}
}
void callJavaFunctionFromLibWithJniLoad()
{
if ( s_classID != 0 && s_ctorMethodID != 0 && s_callmethodID != 0 )
{
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
assert(false);
jobject j_object = env->NewGlobalRef( env->NewObject(s_classID, s_ctorMethodID ) );
jint res = env->CallIntMethod(j_object, s_callmethodID, 0x0002 );
assert( res == 4 );
}
else
{
assert( false ); // this assertion is reached!
}
}
FooPrg.java:
package my;
import java.lang.Integer;
public class FooPrg
{
public FooPrg()
{
}
public int Mult(int val)
{
return val * 2;
}
}
FooLib.java:
package my;
import java.lang.Integer;
public class FooLib
{
public FooLib()
{
}
public int Mult(int val)
{
return val * 2;
}
}
jniload.pro:
TARGET = jniload
CONFIG += qt resources
QT += core gui widgets
android: QT += androidextras
SOURCES += src/main.cpp
TEMPLATE = app
INCLUDEPATH += ifc
LIBS += \
-l$$OUT_PWD/../../lib/jniload_lib/libjniload_lib.so
ANDROID_EXTRA_LIBS += \
$$OUT_PWD/../../lib/jniload_lib/libjniload_lib.so
ANDROID_PACKAGE_SOURCE_DIR = data/android/root
OTHER_FILES += data/android/root/src/my/FooPrg.java
jniload_lib.pro:
TARGET = jniload_lib
CONFIG += qt resources
QT += core gui widgets
android: QT += androidextras
SOURCES += src/MyLib.cpp
HEADERS += ifc/MyLib.h
TEMPLATE = lib
INCLUDEPATH += ifc
# This does has apparently no effect on library
ANDROID_PACKAGE_SOURCE_DIR = data/android/root
OTHER_FILES += data/android/root/src/my/FooLib.java
Finaly got a way to work this out.
I removed ANDROID_PACKAGE_SOURCE_DIR line from jniload.pro file and hanlde manual copy of the .java files through custom build steps:
custom_jniload_lib_step.target = jniload_lib_mockup.h
custom_jniload_lib_step.commands = $(COPY_DIR) data\android\root ..\..\android-build
QMAKE_EXTRA_TARGETS += custom_jniload_lib_step
PRE_TARGETDEPS += jniload_lib_mockup.h
custom_jniload_step.target = jniload_mockup.h
custom_jniload_step.commands = $(COPY_DIR) data\android\root ..\..\android-build
QMAKE_EXTRA_TARGETS += custom_jniload_step
PRE_TARGETDEPS += jniload_mockup.h
Then, upon deployment, android-build/src contains both FooLib.java and FooPrg.java and then both library and program can access them!
I need to wrap a BSD-like C socket API to Java with JNA. It has basically the same functions as standard BSD socket API.
Wrapping select() is problematic because of the fd_set-structure required in its arguments and the FD_* masking functions (macros) that are needed to handle fd_sets. I tried to crawl through the header files (e.g. sys/select.h in Ubuntu 8.04) but the definitions are not so straightforward. Especially I found it difficult to find the implementation of FD_*-macros, which is needed when wrapping them with JNA's InvocationMapper.
Note: I'm not trying to wrap the standard TCP or unix-socket API, but a custom one. Thus built-in sockets in Java do not fit the bill.
Especially I found it difficult to find the implementation of FD_*-macros, which is needed when wrapping them with JNA's InvocationMapper.
The C pre-processor cpp is useful to find out how macros are expanded. Write a dummy program that uses the relevant macros (it should be lexically correct, but needn't compile), run it through cpp and watch what happens.
I use a byte array for the fd_set structure and some arithmetic to find the right byte position within the array:
private static final int FD_SETSIZE = 1024;
private static final boolean isBigEndian = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
private static interface Libc extends Library {
int select (int nfds, byte[] readfds, byte[] writefds, byte[] errfds, TimeVal timeout);
//...
}
private static class FdSet {
byte[] a;
FdSet() {
a = new byte[FD_SETSIZE / 8]; }
void set (int fd) {
a[getBytePos(fd)] |= getBitMask(fd); }
boolean isSet (int fd) {
return (a[getBytePos(fd)] & getBitMask(fd)) != 0; }
private static int getBytePos (int fd) {
if (fd < 0 || fd >= LibcDefs.FD_SETSIZE) {
throw new RuntimeException("File handle out of range for fd_set."); }
if (isBigEndian) {
return (fd / 8 / Native.LONG_SIZE + 1) * Native.LONG_SIZE - 1 -
fd / 8 % Native.LONG_SIZE; }
else {
return fd / 8; }}
private static int getBitMask (int fd) {
return 1 << (fd % 8); }}
private static class TimeVal extends Structure {
public NativeLong tv_sec;
public NativeLong tv_usec;
TimeVal (int ms) {
set(ms); }
void set (int ms) {
tv_sec.setValue(ms / 1000);
tv_usec.setValue(ms % 1000 * 1000); }
#Override protected List<?> getFieldOrder() {
return Arrays.asList("tv_sec", "tv_usec"); }}
public boolean waitInputReady (int timeoutMs) throws IOException {
TimeVal timeVal = (timeoutMs < 0) ? null : new TimeVal(timeoutMs);
FdSet rxSet = new FdSet();
FdSet errorSet = new FdSet();
rxSet.set(fileHandle);
errorSet.set(fileHandle);
int rc = libc.select(fileHandle + 1, rxSet.a, null, errorSet.a, timeVal);
checkSelectErrors(rc, errorSet);
if (rc == 0) {
return false; }
if (!rxSet.isSet(fileHandle)) {
throw new RuntimeException("rxSet bit is not set after select()."); }
return true; }
public boolean waitOutputReady (int timeoutMs) throws IOException {
TimeVal timeVal = (timeoutMs < 0) ? null : new TimeVal(timeoutMs);
FdSet txSet = new FdSet();
FdSet errorSet = new FdSet();
txSet.set(fileHandle);
errorSet.set(fileHandle);
int rc = libc.select(fileHandle + 1, null, txSet.a, errorSet.a, timeVal);
checkSelectErrors(rc, errorSet);
if (rc == 0) {
return false; }
if (!txSet.isSet(fileHandle)) {
throw new RuntimeException("txSet bit is not set after select()."); }
return true; }
private void checkSelectErrors (int rc, FdSet errorSet) throws IOException {
if (rc == -1) {
throw new IOException("Error in select(), errno=" + Native.getLastError() + "."); }
boolean error = errorSet.isSet(fileHandle);
if (!(rc == 0 && !error || rc == 1 || rc == 2 && error)) {
throw new RuntimeException("Invalid return code received from select(), rc=" + rc + ", error=" + error + "."); }
if (error) {
throw new IOException("Channel error state detected"); }}