Guide to using JNI to make custom libraries?

Hi everyone, our team has developed our own motion profiler library, and I am thinking of re-writing it to use JNI for better performance and RAM usage. I already have some experience with JNI, but I’ve never attempted to use it with Gradle or use it for the roboRIO. I’ve found a few sources online with some snippets of code, but I haven’t been able to get any of them working.

With that said, can anyone offer me a beginner’s guide to using JNI in FRC? Specifically, I’m interested in these questions:

  • How do I modify build.gradle to generate JNI headers and compile JNI?
  • How to cross-compile for the roboRIO?
  • And finally, once I have the .so file with my jar, how do I use it/deploy it to the robot?

(I can build on both Windows 10 and Ubuntu 18.04 LTS)

Any help would be really appreciated. Thanks!

I put together an example of building JNI here with gradlerio.

I can try and add getting it to deploy, but if you add this to your existing java project it’s pretty easy to deploy the jni code.

3 Likes

Thanks! However this isn’t exactly what I’m looking for… The build only works if it’s in a robot project, whereas I want to have it in a standalone library, since I’m trying to convert an existing library project to use JNI… If I try to build it without all the other stuff in a robot project, it gives me errors such as this:

A problem occurred configuring root project 'gradle-jni-test'.
> Exception thrown while executing model rule: JniRules#createJniTasks(ComponentSpecContainer, ProjectLayout)
   > Exception thrown while executing model rule: JniLibrary(JniNativeLibrarySpec) { ... } @ build.gradle line 36, column 9
      > No such property: wpi for class: edu.wpi.first.jni.JniNativeLibrarySpec

How do I make this work without a robot project?

You still need to include gradlerio. That’s the only way to get the Rio cross compiler to work. We also have a lot of jni utilities in wpiutil, so including that is a good idea, and why that project I linked has it.

You don’t need any of the deploy code added though, its not necessary. That example I posted is standalone.

I see. However for some reason applying the GradleRIO plugin doesn’t work for me (this is why I commented it out the previous time). I got this error:

* What went wrong:
An exception occurred applying plugin request [id: 'edu.wpi.first.GradleRIO', version: '2019.2.1']
> Failed to apply plugin [class 'jaci.gradle.deploy.DeployPlugin']
   > Could not create an instance of type jaci.gradle.deploy.DeployExtension.
      > java.lang.NullPointerException (no error message)

Gradlerio only works with Gradle 5.0. That error happens with 5.2, and it’s not possible for us to be compatible with both. So you need to change your wrapper to 5.0

1 Like

That worked. Thanks!

Yeah. If you want any explanations on our JNI utilities feel free to ask. For things like arrays and strings they are incredibly helpful, along with callbacks into Java if necessary.

Also, if you cloned my repo it should just automatically get intellisense for both C++ and Java in vscode as long as you have the wpilib extension installed. If not, just the .wpilib folder are needed to enable that functionality in vscode.

1 Like

Everything went well so far. Thanks for the help. Now that I have an so file for the JNI code, what’s the best way to install/deploy the library? Do I have to manually ssh into the rio and copy the dynamic library to /usr/lib, or is there a better way?

There are 2 ways. First, you could deploy it to maven with our format and set it up as a vendor dependency. This is likely too much. The 2nd option is to store it local to your project, and add the following to your dependencies block

nativeLib files('file path here')

And then gradlerio will deploy it properly.

1 Like

Perfect. Thanks!

I know it’s been a while, but I was wondering is there a way to add additional compiler arguments? (e.g. things like ffast-math) And what is the optimization level used?

For compiler options, you can add them like the following. Put the block inside the model block in build.gradle

    binaries {
        all {
            if (targetPlatform.name == wpi.platforms.roborio) {
                cppCompiler.args << '-Wall' << '-Wextra' << '-ffast-math'
            }
        }
    }

For optimizations, -Og is used for debug, and -O2 is used for release. All the other options used can be found here

1 Like

Thank you!

@Thad_House I would also like to explore the first option of deploying a maven repo and setting it up as a vendor dependency. Is there any documentation on this? In particular, I am trying to figure out what the uuid from the vendordep.json is: how is it created and where does it get used? I have noticed how this does not get updated in open-source projects, so it must not be some kind of a hash based on the files in the repo.

The uuid is actually just randomly generated. It can be whatever you want it to be. All it is used for is when VS Code checks for updates, it uses the UUID to determine if it is the same vendor library or a different one. The name was not used, in case the vendor wanted to change their name, but still be considered the same library. You can just use a random online UUID generator, it doesn’t actually pertain to any of the files in the repo.

Thank for that. If one has another project with JNI java code and its respective cpp .so libraries, how does one put this into the vendordeps json? What is the difference between the “jniDependencies” and the “cppDependencies”? I haven’t been able to find a good open-source project that has compiled jni libraries. I tried to use the CTRE libraries as an example, but the “jniDependencies” are hard to sort out from their “cppDependencies”.

cppDependencies are used exclusively for C++ projects. If deploying a java project they will not get used. jniDependencies are anything native needed for a java library, including any non wpilib dependencies needed by the native library.

Thats why in CTRE you see things duplicated, they serve completely separate purposes. They combine their JNI and low level C library into a single library, so both C++ and JNI need that library, but thats only because of how they wrote it.

If you just have JNI you can leave the cppDependencies array empty.

Thank you for your help. I think that I have this dialed in now. On the last point, I think that the thing that was confusing me the most was the field “isJar”. What is the purpose of this field? I wouldn’t think that the JNI shared object library would ever be packaged up into a jar file.

When packaged into maven, the shared library needs to be packed into either a zip or a jar, with the correct classifier and folder layout (put the library in /linux/athena/shared in the archive, and have the classifier be -linuxathenadebug ). That flag tells Gradle if it’s in a jar or a zip. It doesn’t matter which one, it’s just Gradle can’t automatically detect.

1 Like