/Buoy

Simple and clean native binding library for Java

Primary LanguageJavaApache License 2.0Apache-2.0

Buoy - Safely Navigating Panama

The JDK's up-and-coming Project Panama Foreign Function & Memory API provides a very low-level and explicit API that very directly maps native code concepts into a Java API. While this is ideal for providing the level of control necessary to stably bind to native code, it is not ideal for hand-writing bindings to native code and existing tools for automating binding creation are less than ideal for some scenarios. Buoy provides a small, simple, and very thin runtime wrapper over Panama's verbose and subtle parts to make hand-writing native bindings faster, easier, and less error-prone than other alternatives.

Buoy fills a role virtually identical to .NET's P/Invoke and is somewhat similar in usage, though any API similarities are entirely coincidental as Buoy makes no attempt to imitate P/Invoke's API or usage patterns in any capacity.

Why not hand-write Panama bindings directly?

Issues with writing Panama code directly, from a high level perspective:

  • Verbose code, tedious and time-consuming to type.
  • Lots of opportunities for subtle inconsistencies and errors that are well-hidden and hard to find.
  • Difficult to maintain, especially for larger native libraries.

Automatic binding creation with Jextract

Panama has an experimental tool, jextract, that parses C headers and automatically produces bindings corresponding to those headers for the platform it's running on. jextract is a powerful tool and is a huge step forward over hand-written Panama bindings, but it has its own limitations in certain scenarios (particularly libraries that are cross-platform but may leak platform API details into the user-facing API):

  • You must run jextract as part of your build process, and depending on how complex the extraction process is for your use-case this can potentially get a bit messy.
  • Bindings produced by jextract are specific to the platform it runs on. This means that producing cross-platform bindings probably involves either running jextract on each of your intended runtime platforms and then hand-writing a facade that separately calls into the platform-specific Panama bindings, or detailed knowledge and a carefully constructed set of filters to separate out the platform-specific parts of the native libraries into a small subset of the overall binding.
  • jextract-generated bindings tend to be a bit messy, especially anything that deals with struct fields.

Buoy - safely navigating Panama code

For some projects jextract is the best option, but for others it may not be. That's where Buoy comes in. Buoy provides a concise, declarative API for modeling native structs and functions and binding to them, and it supports declaring structs and functions via annotations on Java classes. Buoy's mapper provides powerful facilities for automatically accessing native functions, struct fields, and pointers, and wrapping them in higher-level Java classes. It makes no effort to hide or limit access to Panama features in any way, but it does provide simple and clear shorthand options for most common tasks. It interacts cleanly with Java's polymorphism, allowing use of inheritance and interfaces to model struct nesting and to abstract platform-specific features away from user-facing code. Buoy also provides some facilities to make finding and loading native libraries more pleasant, such as OS/Architecture detection and generating library file names according to standard platform conventions.

Buoy's advantages:

  • Runtime only, very little overhead for populating bound objects and no overhead at invocation time.
  • It takes very little code to access a couple of functions from a platform API (e.g., enabling interactive mode in a Windows terminal).
  • Extremely concise, clean, and declarative binding declarations.
  • Imperative or annotation-based binding declaration, both options are fully interoperable with each other.
  • It's easy and safe to hide platform-specific native APIs behind interfaces.
  • Concise, clean, and easy to use shorthand options for common operations that may be verbose with vanilla Panama (such as dereferencing a pointer).
  • No hiding - at any point you can drop down to directly writing vanilla Panama code with no "gotchas" or magical surprises popping up.

Buoy's disadvantages:

  • You still have to hand-write and maintain binding code. For large API surfaces this may not be desirable.

Project Structure

Buoy itself resides in lib/, and a small, simple example native library that's used for testing resides in native/. The Buoy bindings for the native test library live here and provide a fairly complete example of Buoy's mapping features and some of its utility features as well.