Lightweight Windows .Jar to .Exe converter using a Node.js script
This is perhaps the jankiest solution of all time to a problem that Launch4j solved very well ages ago. I designed this project as a lightweight alternative to l4j, however due to the odd tooling you need on your Window’s path for this work correctly, I advise using Launch4j in all cases. This application is really just developed for my own personal use.
In order for the converter to work, the commands “xxd”, “gcc”, “jar”, and “javac”, must all be available on your PATH. While “javac” is never used by the script, it is used to determine if and where you have a JDK installed, since internally jni.h is a required header. Both “xxd” and “gcc” are available through MSYS, while both “javac” and “jar” are available via JDKs, which if you need a jar to exe program, you probably already have.
Place converter.js
(located in /build/) in the same directory as the desired .jar to convert (I’ll be referring to it as program.jar
for this example). Run converter.jar
with node passing in the name of the jar as a command line argument. Ex:
node converter.js program.jar
If all goes well, program.exe
will appear in the same directory. It is highly advised you move the newly created .exe file out of the directory of the .jar as soon as it’s created, since if the Main-Class’s name is the same as the jar file’s, it will create undefined behavior at runtime due to naming conflicts. More on why in the section “How the Generated .exe works”.
For a more concrete example see the examples folder. /examples/ex1 uses my project Windows10-Add-Startup-Sound and converts it into an exe, the folder contains before and after snapshots of the conversion. You test out converter.js
in /examples/ex1/before by running:
node converter.js startsound.jar
If all goes well you should get an identical progam as the one located in the /after/ directory, which was generated by me using build.js
.
The builder, build.js
in /tools/ builds the main converter and all of the examples. It copies the C code from “/src/main.c” (used for the exe) as a string into “/src/converter.js”. This is so the final converter (located in /build/converter.js) is just one simple script file, rather than a folder with the required C file(s) inside of it.
In /build/ you will find converter.js
, this is the script that actually “converts” the .jar to an .exe.
This converter program takes the name of the jar to convert as a command line argument then uses that jar to generate a C program that will store and execute that Jar. Essentially it does this in three steps; First it uses:
xxd -i program.jar > jar.h
To convert the jar to a .h file containing all the jar’s binary data. Then it extracts the jar’s manifest using:
jar xf prog.jar META-INF/MANIFEST.MF
It uses the Main-Class in the manifest to generate another .h file letting the internally stored C program know the name of the main class for the JNI to call later on. Finally it compiles the C program to an exe using GCC.
The .exe starts by creating a subprocess that acts as a sandbox for the JNI to run the .jar file in. This is important because if the java code in the jar calls System.exit(0)
, jvm.dll will kill the entire process, not just the java program, which would make cleaning up afterwards impossible.
This sandbox process copies the jar from within the exe to the same directory, the jar will have the name of the class specified by Main-Class, having another jar with the same name in the directory with the exe will cause undefined behavior and probably break the other jar in the directory. The program then will start the JNI using the now external jar as the class path. The program will then call the main method and hand control over to the jar. Finally cleanup begins either when the Jar hands control back or kills the process via System.exit()
. The jar file is then deleted and you are just left with the exe.