Runtime access to external JAR library
Hi MPS community,
I am trying to get JNA (Java Native Access) working in MPS. Adding stubs in a solution went fine. I also added the library under Java --> Library tab of the solution under which the JNA java stubs were added.
Compiling a language which uses the JNA library works perfectly as well. However, when I try to run code that uses the library from a model, then I get problems like NoClassDefFoundError.
If I add jna.jar to the MPS startup script (in my case mps.bat), then everything works fine. Of course, adding the classpath hardcoded to the MPS startup script is not a proper solution. I've also seen other JARs (like rt.jar from the JDK solution) work perfectly, so it must be possible to tell MPS somewhere in the solution where the JNA stubs reside that it should use jna.jar during runtime use in a model.
A hint as to where I can specify this information would be highly appreciated.
Best regards,
Eugen
Please sign in to leave a comment.
Hi, Eugen,
It seems like JNA has some issues with MPS classloading.
Though I have never used this library, I’ll try to investigate the problem. For that, I need:
1. some example where the library is supposed to work but doesn’t work
2. if you know some doc about how JNA works with classloading - would be great to have a look at. Googling for 5 min didn’t give any results.
Generally, it seems that if you add jna to MPS startup classpath, everyone can access it as every module’s classloader has MPS classloader among its ancestors. When you add it to the language (sorry, can’t get what exact dependencies you use here), the classloader of the module that actually uses it can’t access jna-solution’s classpath. I’ll get your example and try to find the bad dependency, which makes the library not visible for the module it is used in.
Hi Mihail,
Thanks a lot for your very fast reaction!
I built up an example in the ImportJAR subdirectory of the https://github.com/DSLFoundry/mps-examples repository.
Under the behavior in the LangUsingJNA language, there is a class called MyJNAInterface, which contains the JNA code calling a C library:
I used Windows environment, so I built a DLL (clib.dll) which is also checked into the root of the ImportJAR subdirectory. In case you don't have a Windows machine handy, I added clib.c source (and a java file) to build on another OS if you need. make.bat gives an idea of what to do to build C and java sources.
To kick off the doJNA method of the MyJNAInterface class, I have an intention which can be called from the MyConcept instance in SolutionForLangUsingJNA/ExampleModel:
Before trying the example, you need to set the right path to the DLL file (I don't know how to properly get the path of MPS installation or the model in a static context):
If you start your MPS (I used the unified package http://download.jetbrains.com/mps/34/MPS-3.4.3.zip) with the shipped mps.bat, then upon calling the "Do JNA" intention the first time, there is a JNA initializer error:
If you call it the second time, there is a NoClassDefFoundError:
If you edit mps.bat so that it adds a line that says "set CLASSPATH=%CLASSPATH%;D:\repo\dslfoundry\mps-examples\ImportJAR\jna.jar" (change the path to the one where you checked out mps-examples repo). Then there are no errors and the expected info lines are printed, proving that the function from the DLL is actually called:
So it works by adding the jna.jar to the classpath, but is somehow not loaded correctly via the solution. I also googled for classloading and JNA, but was not successful, because it seems to work pretty isolated and doesn't give many problems with other classloaders. I found about the Native API of JNA that it's possible to do a getInstance with a specific classloader (https://java-native-access.github.io/jna/4.2.1/com/sun/jna/NativeLibrary.html), but I don't get yet how to connect to MPS classloader, so I left the example pure with only what I know and described above.
If you know a way how to get this working, that would be really splendid!
Hi, Eugen,
Finally, I've investigated the issue and that's what happens.
When the Native class tries to load library libjnidispatch.jnilib, it calls ClassLoader.getResource(). This method is not overridden in MPS' ModuleClassLoader, so it delegates to parent class loader first. As parent classloader is app's classloader, it has /lib folder in path and loads jna.jar from there (surprise, Idea platform uses JNA ;) ). This seem to be an old lib, which does not support sizeof(bool).
It's not the same how MPS' ModuleClassLoader works with classes (it searches parent at last). So, actually you get classes from your jna.jar, but the lib from MPS' jna.jar. I think it's a bug in MPS and will discuss it with the team on today's meeting (if we create an issue, I'll post a ref here).
A temporary workaround for you is to use MPS' jna.jar from/lib folder in your JNA solution or to replace jna.jar in /lib folder.
*But* if you actually want to use JNA not in MPS process but in your app written in MPS, it will work fine (as ModuleClassLoader will not be used in this case).
https://youtrack.jetbrains.com/issue/MPS-25298
Thanks a lot for taking care of the issue, Mihail! I'll follow the issue and will try as soon as there is a fix.