Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load and use native c code in a lein project?

Problem
I'm unable to load and call methods in a compiled c class into a leiningen project. My basic approach is to load a Java class, JavaWrapper.java, that uses JNI to call some native methods in the native code, wrapper.o and then call the methods through this java wrapper class.
I imagine there are classLoader issues with loading a java class which loads the native code from a clojure project, but given that I can't seem to directly get clojure code to find the wrapper.o on the library path, I'm not sure how to handle this.

lein project file

(defproject lein-native-test "0.1.0-SNAPSHOT"
...
:java-source-paths ["java-path"]
:jvm-opts ["-Djava.library.path=.:./native:/absolute/path/to/native"] ;;not sure what format it wants
)

clojure file with main method
I've tried it slightly modified with four approaches, all included in code below along with respective error in comments.

(ns lein-native-test.core
(:import (com.test JavaWrapper)))
(def -main []
;;four things I've tried and their errors
(clojure.lang.RT.load "/abs/path/to/wrapper.o") ;;could not find file /abs/path/wrapper.o_init.class or wrapper.o.clj
(clojure.lang.RT.loadLibrary "wrapper.o") ;;UnsatisfiedLinkError no wrapper.o in java library path
(JavaWrapper/load "/abs/path/to/wrapper.o") ;;UnsatisfiedLinkError com.test.JavaWrapper.setup()
(assembly-load "/abs/path/to/wrapper.o") ;;unable to resolvesymbol: assembly-load
)

Java code with native methods that uses JNI, JavaWrapper.java

public class JavaWrapper{
    public native void setup();
    public static void load(String lib){ System.load(lib);}
}

Before trying to get this to work with clojure and lein I did successfully load and use the native methods in wrapper.o via JavaWrapper and JNI.

Possibly related:
I'm also unable to load wrapper.o in JavaWrapper.java via

System.loadLibrary("wrapper.o");

I have to use

System.load("/absolute/path/to/wrapper.o");

Versions of tools
clojure version: 1.5.1
lein version: 2.3.4
jdk: 1.7
os: debian7

A better understanding of ClassLoaders or especially a working simple example would be very useful, thanks.

like image 401
user1854496 Avatar asked Jun 23 '14 11:06

user1854496


1 Answers

The problem was due to an naming error in my method in the C header and source files per the jni standard. The correct way to use jni with clojure is to create a Java wrapper class as I have done and load the dynamic library with the method clojure.lang.RT.loadLibrary
Since I've had trouble finding good examples for this I've made a demo on github

Errors
1) (clojure.lang.RT.load "/abs/path/to/wrapper.o") ;;could not find file /abs/path/wrapper.o_init.class or wrapper.o.clj
This load method is not meant to be used for native code, its expecting a java class or a clj file

2) (clojure.lang.RT.loadLibrary "wrapper.o") ;;UnsatisfiedLinkError no wrapper.o in java library path
Clojure is unable to find the library at link time, hence UnsatisfiedLinkError --- this is due to a naming error

  • first the library should be compiled as a dynamic shared library, i.e. for the gcc compiler use the -shared flag (I actually did but didn't name the output file with the normal .so extension)
  • java and thus clojure expect the native library to be named in a very specific way: libwrapper.so (or .jnilib for mac or .dll for windows but always with "lib" prefix)

3) (JavaWrapper/load "/abs/path/to/wrapper.o") ;;UnsatisfiedLinkError com.test.JavaWrapper.setup()
This time the error is on a method within JavaWrapper in the file or library, thus you know that at least it found the file. An UnsatisfiedLinkError that specifies a specific method in the Java class, like this one, should always be due to a naming error between what is declared a native method in the Java file and what is actually present in the c source or header file.
Note the namespace "com.test"
When declaring a jni method in c the method name must follow a specific format,
from http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html
"Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:"
  • the prefix Java_
  • a mangled fully-qualified class name
  • an underscore (_) separator
  • a mangled method name
  • for overloaded native methods, two underscores (__) followed by the mangled argument signature

In this case the full c source method signature would be

void Java_com_test_setup(JNIEnv *env, jobject obj)


4) (assembly-load "/abs/path/to/wrapper.o") ;;unable to resolvesymbol: assembly-load
This method too is not meant to load native code

like image 145
user1854496 Avatar answered Oct 20 '22 11:10

user1854496