Load DLL (using JNA) inside an OSGi bundle
Asked Answered
S

4

6

OSGi cannot find my DLL file, and I can't seem to figure out why.

Currently I have the DLL file (foo.dll) at the root of my bundle, I've also tried having it in a libs directory.

The Manifest for the bundle in question looks something like this:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: foobundle
Bundle-SymbolicName: com.foo.bar
Bundle-Version: 1.0.0
Bundle-Vendor: me
Import-Package: com.sun.jna,
 com.sun.jna.ptr,
 com.sun.jna.win32
Export-Package: com.foo.bar
Bundle-NativeCode: foo.dll;
 osname=WindowsXP;
 processor=x86

Then in my JNA interface I perform a loadLibrary (as per the documentation):

public interface MyFooInterface extends com.sun.jna.Library{
    static final MyFooInterface INSTANCE = (MyFooInterface)com.sun.jna.Native.loadLibrary("foo", MyFooInterface .class);

    // specific interface defs here...
}

Then in another class I attempt to use the JNA interface

// ...code
int var = MyFooInterface.INSTANCE.bar();
// ...more code

I have JNA supplied via another bundle (which exports com.sun.jna and the other packages imported above), but have also tried packaging it with the bundle defined here (and added it to the classpath in that case, etc.).

I've also tried specifying Bundle-NativeCode: /foo.dll.

Also of interest, these are the relevant OSGi properties (which I pulled up using getprop)

org.osgi.framework.os.name=WindowsXP
org.osgi.framework.processor=x86

Even after all this (and with every trial I made) I always end up with the following error (and a stack trace not shown):

java.lang.UnsatisfiedLinkError: Unable to load library 'foo': The specified module could not be found.

...so what am I missing?

Edit: I should also note that I've tested and had success the JNA interface code and the DLL that it talks to as part of a JUnit Test program.

Edit 2: Adding this code to the class that's calling the library seems to allow JNA to find the library (when Native.loadLibrary gets called later). It seems I should be able to avoid this call based on the Bundle-NativeCode directive in the Manifest. Clearly once the library is loaded Native.loadLibrary grabs the existing instance of it, but I'd prefer not to depend on this very order-specific tactic.

static{
    System.loadLibrary("foo");
}
Salzman answered 3/9, 2009 at 19:48 Comment(5)
Which OSGi implementation are you using?Guelders
I'm using Equinox (specification R4)Salzman
You might want to state which version of Java you are using because I'd never come across JNA before and it's not part of Java up to and including 6.Escalate
the System.loadLibrary() call works because it will call the class loader's findLibrary() method, which if it's the Eclipse implmentation of ClassLoader, will find the 'local' library by looking inside the bundle.Escalate
Don't be scared to debug into these classloader and system calls when you get these nasty problems, it can be very educational.Escalate
R
7

The problem is the specialised JNA loadLibrary call, which is not OSGi aware. When you invoke loadLibrary from an OSGi bundle, it will use the OSGi classloader (which is bundle aware) to find where the DLL is, and in this case, extract it out from the bundle and make it loadable via the System.loadLibrary() call against a specific location.

Since this JNA seems to be (a) not OSGi aware, and (b) superflous, why not just use System.loadLibrary() instead?

If you need to write both, then perform a System.loadLibrary() in the bundle's start() method in the BundleActivator, which will bring the native library in (you probably want to ensure that if it can't be loaded, the bundle can't be started in any case).

Roman answered 7/9, 2009 at 13:13 Comment(4)
Ultimately JNA must call its own Native.loadLibrary or Native.register (which after tracing through with the debugger follows the same sequence of events) in order to enable the interface-driven or direct native access that JNA provides. I've found that the solution, albeit one that seems unsatisfactory is to call System.loadLibrary first, as you suggest, followed later by a call to Native.loadLibrary or Native.register in the appropriate place.Salzman
You might find this works on Windows, but perhaps not on other platforms. I recall an issue whereby loading a DLL with dependencies works on Windows (by loading A first, then B, when B depends on A) but has issues on Macs. I have no idea why that might be the case ... but just a warning that you may have a platform-specific solution in your hands. At the very least, you should test (rather than assume) for non-Windows platforms.Roman
System.loadLibrary uses the OSGi class loader -- the Bundle-NativeCode statement in the Manifest is absolutely required in order to locate the DLL -- so regardless of operating system this solution will find the native code. If foo.dll depended on bar.dll it would become very important to load them in sequence, but that's another issue and fortunately not the case here. Thanks for the heads up, though.Salzman
I struggled with the same problem and came up a solution. As the JNA documentation states "Make your native library available on your classpath, under the path {OS}-{ARCH}/{LIBRARY}, where {OS}-{ARCH} is JNA's canonical prefix for native libraries (e.g. win32-x86, linux-amd64, or darwin). If the resource is within a jar file it will be automatically extracted when loaded." So create a folder at the root of the bundle (assuming the classpath includes that) named i.e. darwin and place the library there. Tested on macOS with a JNA 4.1.0 and a library in an Eclipse plugin.Klansman
E
1

Looking at JNA's documentation, it states:

  • Make your target library available to your Java program. There are two ways to do this:
    • The preferred method is to set the jna.library.path system property to the path to your target library. This property is similar to java.library.path but only applies to libraries loaded by JNA.
    • Change the appropriate library access environment variable before launching the VM. This is PATH on Windows, LD_LIBRARY_PATH on Linux, and DYLD_LIBRARY_PATH on OSX.

So to get around this shortcoming you could resolve the the absolute path of the library and load that.

Assuming that its Eclipse's standard class loader, you can do ClassLoader.findLibrary() which should find the local library in the bundle.

Escalate answered 7/9, 2009 at 11:10 Comment(1)
This comes down to an application packaging and distribution issue, place the natives in a predictable location in your application an synthesize this location for injection into the JVM properties with "jna.library.path".Packthread
P
0

I suggest you try to package the dll as a jar:

jar cvf foo.dll.jar foo.dll

and the load the jar as a regular lib.

Prosthesis answered 4/9, 2009 at 13:14 Comment(3)
I'm not sure that's a viable approach, I need to load the DLL as a native library, and am already sure that it's packaged as part of the OSGi bundle that is trying to use it.Salzman
I'm using this tecnhique in JNLP to load native libs. The jvm can find native libs inside jarsProsthesis
So FWIW I tried it and no good. I constructed foo.jar and added it to my class path (that adds the lines: Bundle-ClassPath: libs/foo.jar, . ) to the Manifest above and the library still cannot be found. The problem stems from more than the JVM not being able to see it, it needs to be loaded via the OSGi pathing/class loader.Salzman
M
0

It's a good idea to have the com.sun.jna packages in your org.osgi.framework.system.packages.extra list, because the native libraries (such as jnidispatch.dll used internally by com.sun.jna.Native) can only be loaded once, whereas OSGi classloaders can load classes multiple times.

Momently answered 20/10, 2021 at 15:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.