Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UnsatisfiedLinkError #148

Open
kingarchie opened this issue Feb 18, 2017 · 34 comments
Open

UnsatisfiedLinkError #148

kingarchie opened this issue Feb 18, 2017 · 34 comments
Labels

Comments

@kingarchie
Copy link

Hi there,

Exception in thread "main" java.lang.UnsatisfiedLinkError: org.jnativehook.GlobalScreen.getAutoRepeatRate()Ljava/lang/Integer

Why do I get this error?

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Hi,

Did you attempt to compile from source? I suspect that you compiled the Java code but not the native C code. There are full instructions for compiling everything for all supported platforms in the Wiki. Let me know if you have any questions or that does not solve your issue.

Best,
Alex

@kwhat kwhat added the question label Feb 18, 2017
@kingarchie
Copy link
Author

I compiled using the JAR - I'll read the wiki. The problem is, is that it has worked for a lot of users but doesn't work for one.

@kingarchie
Copy link
Author

Hi there.

All I did was add the JAR to my modules (IntelliJ) and compile the whole program. It works perfectly - yet only one user has this error.

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Ahh, so one of your users is having trouble, but it is working for you? So the Jar that I distribute contains the required binaries for all supported platforms and goes though a somewhat complicated procedure to automatically "lazy-load" the included binary code. (Take a look at the DefaultLibraryLocator code) The error you are seeing suggests that the loading process failed for what could be a number of reasons. Most commonly, the users that experience issues do not have write access to the location provided by the Java property java.io.tmpdir. This is really common on Windows machines that are locked down to unreasonable degree though some kind of Active Directory policy. You can work around the issue in a number of ways:

  1. Provide a java.io.tmpdir location that the user has access to write to.
  2. Extract the correct binary library for the users host system to some location and then specify the java property java.library.path to point to that location. This is the preferred Sun/Oracle method.
  3. Create your own custom library locator class that either extends org.jnativehook.DefaultLibraryLocator or implements the org.jnativehook.NativeLibraryLocator interface. You can specify which class is used to load the native library using the jnativehook.lib.locator Java property.

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

See if you cant get a complete stack trace, that may help identify what is happening.

@kingarchie
Copy link
Author

Hmm - I think #1 is the best option.

What files are left over from JNativeHook when the application closes? Can you tell me how to actually fix it? What code to use? I literally don't understand. Cheers.

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Look in the java.io.tmpdir for a dynamic library (dll, so or dylib) called "JNativeHook*.dll". The library prefix comes form the property jnativehook.lib.name which defaults to "JNativeHook" and the version information at the end is extracted from the Manifest of the jar or the java property jnativehook.lib.version and if that all fails the sha1 sum is used. The easiest way for you to understand what is going on would be to start reading here and following the code. I can help with with bits of that process you get stick on, but it would probably take 4 or 5 pints of beer to explain the entire thing ;)

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Note, that the static block of code executes when the Jar is loaded into the JVM and before main(...) actually runs.

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

If the library is successfully extracted, and you are still seeing that error, it could also be an ABI mismatch.

@kingarchie
Copy link
Author

Haha, I see - the temp files. How could I provide someone "access" to create a temp file? Also is there anyway I could make these files .deleteOnExit()?

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

I use to do the .deleteOnExit() near here but I started experiencing other "interesting issues" when two instances of the library were running, and one instances terminated before the other. If you want to remove the library on exit, override the default library name with the jnativehook.lib.name property to ensure you don't mess with someone else's program and use your own NativeLibraryLocator class to extract the library. You can probably figure out a way to do this fairly easily by extending the DefaultLibraryLocator, calling super.getLibraries() and adding your own cleanup mechanism.

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Also, remember that you need to tell the GlobalScreen which locator to use with String libLoader = System.getProperty("jnativehook.lib.locator", "org.jnativehook.DefaultLibraryLocator");

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

java -Djnativehook.lib.locator="com.kingArchie.AwesomeSauceLibraryLocator" your.jar should do the trick.

@kingarchie
Copy link
Author

How would I do this automatically? https://gist.github.com/kingArchie/0947b2e8df5a75dcb6003742d24c9f3e

@kingarchie
Copy link
Author

Would that method work? Or how would I add my own file and make GlobalScreen use that lib.?

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Yes, that's pretty much exactly what you want. Just remember, you can only have 1 app instance running or you will have the same problem I did. You can just add something like this to that class:

public class LibraryLocator extends DefaultLibraryLocator {
    static {
        // Be Sure the class is set with the Fully Qualified Class Name.
        System.setProperty("jnativehook.lib.locator", "com.KingArchie.LibraryLocator");
    }
    public LibraryLocator() {
        for (Iterator<File> it = super.getLibraries(); it.hasNext(); ) {
            File f = it.next();
            f.deleteOnExit();
        }
    }
}

@kingarchie
Copy link
Author

http://prntscr.com/eagvc2 - this is the dll that is created. Is this right? It didn't delete on exit for some reason.

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

that is the class... hmm probably because its still in use while the jvm is running ;( #WindowsProblems

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Delete it before you call super. Write a little bit of code that looks at the location for files with similar names. You can control the library prefix with System.setProperty("jnativehook.lib.name", "KingArchie"); in the same static block

@kingarchie
Copy link
Author

Yeah. Thought so. Would this fix the problem that the person was having? Is it possible to create a batch file to deleteOnExit?

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

I am not sure if it will fix the problem that user is having because I dont know exactly what is happening on their system. Is there a full stack trace?

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

You could probably safely delete all libs that start with JNativeHook also, but only on windows.

@kingarchie
Copy link
Author

https://gyazo.com/2ff3c3f19bb00315c20b9fa287942a2d that is the stacktrace I was sent.

I was thinking - if I unregister the nativehook before the shut down, could I then delete the files on exit?

@kingarchie
Copy link
Author

Windows is all my program will run on.

@kingarchie
Copy link
Author

kingarchie commented Feb 18, 2017

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    GlobalScreen.unregisterNativeHook();
                } catch (NativeHookException e) {
                    e.printStackTrace();
                }

                for (File f : temps) {
                    f.deleteOnExit();
                }
            }
        }));

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

Unregistering the running hook wont help. You really need a System.unloadLibrary(libName) or System.unload(libPath); but as far as I know, no such method is provided by the JVM. You maybe able to do something super sketchy with FreeLibrary in the native code... but that could have some serious unintended consequences because there is no way to know what the JVM is doing with the module reference after it's unloaded. If the user in question removes all libs from the temp location, and is still having the same issue, then there maybe something else going on here. The unsatisfied link error is basically the JVM saying that it cannot run the Jar because it could not find the native method in question. The reason that method was not found is obscured by the loading process and the JVM. It could be that it could not find the library to load, but that should have triggered the log.severe(e.getMessage()); exception in the output of the nested catch in the GlobalScreen static block. If you didn't mess with the Log level, it would show up right above where the screenshot you sent cuts off. This would be reinforced by some warning level messages above that error as well. Maybe something like "Unable to extract the native library ..." Other possibilities would be some kind of runtime linking error like a missing dependency on his system, overzealous antivirus, application binary interface miss-match (32-bit dll on a 64-bit JVM), or something I am not quite thinking for right now. Your best bet would be to set the log level to something like debug, and get to full console output of the crash. At this point we are just speculating.

@kingarchie
Copy link
Author

I stopped it posting to console. Alright. I understand, thank you for your time.

@kwhat
Copy link
Owner

kwhat commented Feb 18, 2017

No problem, I leave this open for a while. If you get some more info, just post back here. I would recommend keeping the log level at at least Warning so that you get some important messaging. I have tried to make the warnings and errors only come up in critical situations. If its getting to noisy, let me know.

@kingarchie
Copy link
Author

Alright, great.

@kwhat
Copy link
Owner

kwhat commented Feb 24, 2017

Alright, a little update... it may be possible.

Try using a similar library locator:

public class LibraryLocator extends DefaultLibraryLocator {
    static {
        // Be Sure the class is set with the Fully Qualified Class Name.
        System.setProperty("jnativehook.lib.locator", "com.KingArchie.LibraryLocator");
    }

    public void finalize() {
        for (Iterator<File> it = super.getLibraries(); it.hasNext(); ) {
            File f = it.next();
            f.delete();
        }
    }
}

Add a custom class loader:

/**
 * 
 * @author http://codeslices.net team
 * 
 */

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 
 * Simple custom class loader implementation
 * 
 */
public class CustomClassLoader extends ClassLoader {

    /**
     * The HashMap where the classes will be cached
     */
    private Map<String, Class<?>> classes = new HashMap<String, Class<?>>();

    @Override
    public String toString() {
        return CustomClassLoader.class.getName();
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        if (classes.containsKey(name)) {
            return classes.get(name);
        }

        byte[] classData;

        try {
            classData = loadClassData(name);
        } catch (IOException e) {
            throw new ClassNotFoundException("Class [" + name
                    + "] could not be found", e);
        }

        Class<?> c = defineClass(name, classData, 0, classData.length);
        resolveClass(c);
        classes.put(name, c);

        return c;
    }

    /**
     * Load the class file into byte array
     * 
     * @param name
     *            The name of the class e.g. com.codeslices.test.TestClass}
     * @return The class file as byte array
     * @throws IOException
     */
    private byte[] loadClassData(String name) throws IOException {
        BufferedInputStream in = new BufferedInputStream(
                ClassLoader.getSystemResourceAsStream(name.replace(".", "/")
                        + ".class"));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int i;

        while ((i = in.read()) != -1) {
            out.write(i);
        }

        in.close();
        byte[] classData = out.toByteArray();
        out.close();

        return classData;
    }
}

Then do some magic:

public static void main(String[] args) throws Exception {
    CustomClassLoader cl = new CustomClassLoader();
    Class<T> screen = cl.findClass("org.jnativehook.GlobalScreen");
    Method m = screen.getMethod("registerHook", screen);
    m = null;
    screen = null;
    cl = null;
    System.gc();
}

I literally just pulled this out of my ass and haven't tested any of it, however, the approach should work for using the custom class loader to find the class and then deleting files on GC.

@kingarchie
Copy link
Author

I'll test this tonight!

@kingarchie
Copy link
Author

I assume the last bit I would do on a shutdown hook? Like... m = null etc.?

@kingarchie
Copy link
Author

Cannot resolve symbol T

@kingarchie
Copy link
Author

java.lang.NoSuchMethodException: org.jnativehook.GlobalScreen.registerHook(org.jnativehook.GlobalScreen)

I tried changing registerHook to registerNativeHook - same thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants