Closed christophvw closed 1 year ago
It is my understanding that it's not possible to load a DLL directly from a jar, so it either has to be extracted at runtime or it has to be bundled by the developer of the app that uses FlatLaf (and then loaded by setting flatlaf.nativeLibraryPath
).
I think the way FlatLaf handles this might be inspired by how JNA does it.
Personally I also prefer to bundle the DLL's myself so I added this to my maven build:
<!-- Unpack flatlaf jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>unpack-flatlat-jar</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo message="unpacking flatlaf jar"/>
<unzip dest="${basedir}/target">
<fileset dir="${basedir}/target/lib">
<include name="flatlaf-*.jar" />
<exclude name="flatlaf-swingx-*.jar" />
</fileset>
</unzip>
</target>
</configuration>
</execution>
</executions>
</plugin>
<!-- Make sure flatlaf dll's exist -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>enforce-flatlaf-dll-files-exist</id>
<phase>package</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireFilesExist>
<files>
<file>${basedir}/target/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll</file>
<file>${basedir}/target/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll</file>
</files>
</requireFilesExist>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
<!-- Copy flatlaf dll's to jpackage input directory -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-flatlaf-dll-files</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/jpackage-input</outputDirectory>
<resources>
<resource>
<directory>${basedir}/target/com/formdev/flatlaf/natives</directory>
<includes>
<include>flatlaf-windows-x86.dll</include>
<include>flatlaf-windows-x86_64.dll</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
For retrieving the path of the bundled DLL I created this utility method:
public static File getApplicationJarFile(Class<?> applicationClass) {
try {
URL applicationJarURL = applicationClass.getProtectionDomain().getCodeSource().getLocation();
if (applicationJarURL != null) {
File applicationJarFile = Paths.get(applicationJarURL.toURI()).toFile();
if (applicationJarFile.exists()) {
return applicationJarFile;
}
}
} catch (Exception e) {
Common.LOGGER.error("Exception while getting application jar file: ", e);
}
return null;
}
The native library path is then set using:
System.setProperty(FlatSystemProperties.NATIVE_LIBRARY_PATH, OsUtils.getApplicationJarFile(MyApp.class).getParent());
Bundled by the developer should always be they preferred way... I hope someone will put this into the documentation.
@christophvw thanks for pointing out this topic 👍
I agree that bundling the WIndows DLLs (and Linux SOs) with the application is a good idea and I will make this easier by uploading those files to Maven Central. Will also change FlatLaf to automatically load them if they are in the same folder as the JAR (or in ../bin/
?). This will make the build scripts shorter and it is no longer necessary to set FlatSystemProperties.NATIVE_LIBRARY_PATH
.
However I will keep the native libraries in the FlatLaf JAR and use them as fallback. And if they are blocked by Application Whitelisting, the application still works with standard Windows title bars...
This will be blocked in all environments with proper Application Whitelisting enabled.
I've now signed the two Windows DLLs with a code signing certificate.
See latest 3.0-SNAPSHOT
: https://github.com/JFormDesigner/FlatLaf#snapshots
I wonder whether signed DLLs in temp folder will be also blocked by Application Whitelisting?
And would it make sense to try to write the DLL to the same folder as the FlatLaf JAR (if writable)? Does it make any difference for Application Whitelisting where the DLL is written to?
@remcopoelstra thanks for sharing the build script and the getApplicationJarFile()
method.
Didn't know that it is so easy to get the JAR file 😄
I wonder whether signed DLLs in temp folder will be also blocked by Application Whitelisting?
In our experience it definitely makes things better.
to try to write the DLL to the same folder as the FlatLaf JAR
I would not do that. On macOS, this would break the signature of an enclosing application bundle
I wonder whether signed DLLs in temp folder will be also blocked by Application Whitelisting?
Default AppLocker Rules on Windows will allow everything to run from %ProgramFiles% Folder. When the DLL is signed it will allow the Administrator to create an allow rule (in case it is located outside of %ProgramFiles%). This is better than unsigned, but still bad, because you have to create a lot of extra rules then. And the more trusted publisher you have the larger the attack surface is for certificate misuse.
And would it make sense to try to write the DLL to the same folder as the FlatLaf JAR (if writable)?
No. Subfolders in %ProgramFiles% should never be user writeable as this would render AppLocker default rules useless. When put elsewhere it will be blocked the same way as in %Temp%.
For upcoming FlatLaf 3.1, the DLLs will be uploaded to Maven Central as part of the com.formdev:flatlaf
artifact. This makes it much easier to used them in build tools like Maven or Gradle.
Since commit 35e23574cffaa860da4846480522d73a0840b822, they are part of the 3.1-SNAPSHOT
snapshots.
For trying this out, it is necessary to change FlatLaf version to 3.1-SNAPSHOT
and add the repository https://oss.sonatype.org/content/repositories/snapshots/
to your build (see Maven and Gradle docs).
Here is the documentation and examples how to use in Gradle and Maven: https://www.formdev.com/flatlaf/native-libraries/
I'm no Maven expert. Not sure whether the Maven examples in the docs are optimal. Maybe there is a better or easier solution.
Hope that some people can try this out and give feedback on how well this works. Thanks.
Has anyone had a chance to try this out yet? (see previous post) @remcopoelstra ?
Yes I have done a few first tests, I wanted to do a little more research before getting back to you (I'm also no Maven expert :)
My first impression is that bundling of the dll by adding a Maven dependency is a big improvement! I noticed that the dll ended up in my lib directory, I was wondering if it's possible to configure Maven to place the dll in another directory, for example 'bin', if I find out how to do this I will share it here.
I am not sure yet if it's really an issue, but I had the impression that the native library path that I set was no longer working correctly when running the built app, when running from my IDE (eclipse) everything seems to work ok, but with the built app I see the dll is again created in the temp directory. I will look into this a little deeper to make sure it's not something I am doing wrong.
I don't know yet why it wasn't working before, but everything looks ok now. Setting the native library path to my app directory which contains flatlaf-windows-x86_64.dll is working (dll is no longer copied to temp directory).
I removed all my own unzipping/bundling from my Maven configuration, this is also working correctly (flatlaf-3.1-SNAPSHOT-windows-x86_64.dll from lib directory is loaded, temp dir is still empty). I manually moved this dll to a bin directory and this was also working ok, but I think I will keep it in the lib directory to keep things simple.
One of the first mistakes I made was copying the windows-x86 dependency instead of the windows-x86_64 dependency, maybe switching the order in the documentation will prevent other people from making the same mistake :)
Would it be possible to publish the binaries for older versions (or at least 2.5) as well? We are using FlatLaf bundled with the NetBeans 15 platform and will need to match the binaries to the version bundled there until we update. I know I can extract them from the existing JAR file, but pulling them directly from the repository is much easier and won't require us to change the process when we move to NetBeans 17.
FWIW: I bundle my dylib,dll,so in a jar, extract them at runtime in a folder where my app has write permissions ($home/myapp / $HOME/Library/Preferences/myapp) and load them from there. No issues on MacOS(notificated) and Windows (signed). That is hardly possible for a library (10 used Libs, 10 folder? Thank you) but for an application author it might be the best solution (at least I have no issues at all).
One of the first mistakes I made was copying the windows-x86 dependency instead of the windows-x86_64 dependency, maybe switching the order in the documentation will prevent other people from making the same mistake :)
@remcopoelstra Done. I've also added notes that 32-bit DLL is not needed when bundling 64-bit JRE with application.
Would it be possible to publish the binaries for older versions (or at least 2.5) as well?
Don't think that it is possible to add files to existing versions on Maven Central.
We are using FlatLaf bundled with the NetBeans 15 platform ...
But NetBeans 15 uses FlatLaf 2.4 😕 FlatLaf 2.5 is used in NetBeans 16.
BTW NetBeans 16+ uses it's own mechanism to extract DLLs from FlatLaf jar (on build) and distribute them outside of the jar. They are in netbeans\platform\modules\lib
directory. See https://github.com/apache/netbeans/pull/4803
Your app/package behaves like a trojan horse by writing and loading unsigned executable files to/from the temp folder:
C:\USERS\xxxxxx\APPDATA\LOCAL\TEMP\x\FLATLAF.TEMP\flatlaf-windows-x86_64-[random-number].dll
This will be blocked in all environments with proper Application Whitelisting enabled. This dll file should be properly installed into the application %ProgramFiles% folder instead and should be signed with a code signing certificate.