Как создать библиотеку DLL JNI, которая вызывает функцию из другой библиотеки DLL? — ДЖНИ, Грэдл

#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 позже.

Краткие сведения

macOS clang и Linux gcc