简体   繁体   中英

How to build JNI DLL that calls function from another DLL? - JNI, Gradle

I have actualised the sample given on IDEA website's guide on JNI in this repo (you can see fork there) and now it runs with modern JDK and JUnit Tests even on my aarch64 Mac (hope it runs anywhere else). But I do not really understand what is going on in build.gradle in hello Gradle subproject folder. I can recognise compiler arguments there, but include options are set in separate methods, which gets me confused.

My aim is to have just wrapper C-file that doing dlopen (or its Windows' alternative) and runs some function from dynamic library.

Eg

<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

So I have successfully managed to get JNI working (while not changing build.gradle from sample) if I just run build with two C source files (wrapper that uses JNI and C source code of my library). Then I built dylib using macOS built-in clang compiler and wrote proper (I guess) dlopen and dlsym code. But when I build project I get NULL returned from dlopen call.

I have tried to add some arguments to the build.gradle that used to compile binary that uses dynamic library, but it did not help.

My test running exits here:

// my wrapper
void* dlHandle = dlopen("libsomelib.dylib", RTLD_LAZY);
    if (dlHandle == NULL) {
        printf("DLL NOT OPENED !!!\n");
        exit(0);
    }

I know I have to write proper platform-dependent calls in my wrapper, it's not a big deal for me.

So can you tell me how I can properly get this working using Gradle? Remember that goal is to have just JNI-ready *.c wrapper and just dynamic linked libraries *.so & *.dylib & *.dll in my case.

Update: I've decided not to use Gradle, I use VSCode tasks.json , it is like 20 times more intuitive. I will write my approach as self-answer.

To get it running you can try following:

> cc -g -shared -fpic wrapper.c -L. -lsome -o libwrapper.dylib

I assume that you have libsome.dylib inside current directory. Then you can load libwrapper.dylib inside JNI and it refer to this another lib: libsome.dylib

Update

Let's say you have these two files:

#include <stdio.h>

void anotherFunction () {
  // we are printing message from another C file
  printf ("Hello from another function!\n");
}

and corresponding header file

#ifndef another_h__
#define another_h__

void anotherFunction (void)
#endif                          // another_h__

You can build a library out of it, by calling something like this:

cc -shared -fpic recipeNo023_AnotherFunction.c -o libanother.dylib

This will be equivalent of your: somelib

Then, we can have something like this:

#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 ();
}

We declare function and use it. Name will be resolved later, during linking phase.

Now, you can compile your wrapper lib. In this sample it's HelloWorld .

cc -shared -fpic recipeNo023_redux_HelloWorld.c -L. -lanother

Now, inside JNI , all you do is:

System.loadLibrary("HelloWorld");

Due to the fact HelloWorld was linked with shared library another it will have access to its symbols. So, you don't need to use dlopen .

Try the sample (there is even Docker based file so you don't have to struggle with setting up environment).

As @Oo.oO mentioned this recipe is exactly what I need. It's dynamic load approach and it works on Mac and Linux. Also, as he mentioned there is static linking approach which is demonstrated here and in his answer.

I would also add some compiler options into this:

-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

In lib's source code you should also define

#define EXPORT __attribute__((visibility("default")))

and then write it in header before each function declaration or in source file before each definition for functions that you want to be used from outside or lib.

eg

// 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) { ... }

OR

// in case you don't want to use header (I recommend to use)
// somelib.c
EXPORT void somefunc(int a, int b) { ... }

This answer will be advanced with Windows cases later.

Summary

macOS+ clang & Linux+ gcc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM