LINUX.ORG.RU

Java c Custom Qt Library под капотом

 ,


0

1

Крашится Java класс с динамической библиотекой под капотом. Библиотека основана на QCoreAppliacation. Помогите собрать, если это возможно?

$ cat src/wrapper/Client.java 
package wrapper;

import java.io.IOException;

public class Client {

    native long create();
    native void start(long ptr);

    static {
        System.load(System.getProperty("user.dir")+"/bin/libclient.so");
    }

     public static void main(String[] args) throws Exception{

        Client client = new Client();
        long ptr = client.create();
        client.start(ptr);

        char ch;
        ch = (char) System.in.read();
    }
    
}

Заголовочный JNI файл сгенерирован был автоматически

$ cat bin/wrapper_Client.h 
#include <jni.h>

#ifndef _Included_wrapper_Client
#define _Included_wrapper_Client
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jlong JNICALL Java_wrapper_Client_create
  (JNIEnv *, jobject);

JNIEXPORT void JNICALL Java_wrapper_Client_start
  (JNIEnv *, jobject, jlong);

#ifdef __cplusplus
}
#endif
#endif

Реализация JNI:

$ cat bin/wrapper_Client.cpp 
#include "wrapper_Client.h"
#include "qlibrary.h"


JNIEXPORT jlong JNICALL Java_wrapper_Client_create (JNIEnv *, jobject)
{
    jlong jresult = 0;
    Controller *controller;
    runApp(controller);
    *(Controller**) &jresult = controller;
    return jresult;
}


JNIEXPORT void JNICALL Java_wrapper_Client_start (JNIEnv *, jobject, jlong jptr)
{
    Controller *controller;
    controller = *(Controller **) &jptr;
    start(controller);
    
}

Команда сборки

g++ -std=c++11 -shared -fPIC -DQLIBRARY_LIBRARY -DQT_DEPRECATED_WARNINGS -DQT_NO_DEBUG -DQT_CORE_LIB -I. -isystem /usr/include/x86_64-linux-gnu/qt5 -isystem /usr/include/x86_64-linux-gnu/qt5/QtCore -I. -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++ -o libclient.so  -I/usr/lib/jvm/java-11-openjdk-amd64/include/ -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux/ wrapper_Client.cpp -lqlibrary -lQt5Core -lpthread

Имеется библиотека libqlibrary.so. Она установлена в системе и в С++ приложении нормально работает.

class Controller;

void runApp(Controller*&);
void start(Controller*&);
...

void runApp(Controller* &controller)
{
    std::thread th
    (
        [&]
        {
            int argc = 0;
            char *argv = nullptr;
            QCoreApplication app(argc, &argv);
            controller = new Controller;
            app.exec();
        }
    );
    th.detach();
}


void start(Controller*& controller)
{
    controller->start();
}
...

Запустить байт код не удается:

$ java -classpath bin wrapper.Client
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f4c9a4d08f4, pid=26982, tid=26984
#
# JRE version: OpenJDK Runtime Environment (11.0.7+10) (build 11.0.7+10-post-Ubuntu-2ubuntu218.04)
# Java VM: OpenJDK 64-Bit Server VM (11.0.7+10-post-Ubuntu-2ubuntu218.04, mixed mode, sharing, tiered, compressed oops, g1 gc, linux-amd64)
# Problematic frame:
# V  [libjvm.so+0x7178f4]

* В функцию runApp вы передаете ссылку на стековый объект.
* В функции эта ссылка захватывается лямбдой и «уезжает» в отдельный поток.
* Сразу же после этого runApp завершается, следом завершается Java_wrapper_Client_create. В этот момент захваченная ссылка стоновится совсем невалидной. Java_wrapper_Client_create возвращает значение из неинициализированной переменной controller.
--
* Когда выполнение доходит до присвоения значения объекту по ссылке, эта ссылка уже невалидна.

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 1)

controller = *(Controller **) &jptr;

Вот опять. Больше звёздочек богу звёздочек.

Controller *controller;
controller = (Controller*)jptr;
start(controller);
ox55ff ★★★★★
()
Ответ на: комментарий от trex6

Да понамудрил со ** при попытке запустить. Уже давно сижу с этим.

Подправил, вроде теперь ссылка на участок памяти с controller отдается от create к start.


JNIEXPORT jlong JNICALL Java_wrapper_Client_create (JNIEnv *, jobject)
{
    jlong jresult = 0;
    Controller *controller;
    runApp(controller);
    jresult = (jlong)controller;
    return jresult;
}


JNIEXPORT void JNICALL Java_wrapper_Client_start (JNIEnv *, jobject, jlong jptr)
{
    Controller *controller;
    controller = (Controller*) jptr;
    start(controller);
    
}

Теперь приложение не крашится. Правда не понятно работает или нет. Для того, чтобы наблюдать работу в коде имеются вставки в слотах

qDebug() << "...";
Почему-то ничего не печатается.

Кстати, в ++ номармально работает, ссылка же здесь тоже уезжает в отдельный поток.

#include <iostream>
#include "qlibrary.h"

int main()
{
    std::cout << "press key 's' for start, 'f' for finish\n";
    char ch;
    Controller *controller;
    runApp(controller);
    while(ch != 'f')
    {
        std::cout << getNumber(controller) << std::endl;
	std::cout << "key: ";
	std::cin >> ch;
	if (ch == 's' ) start(controller);
    }
}

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Вроде указатели то правильно передаются. Но, почему-то сигнально-слотный механизм не заводится.

public class Client {
    native long create();
    native void start(long ptr);
    native int getNumber(long ptr);
    static {
        System.load(System.getProperty("user.dir")+"/bin/libclient.so");
    }
    public static void main(String[] args) throws Exception{
        Client client = new Client();
        long ptr = client.create();
        System.out.print("ptr = ");
        System.out.print(ptr);
        client.start(ptr);
        char ch;
        ch = (char) System.in.read();
	int n;
        n = client.getNumber(ptr);
        System.out.print(n);
        ch = (char) System.in.read();
    }
    
}

$ java -classpath bin wrapper.Client jresult = 140028296803600 ptr = 140028296803600jptr = 140028296803600 start f jptr = 140028296803600 0

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Нет, крашится. Поставил println и все накрылось

$ java -classpath bin wrapper.Client
jresult = 140408190457104
ptr = #
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007fb304003600, pid=29219, tid=29220
#
# JRE version: OpenJDK Runtime Environment (11.0.7+10) (build 11.0.7+10-post-Ubuntu-2ubuntu218.04)
# Java VM: OpenJDK 64-Bit Server VM (11.0.7+10-post-Ubuntu-2ubuntu218.04, mixed mode, sharing, tiered, compressed oops, g1 gc, linux-amd64)
# Problematic frame:
# C  0x00007fb304003600

Похоже, действительно, после ухода в поток указатели становятся не валидными ... Но, почему же в С++ приложении все работает.

cppprogger
() автор топика

я не большой специалист в жаве, но если твой код каким-то боком из pthread-потоков дергает jvm – то надо обязательно дернуть AttachCurrentThread / DetachCurrentThread

waker ★★★★★
()
Ответ на: комментарий от cppprogger

после ухода в поток указатели становятся не валидными

Не указатель, а ссылка. Вам важно понять - почему, в своем первом посте я достаточно подробно описал.

К джаве никакого отношения не имеет и в С++ будет точно так же падать.

P.S. А еще неплохо бы «докрутить» happens before и обложить доступ к controller во всех потоках атомиками/мьютексами/вашими любимыми примитивами синхронизации.

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 2)
Ответ на: комментарий от trex6

* В функцию runApp вы передаете ссылку на стековый объект.
* В функции эта ссылка захватывается лямбдой и «уезжает» в >отдельный поток.
* Сразу же после этого runApp завершается, следом завершается Java_wrapper_Client_create. В этот момент захваченная ссылка стоновится совсем невалидной. Java_wrapper_Client_create возвращает значение из неинициализированной переменной controller.
--
* Когда выполнение доходит до присвоения значения объекту по ссылке, эта ссылка уже невалидна.

trex6 подскажите, вот такой подход «объявить переменную глобальной в модуле main» является решением проблемы:

int main()
{
    Controller *controller;
    runApp(controller);
    // ...
?

Если распечатать в консоль значение controller, то в main и лямбде оно одинаковое.

cppprogger
() автор топика
Ответ на: комментарий от trex6

Да, спасибо! Как грубое решение прокатило во всем модуле wrapper_Client.cpp завести глобальный указатель Controller*

Controller *controller;

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Ты сделал костыль. Твою проблему правильно описал trex6. У тебя Java_wrapper_Client_create завершается до того, как отработает код в отдельном потоке. Ты вынес переменную в глобальную область видимости. Теперь нет записи в бывшую локальную переменную. И вроде как всё работает. Но это иллюзия. Система тебе не даёт гарантий через какое время будет запущен поток. Ты в java отдаёшь переменную, которая ещё не инициализирована кодом из потока. Там даже не nullptr, а мусор. Если у тебя на компе процессор будет загружен, то поток может не успеть инициализировать переменную controller и опять всё перестанет работать.

Я так понял, ты создаёшь поток, чтобы крутить в нём цикл сообщений QCoreApplication. Я никогда не смешивал java и Qt, поэтому не подскажу как правильно это делать. Читай маны. Но костыль с глобальной переменной работать не будет.

Ещё. Ты отсоединяешь поток через метод detach. Но у тебя нет кода нормального завершения потока. Как при выходе из программы у тебя завершается цикл сообщений QCoreApplication? Тебе по хорошему нужно при выходе корректно завершить QCoreApplication, а потом подождать, пока твой поток завершится.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

А так?

void runApp(Controller* &controller)
{
    Controller* ptr = nullptr;
    std::thread th
    (
        [&]
        {
            int argc = 0;
            char *argv = nullptr;
            QCoreApplication app(argc, &argv);
            ptr = new Controller;
            QObject::connect(ptr, &Controller::kaput, &app, &QCoreApplication::quit, Qt::QueuedConnection);
            app.exec();
        }
    );
    th.detach();
    while(!ptr)
    {
        QThread::usleep(100);
    }
    controller = ptr;
}

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Это всё равно сверх костыльно. У тебя два потока получают одновременный доступ к переменной. Так писать без синхронизации нельзя.

Разве в интернете нет статей как правильно дружить Qt и java? Надо делать нормально, чтобы потом баги не разгребать.

ox55ff ★★★★★
()
Ответ на: комментарий от cppprogger

Такой подход будет работать в некоторых случаях. Возможно, даже очень часто.

Но у вас все еще остается проблема с «happens before» между потоками.

Вам необходимо разобраться с правильными подходами при разработке кода для многопоточной среды.

Я рекомендую обратить внимание вот на эту книжку https://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/19339...

Книга есть на русском языке и помогает понять основы многопоточного программирования в С++.

P.S. С точки зрения архитектуры, любое решение с глобальными переменными «пахнет плохо».

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 1)
Ответ на: комментарий от ox55ff

Так Java сейчас не причем, и Qt тоже. Си-шые обертки дружать Java и С++. Но, с указателем на класс я подтормаживаю, т.к. решение ждать в цикле пока поток не задеттачиться (и не запишет значение в указатель) конечно же костыльное. Но, оно сделано в надежде, что далее этот указатель будет только читаться.

Возможно нужно сделать глобальный указатель типа std::atomic и тогда получим потокобезопастный вариант.

cppprogger
() автор топика
Ответ на: комментарий от trex6

Спасибо большое за книгу.

Ну и похоже, да, нужно загонять указатель в класс и делать его атомиком или мьтекс вешать на него. А потом к этому классу писать Сишные обертки для Явы )))

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Так Java сейчас не причем, и Qt тоже.

Зачем тогда отдельный поток? Выкинь его. И не делай app.exec(); раз тебе цикл событий Qt не нужен.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

я имел ввиду, что косяк у меня при стыковке С++ и Си.

cppprogger
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.