ikvmnet / ikvm

A Java Virtual Machine and Bytecode-to-IL Converter for .NET
Other
1.28k stars 121 forks source link

java.lang.UnsatisfiedLinkError with IKVM 8.7.1 bioformats_package. #448

Open BiologyTools opened 1 year ago

BiologyTools commented 1 year ago

I'm using IKVM 8.7.1 and I am getting this error when calling openBytes(). I know this can't be an error in the java code since I'm using the same version 7.0.1 as before back when I converted the jar to dll with ikvmc.exe it worked fine. I'm now using the IKVM Maven SDK instead of ikvmc.exe. Do you have any idea on what could be going on?

java.lang.UnsatisfiedLinkError
  HResult=0x80131500
  Message=ome/jxrlib/JXRJNI.new_DecodeContext()J
  Source=formats.gpl
  StackTrace:
   at loci.formats.in.NDPIReader.openBytes(Int32 no, Byte[] buf, Int32 x, Int32 y, Int32 w, Int32 h)
   at loci.formats.FormatReader.openBytes(Int32 no, Int32 x, Int32 y, Int32 w, Int32 h)
   at loci.formats.ImageReader.openBytes(Int32 no, Int32 x, Int32 y, Int32 w, Int32 h)
   at Bio.BioImage.GetTile(BioImage b, ZCT coord, Int32 serie, Int32 tilex, Int32 tiley, Int32 tileSizeX, Int32 tileSizeY) in F:\repos\BioImager\BioImager\Source\Bio.cs:line 6049
   at Bio.BioImage.OpenOME(String file, Int32 serie, Boolean tab, Boolean addToImages, Boolean tile, Int32 tilex, Int32 tiley, Int32 tileSizeX, Int32 tileSizeY) in F:\repos\BioImager\BioImager\Source\Bio.cs:line 5906
   at Bio.BioImage.OpenFile(String file, Int32 series, Boolean tab, Boolean addToImages, Boolean tile, Int32 tileX, Int32 tileY, Int32 tileSizeX, Int32 tileSizeY) in F:\repos\BioImager\BioImager\Source\Bio.cs:line 4306
   at Bio.BioImage.OpenFile(String file, Int32 series, Boolean tab, Boolean addToImages) in F:\repos\BioImager\BioImager\Source\Bio.cs:line 4176
   at Bio.BioImage.OpenFile(String file, Boolean tab) in F:\repos\BioImager\BioImager\Source\Bio.cs:line 4166
   at Bio.TabsView.openToolStripMenuItem_Click(Object sender, EventArgs e) in F:\repos\BioImager\BioImager\Source\TabsView.cs:line 210
   at System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripItem.cs:line 2996
   at System.Windows.Forms.ToolStripMenuItem.OnClick(EventArgs e) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripMenuItem.cs:line 899
   at System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripItem.cs:line 2370
   at System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripItem.cs:line 2560
   at System.Windows.Forms.ToolStripItem.FireEventInteractive(EventArgs e, ToolStripItemEventType met) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripItem.cs:line 2244
   at System.Windows.Forms.ToolStripItem.FireEvent(EventArgs e, ToolStripItemEventType met) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripItem.cs:line 2229
   at System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStrip.cs:line 3764
   at System.Windows.Forms.ToolStripDropDown.OnMouseUp(MouseEventArgs mea) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripDropDown.cs:line 1382
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs:line 12439
   at System.Windows.Forms.Control.WndProc(Message& m) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs:line 13229
   at System.Windows.Forms.ScrollableControl.WndProc(Message& m) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ScrollableControl.cs:line 1507
   at System.Windows.Forms.ToolStrip.WndProc(Message& m) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStrip.cs:line 4989
   at System.Windows.Forms.ToolStripDropDown.WndProc(Message& m) in /_/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripDropDown.cs:line 2135
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.ControlNativeWindow.cs:line 68
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.ControlNativeWindow.cs:line 122
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam) in /_/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs:line 370
   at Interop.User32.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop(UIntPtr dwComponentID, msoloop uReason, Void* pvLoopData) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ComponentManager.cs:line 346
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(msoloop reason, ApplicationContext context) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs:line 1117
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(msoloop reason, ApplicationContext context) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs:line 981
   at System.Windows.Forms.Application.Run(Form mainForm) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs:line 1188
   at BioImagerApp.Program.Main(String[] args) in F:\repos\BioImagerApp\BioImagerApp\Program.cs:line 17
wasabii commented 12 months ago

Happen to have a projec/solution that demonstrates this?

BiologyTools commented 12 months ago

I have prepared a project file and uploaded it to a google drive since the issue occurs with some whole-slide-images only which used to open up fine before. Unfortunately the file size is pretty big due to the size of the whole-slide-image. Here is the link: https://drive.google.com/file/d/1PyLNtq3TT_M1_5-xr7-ou1qZpCFnTF8Q/view?usp=sharing

BiologyTools commented 11 months ago

@wasabii This issue seems to effect all NDPI whole-slide-images. Here BioformatsTest.zip is the project without an image since WSI's tend to be large. Here is a link to another NDPI image showing the same error: https://cytomine.com/collection/os-2/os-2-ndpi Have you had time to look into this error?

wasabii commented 11 months ago

I've done enough work to understand the problem now. Now I just need to come up with a solution.

The issue is the JNI spec says this:

In the JDK, each class loader manages its own set of native libraries. The same JNI native library cannot be loaded into more than one class loader. Doing so causes UnsatisfiedLinkError to be thrown. For example, System.loadLibrary throws an UnsatisfiedLinkError when used to load a native library into two class loaders.

What's happening is the code loading the JNI library is different from the code trying to bind to it. There seems to be a common "native library loading capability" in a module named "org.scijava.nativelib". The jxrlib library takes it's native DLL, extracts it to a temporary directory, and then calls into org.scijava.nativelib to do the loading. At which point, it is org.scijava.nativelib that has loaded the library. And as such, it isn't available to the classloader with JXRLIB in it!

The thing here is that the org.scijava.nativelib functionality doesn't support the calling library being in a different classloader than itself.

And the way it's set up today, statically compiled DLLs each have their own unique class loader, so the class visibility can be restricted based on .NET reference visibility.

I consider this sort of a bug in org.scijava.nativelib or jxrlib. They should be passing the classloader down, or the loading class down, and using that passed classloader to do the native loading. But, they don't, so these two things have to be in the same clas loader.

Not yet sure of a solution on the IkvmReference side.

BiologyTools commented 11 months ago

I'm surprised this used to work at all with IKVM 8.6.4. Should I get in touch with org.scijava.nativelib devs to get this fixed? I suspect this issue will happen with a lot more formats than just NDPI since it's related to JXRLIB. Thank you for your time this must have been a tough one to track down.

wasabii commented 11 months ago

It works if you take all of the jar files and merge them into one assembly. Because IKVM loads each assembly into it's own class loader. But that's not how MavenReference works, and for good reason.

So, this will still work, for instance, if you use IkvmReference to merge all the jars together. But then you can't use MavenReference to get the JARs in the first place.

I'm not yet sure what the right answer is here.

In IKVM, each assembly gets it's own class loader. Because this means when you build assemblies like AssemblyA with a dependency to AssemblyB, the class loader hierarchy makes resources in AssemblyB visible to AssemblyA through delegation. But not resources in some random AssemblyC. The assembly dependencies form the class loader hierarchy, so it mirrors .NET, and you don't have resources or classes from random unexpected places being picked up.

And then MavenReference creates on IkvmReference per artifact, linking them together based on how Maven describes their references. So the Maven dependency tree ultimately informs the class loader hierarchy.

wasabii commented 11 months ago

https://github.com/scijava/native-lib-loader/issues/49

It would obviously make the problem go away if this library just let you do the right thing, and then all the code got fixed. But I do feel like we should have a better answer.

BiologyTools commented 11 months ago

Thank you for opening an issue with scijava hopefully this will be fixed by them and make it's way to the Bioformats library eventually. I'm glad this will hopefully help some scientists out in their work since IKVM Maven is now mentioned as a way to work with the Bioformats library in .NET in the bioformats documentation. https://bio-formats.readthedocs.io/en/stable/developers/dotnet-dev.html