I’m currently doing some development on a large RCP application. One recent task required using a Windows DLL to open an email message through MAPI, which is made pretty easy with OSGi. But I encountered a few issues that I’m sure will bite other people.
Bundle-NativeCode
OSGi now provides some support for resolving and loading shared libraries/DLLs in Java applications. A bundle declaratively specifies its DLLs and associated platform requirements in their bundle manifest using the Bundle-NativeCode header. This header value is then used when loading a DLL through System.loadLibrary().
TheBundle-NativeCode header specifies a number of comma-delimited clauses. For example:
Bundle-NativeCode: lib/win32/x86/http.dll; lib/win/zlib.dll;
processor=x86; osname=win32,
lib/macosx/libhttp.dylib; lib/macosx/libzlib.jnilib;
osname=macosx; processor=x86; processor=x86_64,
*
A clause specifies one or more DLLs. A clause is matched to the current platform using a set of parameters (e.g., processor and osname, but also osversion, language, or a selection-filter). Parameters of different types are ANDd together; parameters of the same type are ORd together, which can be tricky. Only a single clause is matched for the current platform, so multiple libraries must be specified within the same clause.
The trailing “*” indicates that the DLLs are optional. It means that the bundle will still resolve even if there are no other matching clauses. If this optional specifier is used, it must come last.
The power of Bundle-NativeHeader is two fold. First, a bundle won’t resolve if there is no matching clause for the current platform. If you don’t include the optional specifier (the trailing asterisk), your code is pretty much guaranteed to never throw an UnsatisfiedLinkError. Second, OSGi will resolve library name (e.g., “http”) to the appropriate DLLs for the platform, and will manage the DLL loading and unloading.
Gotchas
I’ve been bitten by a couple of “gotchas” with the Bundle-NativeCode header.
Gotcha #1: System.loadLibrary() must still be called
I incorrectly thought that OSGi would load the specified DLLs automatically. Unfortunately your Java code must still explicitly load the libraries using System.libraryLoad(). Happily you needn’t specify the path location or the platform-specific prefixes or extensions. So using the example above, the code could just call:
System.loadLibrary("http");
[In hindsight, this makes sense: you wouldn’t want DLLs loaded unnecessarily.]
Gotcha #2: Dependencies aren’t traced
OSGi doesn’t resolve DLL dependencies through the bundle’s dependencies. This hasn’t bitten me personally, but I have come across a few reports online.
Gotcha #3: “Missing host Bundle-NativeCode_0.0.0″: not as hazardous as may appear
The lonely asterisk (“*”) is not actually considered as a clause. It’s simply a configuration parameter that causes the bundle to continue to be resolved even if there are no matching clauses.
When there are no matching clauses, Equinox displays a message like:
!SUBENTRY 2 jmapi 2 0 2011-03-28 10:23:27.633 !MESSAGE Missing host Bundle-NativeCode_0.0.0.
This is an error message only if the asterisk has not been specified! If the asterisk has been specified, then it’s merely an informational message; the bundle may still be successfully resolved.
This differentiation has caused me grief on several occasions now as I assumed such a message was an error and the cause of subsequent errors.
References
The OSGi Core Specification describes the interpretation of the Bundle-NativeCode header. In OSGi 4.2, it’s specifically in §3.7 and §3.10.
There are a few blog posts describing how to use this facility. They were very helpful!
“OSGi now provides some support for resolving and loading shared libraries/DLLs […]”
What does “now” mean? Since 4.3?
By: erdal on March 29, 2011
at 3:55 pm
Good point: it’s not a new feature and has been around since at least 2006!
By: Brian de Alwis on March 29, 2011
at 4:02 pm
Hi! Thank you for a good post, it’s very helpful. Thanks to this post, I have been able to bundle native library “sapjco3″.
But I don’t understand one thing.
I am importing package com.sap.mw.jco.*; But this package will be available only after System.loadLibrary(), so during it get’s available during runtime. During compilation it’s not available, that’s why my compilation fails on line “import com.sap.mw.jco.*” What am I missunderstanding..? How can I make native libraries available during compilation
Sorry, if my question is silly.
By: eragulin on September 6, 2013
at 3:48 am
The DLLs are only needed at runtime when the JVM loads the library. The compiler only needs access to the Java classes containing the native methods. If the compile is failing on an import then verify your bundle dependencies and that they’re all exporting the appropriate packages.
By: Brian de Alwis on September 6, 2013
at 6:50 am
Thank you for your answer. It turned out that I was using the wrong version of the jar. After I installed the correct library, everything worked perfectly. Thanks again for your article, especially for gotcha#1
By: eragulin on September 6, 2013
at 3:12 pm