kwhat / jnativehook

Global keyboard and mouse listeners for Java.
Other
1.73k stars 344 forks source link

DLL not deleted from java.io.tmpdir #166

Open ck3ck3 opened 7 years ago

ck3ck3 commented 7 years ago

Hello, I'm using JNativeHook version 2.0.2 but I think that this problem is still relevant. I noticed that my temp directory contains a lot (over 300) copies of JNativeHook-*.dll . I looked into it and I think I know why it happens.

EDIT 1: Actually I think that now I know why it happens. When JNativeHook's jar manifest is not found (see below "long version" to see how that happens in my case), the DLL filename will contain its SHA-1 signature. The first time we run the program, the DLL is copied to the temp directory with that SHA-1 filename and it remains in the temp directory. When we run it any other time after that, in DefaultLibraryLocator.java line 169 you try to rename the temp file to the filename which contains the SHA-1 signature. But a file with this name already exists there from a previous run, so the rename fails. As a result, the temp file will now remain in the temp directory and be used as the library file. Every time we run the program after the first time, it will create another DLL with a random file name to remain in the temp directory.

I think that the solution could be to check if this file already exists. If it doesn't, rename to it like you do now. But if it does exist, maybe verify its authenticity by calculating its SHA-1 and see if it matches the one calculated from your resource file. If it matches, set this file as the library file (no need to copy or rename anything). If it doesn't match, try to overwrite it. If overwrite fails, maybe then keep using the temp filename.

EDIT 2: In case anyone with a similar problem is reading this, here's the workarounds I tried: Calling .deleteOnExit() doesn't work since the DLL is apparently still loaded when that method is called by the JVM upon termination. I managed to update my own jar's manifest properly using the Maven jar plugin. I manually added the /org/jnativehook section from this lib's manifest to my own, and that plugin apparently writes the jar so that the manifest is the first file as required. So this worked on my jar, but I pack my jar inside an exe (by using launch4j), so this didn't work for the exe because GlobalScreen.class.getProtectionDomain().getCodeSource().getLocation() returns the exe's filename rather than the jar inside of it. A workaround for this would be to set launch4j to not wrap the jar. But eventually I just decided to pack the DLL in my installer and manually put it in my program's directory, so that System.loadLibrary() would find it by default and avoid the whole mess. I chose the 32 bit version to be on the safe side and I bundle my program with a 32 bit JRE anyway.

------ Original message from before edit ------

Short version: DefaultLibraryLocator.java line 137: libFile = File.createTempFile(...) , but then libFile is never deleted. I think that adding a line after this one with libFile.deleteOnExit() would fix this.

Long version (which I add because maybe I'm doing something wrong?) : My project is built into a jar that contains JNativeHook's .class files, not its jar file. As a result, I don't have JNativeHook's manifest.mf with the version info. When I run my program, I see JNativeHook's log warning "Invalid library manifest!", but then it extracts the DLL into my temp folder and runs properly. I tried adding the relevant part from your manifest.mf into mine (from "Name: org/jnativehook" and onwards), but it still didn't work. I did this using Maven's Shade plugin by supplying my own manifest.mf file for it to embed into my jar. When I did that, the logger warning changed to "Cannot find library manifest!" (but it still worked by extracting to my temp folder). This didn't work because DefaultLibraryLocator.java line 95 calls jarInputStream.getManifest(), and there's this Java bug from 17 years ago that says that if the META-INF/MANIFEST.MF isn't the first file packed into the zip (which is the jar), the getManifest() method will return null. I verified that this is indeed the problem by creating a zip file with just META-INF/MANIFEST.MF and later adding the rest of the files into this zip, renamed it into jar and ran it - and indeed it ran fine and extracted the dll properly without complaining about the manifest.

The workaround for me is going to be to delete the DLL from the temp directory myself when I cleanup before exit. Or find a way for Maven to modify my manifest.mf but still pack it first, but I couldn't find a way to do it so far.

kwhat commented 6 years ago

I am aware of this issue, however, there is no way to fix without a custom class loader to load the native lib. It is a lot of work and not guaranteed to work, but I will investigate as part of 3.0 or later.