2021-09-14 21:29:41 +00:00
# coffi
2021-09-24 19:48:27 +00:00
[](https://clojars.org/org.suskalo/coffi)
2024-09-30 16:17:04 +00:00
Coffi is a foreign function interface library for Clojure, using the [Foreign
Function & Memory API](https://openjdk.org/jeps/454) in JDK 22 and later. This
allows calling native code directly from Clojure without the need for either
Java or native code specific to the library, as e.g. the JNI does. Coffi focuses
on ease of use, including functions and macros for creating wrappers to allow
the resulting native functions to act just like Clojure ones, however this
doesn't remove the ability to write systems which minimize the cost of
marshaling data and optimize for performance, to make use of the low-level
2024-10-04 20:00:57 +00:00
access the FF& M API gives us.
2021-09-24 19:48:27 +00:00
2024-10-04 20:15:36 +00:00
- [Getting Started ](https://igjoshua.github.io/coffi/01-Getting-Started.html )
- [API Documentation ](https://igjoshua.github.io/coffi/ )
2024-10-04 20:11:21 +00:00
- [Recent Changes ](CHANGELOG.md )
2021-09-24 19:48:27 +00:00
## Installation
2024-10-04 20:00:57 +00:00
This library is available on Clojars, or as a git dependency. Add one of the
following entries to the `:deps` key of your `deps.edn` :
2021-09-24 19:48:27 +00:00
```clojure
2024-10-04 15:50:32 +00:00
org.suskalo/coffi {:mvn/version "1.0.486"}
io.github.IGJoshua/coffi {:git/tag "v1.0.486" :git/sha "c61090c"}
2021-09-29 01:47:30 +00:00
```
If you use this library as a git dependency, you will need to prepare the
library.
```sh
$ clj -X:deps prep
2021-09-24 19:48:27 +00:00
```
2024-09-30 16:17:04 +00:00
Coffi requires usage of the package `java.lang.foreign` , and most of the
operations are considered unsafe by the JDK, and are therefore unavailable to
your code without passing some command line flags. In order to use coffi, add
the following JVM arguments to your application.
2021-09-30 13:19:00 +00:00
```sh
2024-09-30 16:17:04 +00:00
--enable-native-access=ALL-UNNAMED
2021-09-30 13:19:00 +00:00
```
2022-02-03 19:47:45 +00:00
You can specify JVM arguments in a particular invocation of the Clojure CLI with
2024-10-04 20:11:21 +00:00
the `-J` flag like so:
2022-02-03 19:47:45 +00:00
``` sh
2024-09-30 16:17:04 +00:00
clj -J--enable-native-access=ALL-UNNAMED
2022-02-03 19:47:45 +00:00
```
You can also specify them in an alias in your `deps.edn` file under the
`:jvm-opts` key (see the next example) and then invoking the CLI with that alias
using `-M` , `-A` , or `-X` .
``` clojure
2024-09-30 16:17:04 +00:00
{:aliases {:dev {:jvm-opts ["--enable-native-access=ALL-UNNAMED"]}}}
2022-02-03 19:47:45 +00:00
```
Other build tools should provide similar functionality if you check their
documentation.
2021-09-30 13:19:00 +00:00
2024-09-30 16:17:04 +00:00
When creating an executable jar file, you can avoid the need to pass this
argument by adding the manifest attribute `Enable-Native-Access: ALL-UNNAMED` to
2024-10-04 20:00:57 +00:00
your jar. See your build tool's documentation for how to add this.
2024-09-30 16:17:04 +00:00
2021-09-25 18:49:37 +00:00
Coffi also includes support for the linter clj-kondo. If you use clj-kondo and
this library's macros are not linting correctly, you may need to install the
2024-09-30 16:17:04 +00:00
config bundled with the library. You can do so with the following shell command,
run from your project directory:
2021-09-25 18:49:37 +00:00
```sh
$ clj-kondo --copy-configs --dependencies --lint "$(clojure -Spath)"
```
2021-09-24 19:48:27 +00:00
## Usage
2024-10-04 20:00:57 +00:00
The two main namespaces are `coffi.mem` which provides functions for allocating
and manipulating off-heap memory and (de)serializing values, and `coffi.ffi`
which can load native libraries, declare native function wrappers, and
(de)serialize functions as callbacks.
2021-09-24 19:48:27 +00:00
```clojure
2024-10-04 20:00:57 +00:00
(require '[coffi.mem :as mem])
2021-09-26 19:11:29 +00:00
(require '[coffi.ffi :as ffi :refer [defcfn]])
2021-09-24 19:48:27 +00:00
(defcfn strlen
"Given a string, measures its length in bytes."
2021-09-26 19:11:29 +00:00
strlen [::mem/c-string] ::mem/long)
2021-09-24 19:48:27 +00:00
(strlen "hello")
;; => 5
2021-09-25 18:46:26 +00:00
(ffi/load-system-library "z")
```
2024-10-04 20:00:57 +00:00
In the `coffi.mem` namespace there are types for all the signed primitive
numeric types in C, plus `::mem/pointer` and `::mem/c-string` , and ways to use
malli-like type declarations to define structs, unions, arrays, enums, and
flagsets.
2021-09-14 21:29:41 +00:00
2021-10-09 16:21:58 +00:00
## Alternatives
This library is not the only Clojure library providing access to native code. In
2024-10-04 20:00:57 +00:00
addition the following libraries (among others) exist:
2021-10-09 16:21:58 +00:00
- [dtype-next ](https://github.com/cnuernber/dtype-next )
- [tech.jna ](https://github.com/techascent/tech.jna )
- [clojure-jna ](https://github.com/Chouser/clojure-jna )
2024-10-04 20:00:57 +00:00
Dtype-next has support for Java versions 8-15, 17+, and GraalVM, but is focused
2021-10-17 21:08:27 +00:00
strongly on array-based programming, as well as being focused on keeping memory
in the native side rather than marshaling data to and from Clojure-native
2024-10-04 20:00:57 +00:00
structures. In Java 17+, this uses the Foreign Function & Memory API (a part of
Project Panama until stabilization in JDK 22), while in other Java versions it
uses JNA.
2021-10-09 16:21:58 +00:00
Tech.jna and clojure-jna both use the JNA library in all cases, and neither
2024-10-04 20:00:57 +00:00
provide explicit support for callbacks. JNA allows the use of
`java.nio.ByteBuffer` s to pass structs by value, and both libraries provide ways
to use this by-value construction to call by-reference apis.
2021-10-09 16:21:58 +00:00
An additional alternative to coffi is to directly use the JNI, which is the
longest-standing method of wrapping native code in the JVM, but comes with the
downside that it requires you to write both native and Java code to use, even if
you only intend to use it from Clojure.
If your application needs to be able to run in earlier versions of the JVM than
2024-10-04 20:00:57 +00:00
22, you should consider these other options. Dtype-next provides the most robust
support for native code, but if you are wrapping a simple library then the other
libraries may be more appealing, as they have a smaller API surface area and
it's easier to wrap functions.
2021-10-09 16:22:08 +00:00
2024-10-04 20:00:57 +00:00
There is also a [third party round up ](https://docs.google.com/spreadsheets/d/1ViLHNUgrO2osh2AH0h7MaCaXz8g0UpLbyWojY5f10kk/edit?gid=332155605#gid=332155605 )
of FFI options for Clojure.
2021-10-09 16:22:08 +00:00
2021-09-25 18:41:14 +00:00
## Known Issues
The project author is aware of these issues and plans to fix them in a future
release:
2024-10-02 19:47:21 +00:00
- When generating docs with codox in a library that depends on coffi, the below error will be produced. A temporary workaround is to add an explicit dependency in your codox build on insn at version 0.2.1
2024-10-03 20:18:49 +00:00
```
Unable to find static field: ACC_OPEN in interface org.objectweb.asm.Opcodes
```
2021-09-25 18:41:14 +00:00
## Future Plans
These features are planned for future releases.
- Support for va_args type
2024-10-04 20:00:57 +00:00
- Header parsing tool for generating a data model? (maybe just work with [clong ](https://github.com/phronmophobic/clong )?)
2021-09-26 19:11:29 +00:00
- Generic type aliases
2022-04-11 22:09:22 +00:00
- Unsigned integer types
- Record-based struct types
2021-10-07 20:57:02 +00:00
- Helper macro for out arguments
2021-10-08 17:57:31 +00:00
- Improve error messages from defcfn macro
2021-10-09 12:26:12 +00:00
- Mapped memory
2024-10-04 20:00:57 +00:00
- Helper macros for custom serde implementations for composite data types (this is in progress [for structs ](https://github.com/IGJoshua/coffi/issues/12 )!)
2021-09-25 18:41:14 +00:00
2021-09-14 21:29:41 +00:00
## License
2023-03-31 16:02:54 +00:00
Copyright © 2023 Joshua Suskalo
2021-09-14 21:29:41 +00:00
Distributed under the Eclipse Public License version 1.0.