2

I'm working on a project for school using JNI. In a nutshell, I have gotten the first part working using the shared library and JNI, but the second part (which refers to the same library) gives an UnsatisfiedLinkError every time I try to run the code. My professor and TAs are all unable to figure out this problem and I have been trying to debug it for over a week now.

The first part is a .java program that will be executed independently, given a file of integers and a frequency, it should call native code in C to compute a direct fourier transform and return a value given a frequency. In java, I stored these ints into an array and passed the array to JNI to compute the DFT in C. This returns a single int value and works like a charm.

For the second part, we have created a gui interface which loads the JNI library and takes in a .wav file or a file of our own format. This gui is required to calculate the frequency of a file when selected from a file chooser. Since the .wav files (and the other type) have header information as well as binary data, rather than integer numbers as in the first part, my strategy is to pass the filename (as a string) to JNI so that my native code can dissect the int values into an array itself and then calculate the frequency of the file. My gui is complete, and the action event of selecting a file from the chooser is supposed to call the JNI code and return the frequency of the file. Interestingly, the code compiles in linux and loads the library successfully, but crashes with this link error when the file is chosen and the native code is supposed to be called using the file chooser. I am at a complete and total frustrated defeat. Any ideas?

HERE IS THE HEADER FILE GENERATED BY JNI

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class dft */

#ifndef _Included_dft
#define _Included_dft
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     dft
 * Method:    computeDFT
 * Signature: ([III)I
 */
JNIEXPORT jint JNICALL Java_dft_computeDFT
  (JNIEnv *, jobject, jintArray, jint, jint);

/*
 * Class:     dft
 * Method:    computeFileDFT
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_dft_computeFileDFT
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

HERE IS THE CODE THAT WORKS

public class dft
{
private native int computeDFT(int[] nums, int count, int freq);
private native int computeFileDFT(String fileName);

static
{
    /* 
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     * SYSTEM LOAD LIBRARY PATH BELOW
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     */
    System.loadLibrary("dft");
}

/**
 * @param args
 * @throws IOException 
 */
public static void main(String[] args) throws IOException
{
    File infile;
    Scanner sc = null;
    dft dft = new dft();
    int count = 0;
    int[] data = new int[4096];

    //code that reads the file and puts the content into an array called 'data'
            //the args[1] below is the specified frequency to be calculated in JNI

    int dftValue = dft.computeDFT(data, count, Integer.parseInt(args[1]));
    System.out.println(dftValue);
}

HERE IS THE PROBLEM CODE

private native int computeFileDFT(String fileName);

static
{
    /* 
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     * SYSTEM LOAD LIBRARY PATH BELOW
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     */
    System.loadLibrary("dft");
    System.out.println("loaded successfully");
}

    //imagine more code here creating a JPanel object,
    //adding a bunch of stuff to it, etc. 
    //One key item that is added is a button, 
    //which when pressed opens a JFileChooser object, 
    //below you will see my implementation of the 
    //ActionListener which listens to the button and is 
    //supposed to execute the native code when the file is chosen.

private class TunerButtonListener implements ActionListener
{
    //@Override
    public void actionPerformed(ActionEvent event)
    {
        int returnVal = dialog.showOpenDialog(fileButton);
        if(returnVal == JFileChooser.APPROVE_OPTION) 
        {
            currentFileName = dialog.getSelectedFile().getName();
            currentFilePath = dialog.getSelectedFile().getPath();
            message.setText(currentFileName);


            int val = computeFileDFT(currentFilePath);
                    //more code goes here that does more stuff

THE FULL ERROR MESSAGE AND STACK

Exception in thread "AWT-EventQueue-0" java.lang.UnsatisfiedLinkError: TunerPanel.computeFileDFT(Ljava/lang/String;)I
at TunerPanel.computeFileDFT(Native Method)
at TunerPanel$TunerButtonListener.actionPerformed(TunerPanel.java:167)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2012)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2335)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:404)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
at java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:289)
at java.awt.Component.processMouseEvent(Component.java:6389)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3268)
at java.awt.Component.processEvent(Component.java:6154)
at java.awt.Container.processEvent(Container.java:2045)
at java.awt.Component.dispatchEventImpl(Component.java:4750)
at java.awt.Container.dispatchEventImpl(Container.java:2103)
at java.awt.Component.dispatchEvent(Component.java:4576)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4633)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4297)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4227)
at java.awt.Container.dispatchEventImpl(Container.java:2089)
at java.awt.Window.dispatchEventImpl(Window.java:2518)
at java.awt.Component.dispatchEvent(Component.java:4576)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:672)
at java.awt.EventQueue.access$400(EventQueue.java:96)
at java.awt.EventQueue$2.run(EventQueue.java:631)
at java.awt.EventQueue$2.run(EventQueue.java:629)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:116)
at java.awt.EventQueue$3.run(EventQueue.java:645)
at java.awt.EventQueue$3.run(EventQueue.java:643)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:642)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)
turkycat
  • 35
  • 7
  • Post the full exception message. It looks to me like you've declared the native method(s) again in a different class, instead of using them from the class they were originally declared in, for which a native implementation exists. – user207421 Nov 08 '12 at 06:42
  • @Adam He has already told us that he has loaded the library successfully, so the .so file must have been found. His problem is in executing the native method, which has nothing to do with `LD_LIBRARY_PATH`. – user207421 Nov 08 '12 at 06:47
  • @adam & ejp - ejp is correct. I was able to get it working with the first part which calls the native methods from a main, the second part calls the native methods from a ActionListener implementation. The only difference in the way it's called is that main is static. I edited my main post with the full error. Thanks for all the help so far. – turkycat Nov 08 '12 at 06:52
  • no other libs, just the two methods in the single file called "dft.c" and the respective "libdft.so" – turkycat Nov 08 '12 at 06:54
  • @EJP hey, still figuring out how to be efficient on this website. Just letting you know I posted the full error for you. Thanks for your help so far :D – turkycat Nov 08 '12 at 06:57
  • @Adam not really, because the part that works for the first part takes in an array of ints that is generated in java, while the second part takes in a filename that needs to be opened, and some data parsed out before the values can be read and then the frequency computed. – turkycat Nov 08 '12 at 07:09
  • @Adam it doesn't have any effect that I'm doing this in C, not C++ does it? Here is the method header as it appears in my C file: JNIEXPORT jint JNICALL Java_dft_computeFileDFT(JNIEnv *env, jobject obj, jstring str) – turkycat Nov 08 '12 at 07:24
  • @Adam tried that, unfortunately. Same deal; I just get a stack trace of only two elements, PianoTuner.main and PianoTuner.computeFileDFT – turkycat Nov 08 '12 at 07:48
  • @Adam here's an interesting test I just tried....i edited the main of PianoTuner (the problem child) and PASTED the entire code from the other main method (changing dft. to PianoTuner.) and gave it the filename of the txt file where the integers are located manually (rather than reading in from command line like the other part does) and i get the SAME EXCEPTION. to clarify, I'm using the OTHER METHOD that works in the first part. Now I am really confused. – turkycat Nov 08 '12 at 07:53
  • Can you show us the first line of where you *define* these *two* functions? It appears you have defined one function correctly and the other incorrectly. – Peter Lawrey Nov 08 '12 at 08:17
  • @PeterLawrey both of the definitions are in the code above. In the code marked "problem", I originally only had the 2nd method, but I just recently added the first to it in a desperate effort. I cut out some of the unimportant/unrelated middle code, but those definitions are there. – turkycat Nov 08 '12 at 08:21
  • @Adam You continue to bark up the wrong tree. If the problem was with loading the library, he would have got the exception at load time. The problem is with the method and its signature. It has nothing to do with paths, dependent libraries, ... – user207421 Nov 08 '12 at 11:29

1 Answers1

2

Reading the enormous comment thread of the all omnipresent smart people of S.O. JNI subculture, i might be missing something, but to me the bug is obvious:

You have created your native JNI lib against class dft, hence your native functions are Java_dft_*. It indeed works when being called in scope of dft instance.

Then you create another class TunerPanel. Of course you declare the same native method signature, but you don't have any library with Java_TunerPanel_* functions! You are still using the same original library with Java_dft_*.

Make dft instance accessible from TunerPanel. You can't use the same native library for native methods from two different classes. Or make the native methods static. As far as my understanding of FFT goes, you don't need to keep any persistent data between runs of compute*.

Pavel Zdenek
  • 7,146
  • 1
  • 23
  • 38
  • oh wow... that does seem obvious now. To clarify... should I be creating all new header files and libraries for the TunerPanel class, or can I still use the `dft.c` file and respective `dft.h` `libdft.so` files, and somehow alter my makefile to generate method headers following the correct `Java_TunerPanel_` syntax? – turkycat Nov 08 '12 at 10:26
  • JNI does not allow calling arbitrary native functions. If your Java package is `foo`, class is `bar` and the native method is `baz()`, then the library you are `load`ing for that class must have a function `Java_foo_bar_baz()` and nothing else. If you absolutely need to call one and the same native logic from *two different* Java classes, you need to make *two different and separate native libs* with different function names in header file. I think that one native wrapper class made accessible from wherever in Java you need, is still much better solution. – Pavel Zdenek Nov 08 '12 at 10:42
  • Thank you so much! I've been toying with it all night tonight on top of a weeks worth of toil and nonsense. IT'S WORKING! I can definitely say I have learned a couple things from this whole experience! Thanks again! – turkycat Nov 08 '12 at 12:19
  • You saved my life with this answer. I'd also give +1 for the "all omnipresent smart people of S.O. JNI subculture", but it is possible only once ;) – Robert Jan 03 '14 at 10:29