Open kingarchie opened 7 years ago
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
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.
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.
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:
java.io.tmpdir
location that the user has access to write to.java.library.path
to point to that location. This is the preferred Sun/Oracle method. 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.See if you cant get a complete stack trace, that may help identify what is happening.
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.
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 ;)
Note, that the static block of code executes when the Jar is loaded into the JVM and before main(...) actually runs.
If the library is successfully extracted, and you are still seeing that error, it could also be an ABI mismatch.
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()?
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.
Also, remember that you need to tell the GlobalScreen which locator to use with String libLoader = System.getProperty("jnativehook.lib.locator", "org.jnativehook.DefaultLibraryLocator");
java -Djnativehook.lib.locator="com.kingArchie.AwesomeSauceLibraryLocator" your.jar
should do the trick.
How would I do this automatically? https://gist.github.com/kingArchie/0947b2e8df5a75dcb6003742d24c9f3e
Would that method work? Or how would I add my own file and make GlobalScreen use that lib.?
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();
}
}
}
http://prntscr.com/eagvc2 - this is the dll that is created. Is this right? It didn't delete on exit for some reason.
that is the class... hmm probably because its still in use while the jvm is running ;( #WindowsProblems
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
Yeah. Thought so. Would this fix the problem that the person was having? Is it possible to create a batch file to deleteOnExit?
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?
You could probably safely delete all libs that start with JNativeHook also, but only on windows.
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?
Windows is all my program will run on.
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();
}
}
}));
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.
I stopped it posting to console. Alright. I understand, thank you for your time.
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.
Alright, great.
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.
I'll test this tonight!
I assume the last bit I would do on a shutdown hook? Like... m = null etc.?
Cannot resolve symbol T
java.lang.NoSuchMethodException: org.jnativehook.GlobalScreen.registerHook(org.jnativehook.GlobalScreen)
I tried changing registerHook to registerNativeHook - same thing.
Hi there,
Why do I get this error?