#java #c #gradle #dll #java-native-interface
Вопрос:
Я актуализировал образец, приведенный в руководстве веб-сайта IDEA по JNI в этом репозитории (вы можете увидеть там вилку), и теперь он работает с современными тестами JDK и JUnit даже на моем Mac aarch64 (надеюсь, он работает где-нибудь еще). Но я действительно не понимаю, что происходит build.gradle
в hello
папке подпроекта Gradle. Я могу распознавать аргументы компилятора там, но параметры включения задаются в отдельных методах, что приводит меня в замешательство.
Моя цель состоит в том, чтобы иметь только C-файл-оболочку, который выполняет dlopen
(или его альтернативу Windows) и запускает некоторые функции из динамической библиотеки.
Напр.
<ProjectRoot>/hello/src/main/c/ wrapper-with-JNI-call-convensions.c
libsomelib.dylib
libsomelib.so
somelib.dll
Calls looks like this: Java -[JNI]-> wrapper.dll -> somelib.dll
^ ^ ^
| | |
building wrapper.c using Gradle to this
Таким образом, мне успешно удалось заставить JNI работать (не меняясь build.gradle
по сравнению с образцом), если я просто запущу сборку с двумя исходными файлами C (оболочка, которая использует исходный код JNI и C моей библиотеки). Затем я построил dylib
с помощью встроенного clang
компилятора macOS и написал правильный (я думаю) dlopen
и dlsym
код. Но когда я создаю проект, я получаю NULL, возвращенный из dlopen
вызова.
Я попытался добавить некоторые аргументы к build.gradle
тому, что использовалось для компиляции двоичного файла, использующего динамическую библиотеку, но это не помогло.
Мой тестовый запуск завершается здесь:
// my wrapper
void* dlHandle = dlopen("libsomelib.dylib", RTLD_LAZY);
if (dlHandle == NULL) {
printf("DLL NOT OPENED !!!n");
exit(0);
}
Я знаю, что мне нужно писать правильные вызовы, зависящие от платформы, в своей оболочке, для меня это не имеет большого значения.
Итак, можете ли вы сказать мне, как я могу правильно заставить это работать с помощью Gradle? Помните, что цель состоит в том, чтобы в моем случае была только готовая *.c
оболочка JNI и только динамические связанные библиотеки *.so amp; *.dylib amp; *.dll
.
Обновление: Я решил не использовать Gradle, я использую VSCode tasks.json
, он в 20 раз более интуитивно понятен. Я напишу свой подход как самостоятельный ответ.
Комментарии:
1. Я не уверен, что это то, что вы ищете: github.com/mkowsiak/jnicookbook/tree/master/recipes/recipeNo018 , но он делает то же самое.
2. @Oo.oO ты прав. Это то, что мне нужно, но я пытался передать эти аргументы таким
compileTask.compilerArgs.addAll(["-x", "c","-Wl,-rpath, @loader_path" ])
образом, и это не работает. Также я не знаю, почему в примере нет никаких аргументов для построения библиотеки, есть аргументы just-x c
и include-paths, и это каким-то образом создает этот dylib.3.
-rpath
на macOS может быть сложно. Вы также задали имя библиотеки. Вот почему в образце у вас есть:-install_name libReloadLib.$(EXT)
. Если у вас уже есть библиотека, возможно, потребуется изменить ее имя. Вы можете найти образец здесь:https://www.owsiak.org/setting-up-googletest-on-macos/
4. Здесь возникает еще один вопрос. Тебе вообще нужно звонить
dlopen
? Может быть, вы можете просто следовать этой схеме: github.com/mkowsiak/jnicookbook/tree/master/recipes/recipeNo023 — в этом случае вы просто связываете все вместе, и вам вообще не нужно звонитьdlopen
.5. @Oo.oO Я не практиковал создание библиотек на macOS, и я подумал, что
dlopen/LoadLibrary
это необходимо для использования функций из динамических связанных библиотек в каждой ОС. Но на самом деле я могу просто скомпилировать.c
и.dylib
на macOS и просто использовать функцию, определенную в заголовке?
Ответ №1:
Чтобы запустить его, вы можете попробовать следующее:
> cc -g -shared -fpic wrapper.c -L. -lsome -o libwrapper.dylib
Я предполагаю, что у вас есть libsome.dylib
внутри текущего каталога. Затем вы можете загрузить libwrapper.dylib
его внутрь JNI
, и он будет ссылаться на эту другую библиотеку: libsome.dylib
Обновить
Допустим, у вас есть эти два файла:
#include <stdio.h>
void anotherFunction () {
// we are printing message from another C file
printf ("Hello from another function!n");
}
и соответствующий файл заголовка
#ifndef another_h__
#define another_h__
void anotherFunction (void)
#endif // another_h__
Вы можете создать из него библиотеку, вызвав что-то вроде этого:
cc -shared -fpic recipeNo023_AnotherFunction.c -o libanother.dylib
Это будет эквивалентно вашему: somelib
Тогда у нас может получиться что-то вроде этого:
#include "recipeNo023_redux_HelloWorld.h"
void anotherFunction ();
JNIEXPORT void JNICALL Java_recipeNo023_redux_HelloWorld_displayMessage
(JNIEnv * env, jclass obj) {
printf ("Hello world!n");
/* We are calling function from another source */
anotherFunction ();
}
Мы объявляем функцию и используем ее. Имя будет определено позже, на этапе связывания.
Теперь вы можете скомпилировать свою wrapper
библиотеку. В этом образце это так HelloWorld
.
cc -shared -fpic recipeNo023_redux_HelloWorld.c -L. -lanother
Теперь, внутри JNI
, все, что ты делаешь, это:
System.loadLibrary("HelloWorld");
В связи с тем HelloWorld
, что он был связан с общей библиотекой another
, у него будет доступ к ее символам. Таким образом, вам не нужно использовать dlopen
.
Попробуйте пример (есть даже файл на основе Docker, так что вам не придется бороться с настройкой среды).
Комментарии:
1. Итак, — нужно ли мне
dlopen
в этом случае? — нужен ли мне заголовок функции, определенной в libsome?2. Здесь тебе это не нужно
dlopen
.3. На самом деле, я решил избавиться от этой сборки Gradle, потому что я не понял, как правильно передавать аргументы через это. Я построил, как вы сказали, с некоторыми дополнительными аргументами, и это работает! Спасибо! Но могу ли я спросить
.so
, можно ли использовать Linux таким же образом? Я имеюdlopen
в виду вообще без. И где я могу прочитать о том, в чем разница?4. Да, это точно так же. Взгляните на
Makefiles
это . Вы заметите, что один и тот же код работает на обоих:macOS
иLinux
. Единственное различие заключается в расширении файла:so
дляLinux
иdylib
дляmacOS
. Кроме того, в некоторых случаях есть небольшие корректировки аргументов, но механизм довольно похож.5. Как я понимаю, библиотека-оболочка динамически загружается в JVM через
dlopen
, но другая библиотека динамически не загружается в оболочку, верно?
Ответ №2:
Как упоминал @Oo.oO, этот рецепт-именно то, что мне нужно. Это подход с динамической нагрузкой, и он работает на Mac и Linux. Кроме того, как он упомянул, существует подход статического связывания, который продемонстрирован здесь и в его ответе.
Я бы также добавил в это некоторые параметры компилятора:
-O3 # for optimisation of speed
-x c # to explicitly set language to C, in my case
-fvisibility=default # to export only that I need (read below)
-arch x86_64 # option ONLY for macOS built-in clang in case you want build Intel binary on Silicon Mac
В исходном коде lib вы также должны определить
#define EXPORT __attribute__((visibility("default")))
а затем напишите его в заголовке перед каждым объявлением функции или в исходном файле перед каждым определением функций, которые вы хотите использовать извне или в библиотеке.
напр.
// somelib.h
EXPORT void somefunc(int a, int b);
// somelib.c
#include "somelib.h" // somelib.c and somelib.h in same directory
void somefunc(int a, int b) { ... }
или
// in case you don't want to use header (I recommend to use)
// somelib.c
EXPORT void somefunc(int a, int b) { ... }
Этот ответ будет расширен в случае Windows позже.