lopcode / vips-ffm

libvips bindings for Java, Kotlin, and JVM projects using the Foreign Function and Memory API (FFM)
http://vipsffm.photofox.app/
Apache License 2.0
22 stars 1 forks source link

Windows linker - IllegalArgumentException: Cannot open library: vips.dll -> ExceptionInInitializerError #55

Closed JohannesBeranek closed 1 month ago

JohannesBeranek commented 1 month ago
SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("vips"), LIBRARY_ARENA)
    .or(SymbolLookup.loaderLookup())
    .or(Linker.nativeLinker().defaultLookup());

on Windows this tries to load "vips.dll", which is the wrong name. The library file of libvips 8.15.3 binary windows build is libvips-42.dll.

Note that the same system/setup works with an updated JVips (JNI bindings of libvips - the current github version uses 8.12.x though, so had to use a fork and upgrade that).

lopcode commented 1 month ago

Thanks for raising an issue! This is interesting - the code snippet you included is directly generated by jextract (like everything in VipsRaw).

Specifically, the vips library name is passed to jextract, like so (in generate_ffm_bindings.sh):

"$JEXTRACT_DOWNLOAD_PATH"/bin/jextract \
--include-dir "/opt/homebrew/include/vips/" \
--include-dir "/opt/homebrew/include/" \
--include-dir "/opt/homebrew/include/glib-2.0" \
--include-dir "/opt/homebrew/lib/glib-2.0/include" \
--output core/src/main/java \
--target-package "app.photofox.vipsffm.jextract" \
--header-class-name "VipsRaw" \
--library "vips" \
@includes_filtered.txt \
"$LIBVIPS_ENTRY_PATH"

I don't have much experience with Windows systems - on Unix-like systems the library usually has a symlink added without the number in it. I see I have both libvips.dylib and libvips.42.dylib in my /opt/homebrew/lib/ folder.

I'm wondering if I'm just using the "wrong" library name, or if this is a limitation of the jextract output that needs a workaround. There are some docs for it here - what do you think 🤔

JohannesBeranek commented 1 month ago

Seems to me the root issue in jextract is actually System.mapLibraryName which just maps libraries in windows by appending "dll".

Also searching https://www.google.com/search?q=libvips+library+name+vips+vips-42 tells me that this might be mostly an issue due to libvips naming, though I must say in my linux and osx years I've seen tons of libs with numbers in the name - symlinked though, as you say. Unfortunately windows does no such thing, never has.

And when windows never symlinks any libs by default, I think one shouldn't assume symlinking for windows, so having people manually have to symlink libvips on windows (most people don't even know about mklink on windows in comparison to a simple ln -s on linux) feels very dirty tbh.

Would be nice if there was an option in jextract to provide a fallback library name, as it seems the java code would be able to do that with a simple additional .or(...).

I'll see if I can find more info on jextract mailing lists and the like.

JohannesBeranek commented 1 month ago

@lopcode very interesting in this context: https://github.com/openjdk/jextract/pull/172/files

lopcode commented 1 month ago

Also did a bit of digging in other libvips bindings and found this in the Ruby bindings: https://github.com/libvips/ruby-vips/blob/master/lib/vips.rb#L26

So they're being quite explicit about the expected library name on each platform. I think I have to do something similar.

JohannesBeranek commented 1 month ago

@lopcode found it: https://cr.openjdk.org/~mcimadamore/panama/jextract_changes.html in Change #8: Better library loading looks to me like you could just do -l vips -l libvips-42 and have it work for both *nix/bsd (macos/osx) systems and windows

JohannesBeranek commented 1 month ago

@lopcode your link brings something very interesting - 42 seems to be the current abi number, so that can (and most probably will) change in the future. Which also means it would be beneficial in the long run to have some other solution than just hardcoding 42. But the number should be constant wrt. libvips version - the same libvips version will always have the same ABI version.

lopcode commented 1 month ago

I don't currently understand how the jextract or chain can ever work - at least on my system, a library that can't be found will cause an IllegalArgumentException. So it's not possible to add multiple possible names because one will always fail.

I'm currently inclined to replace this piece of code with something custom that changes the library search name based on the operating system, similar to the ruby-vips snippet above.

lopcode commented 1 month ago

Ok, #57 is an attempt to fix this issue on Windows. I included a property reference to allow consumers to change the ABI number, if they really need to.

lopcode commented 1 month ago

I think I've fixed this with a custom SymbolLookup builder, which lets the library do a few searches for various platform-specific names: https://github.com/lopcode/vips-ffm/blob/main/core/src/main/java/app/photofox/vipsffm/VipsLibLookup.java#L10

Now I also understand the SymbolLookup.or() better - it's not about just "loading" the library, it's expanding the search space for symbols (so it tries the first loaded library for a symbol, then the next one, etc). I had to add lookup spaces for vips, glib, and gobject which I think makes sense. Although having to be explicit makes me nervous that it won't find other libs on Windows like libheif... but let's see how it goes.

I've also included a GitHub Actions check for Windows to prove it works, and stop me from accidentally breaking it in the future: https://github.com/lopcode/vips-ffm/blob/main/.github/workflows/checks.yml#L53

This is all in 0.5.0, if you could give it a try please 🙂 And thanks again for the heads up, I wouldn't have noticed.

JohannesBeranek commented 1 month ago

Runs on windows now. I'll post further issues separately, thanks!