Skip to content

Commit 75a92ca

Browse files
committed
Fixes documentation and javadoc
1 parent 3cbfbbc commit 75a92ca

File tree

13 files changed

+286
-79
lines changed

13 files changed

+286
-79
lines changed

CONCEPTS.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Concepts
2+
3+
The main concept behind the library is to provide easy access to native structures and functions of dynamic libraries written in different languages.
4+
Java FFM API provides solid core objects and patterns to interact with native code, but is lacking of DevEx when working with real native code.
5+
That said, the developer needs to write a lot of boilerplate code to make s simple native call and understand the whole native-java engine under the hood.
6+
7+
To solve this issue `native-memory-processor` consists of two major parts:
8+
1) `annotation` project, which declares core annotations and some helpers to work with native code in runtime.
9+
- `@NativeMemory` - main annotation to interface. Provide header files for parsing or leave empty if you need just native function generation.
10+
- `@NativeMemoryOptions` - annotation to interface, can define optional advanced options to code generation.
11+
- `@Structs` / `@Struct` - annotations to interface, defines the intent to parse structures.
12+
- `@Enums` / `@Enum` - annotations to interface, defines the intent to parse enums.
13+
- `@Unions` / `@Union` - annotations to interface, defines the intent to parse unions.
14+
- `@NativeManualFunction` - annotation to method in interface, defines the native function to be generated.
15+
- `@ByAddress` / `@Returns` - annotation to parameter of method in interface, defines parameter option to send as a pointer / value and native function return object.
16+
17+
2) `annotation-processor` project, which provides processing of annotation described above at compile time in few steps:
18+
- indicate the main `@NativeMemory` annotation on interface
19+
- get provided header files (if any) and generate structures/enums/unions as defined by corresponding annotations
20+
- get annotated by `@NativeManualFunction` methods in interface and generate java-to-native code
21+
22+
In general, library tries to encapsulate the hardcore part of native interacting by code generation and provide user-specific classes with variety of options.
23+
24+
## Difference from jextract
25+
26+
The OpenJDK team had already created a similar tool, called `jextract` to keep developer hands clean from native code.
27+
This is a standalone CLI tool, which works with `libclang` to parse provided header files and generate the Java FFM-ready code.
28+
While it is great in concept, I found it 'not-so-friendly' for a person, who never worked with native code before.
29+
Mainly, the interacting and generated code is hard to understand in different ways:
30+
- structure/function names usually uses `_` and `$` signs or its combinations in generated methods
31+
- no support of opaque structures
32+
- it is HUGE! Simple `gpiochip_info` structure from `gpio.h` kernel UAPI of three fields transforms to 284 lines of code (sic!)
33+
- jextract holds no context of what it is generating. Meaning you can get a header file for defining primitive types by kernel (e.g. `__kernel_sighandler_t` for above structure), which is usually do not needed in your code
34+
- there is no simple way to understand the connections between generated classes, because of it's size and naming
35+
- it is standalone binary, which is needed to be connected somehow to your gradle/maven build toolchain
36+
37+
I tried to get all advantages of `jextract` and create more user-friendly version of it.
38+
Still both `native-memory-processor` and `jextract` uses the same parsing library `libclang` and the quality of parsers are the same, but the process is very different:
39+
- annotation processing by gradle/maven instead of standalone binary
40+
- very specific control over the objects you are parsing with context based approach
41+
- more helpers and code validation

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
![Maven Central Version](https://img.shields.io/maven-central/v/io.github.digitalsmile.native/annotation-processor?label=annotation-processor)
55
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/digitalsmile/native-memory-processor/gradle.yml)
66

7+
#### Create an interop code with ease.
8+
79
## Introduction
810

911
With the release of JDK 22 the new Foreign Function & Memory API (FFM API) has been introduced from preview phase.
@@ -17,7 +19,8 @@ This project goal is to combine the power of FFM API and Java, like annotation p
1719

1820
## Features
1921
- two separate libraries - `annotation` and `annotation-processor` working with code generation during compile time
20-
- C/C++ types support in top level: struct, union, enum
22+
- C/C++ types support in top level: struct, union, enum (including anonymous and opaque)
23+
- context based generation: combine several native libraries within one code base
2124
- can load third party native libraries or use already loaded by classloader
2225
- flexible native-to-java function declarations
2326
- pass to native functions primitives and objects, by value or just a pointer
@@ -38,9 +41,9 @@ Windows hosts are not yet supported.
3841
```groovy
3942
dependencies {
4043
// Annotations to use for code generation
41-
implementation 'io.github.digitalsmile.native:annotation:{$version}'
44+
implementation 'io.github.digitalsmile.native:annotation:${version}'
4245
// Process annotations and generate code at compile time
43-
annotationProcessor 'io.github.digitalsmile.native:annotation-processor:{$version}'
46+
annotationProcessor 'io.github.digitalsmile.native:annotation-processor:${version}'
4447
}
4548
```
4649

@@ -50,18 +53,17 @@ dependencies {
5053
@Structs
5154
@Enums
5255
public interface GPIO {
53-
@NativeFunction(name = "ioctl", useErrno = true, returnType = int.class)
56+
@NativeManualFunction(name = "ioctl", useErrno = true)
5457
int nativeCall(int fd, long command, int data) throws NativeMemoryException;
5558
}
5659
```
5760
This code snippet will generate all structures and enums within the header file `gpio.h` (located in `resources` folder), as well as `GPIONative` class with call implementation of `ioctl` native function.
58-
Find more examples in [documentation](USAGE.md) or in my other project https://github.com/digitalsmile/gpio
61+
62+
Observe concepts in [doucmentation](CONCEPTS.md) or find more examples in [getting started](USAGE.md). You can also look at [tests](annotation-processor-test/src/test/java/io/github/digitalsmile/gpio) or in my other project https://github.com/digitalsmile/gpio
5963

6064
3) Enjoy! :)
6165

6266
## Plans
6367

6468
- more support of native C/C++ types and patterns
65-
- validation of structures parameters and custom validation support
66-
- adding more context on different header files, especially connected with each other
67-
69+
- validation of structures parameters and custom validation support

USAGE.md

Lines changed: 75 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,73 @@
11
# Getting started
22

3-
## Concepts
4-
5-
The main concept behind the library is to provide easy access to native structures and functions of dynamic libraries written in different languages.
6-
Java FFM API provides solid core objects and patterns to interact with native code, but is lacking of DevEx when working with real native code.
7-
That said, the developer needs to write a lot of boilerplate code to make s simple native call and understand the whole native-java engine under the hood.
8-
9-
To solve this issue `native-memory-processor` consists of two major parts:
10-
1) `annotation` project, which declares core annotations and some helpers to work with native code in runtime.
11-
- `@NativeMemory` - main annotation to interface. Provide header files for parsing or leave empty if you need just native function generation.
12-
- `@NativeMemoryOptions` - annotation to interface, can define optional advanced options to code generation.
13-
- `@Structs` / `@Struct` - annotations to interface, defines the intent to parse structures.
14-
- `@Enums` / `@Enum` - annotations to interface, defines the intent to parse enums.
15-
- `@Unions` / `@Union` - annotations to interface, defines the intent to parse unions.
16-
- `@NativeFunction` - annotation to method in interface, defines the native function to be generated.
17-
- `@ByAddress` / `@Returns` - annotation to parameter of method in interface, defines parameter option to send as a pointer / value and native function return object.
18-
19-
2) `annotation-processor` project, which provides processing of annotation described above at compile time in few steps:
20-
- indicate the main `@NativeMemory` annotation on interface
21-
- get provided header files (if any) and generate structures/enums/unions as defined by corresponding annotations
22-
- get annotated by `@NativeFunction` methods in interface and generate java-to-native code
23-
24-
In general, library tries to encapsulate the hardcore part of native interacting by code generation and provide user-specific classes with variety of options.
25-
26-
## Difference from jextract
27-
28-
The OpenJDK team had already created a similar tool, called `jextract` to keep developer hands clean from native code.
29-
This is a standalone CLI tool, which works with `libclang` to parse provided header files and generate the Java FFM-ready code.
30-
While it is great in concept, I found it 'not-so-friendly' for a person, who never worked with native code before.
31-
Mainly, the interacting and generated code is hard to understand in different ways:
32-
- structure/function names usually uses `_` and `$` signs or its combinations in generated methods
33-
- it is HUGE! Simple `gpiochip_info` structure from `gpio.h` kernel UAPI of three fields transforms to 284 lines of code (sic!)
34-
- jextract holds no context of what it is generating. Meaning you can get a header file for defining primitive types by kernel (e.g. `__kernel_sighandler_t` for above structure), which is usually do not needed in your code
35-
- there is no simple way to understand the connections between generated classes, because of it's size and naming
36-
- it is standalone binary, which is needed to be connected somehow to your gradle/maven build toolchain
37-
38-
I tried to get all advantages of `jextract` and create more user-friendly version of it.
39-
Still both `native-memory-processor` and `jextract` uses the same parsing library `libclang` and the quality of parsers are the same, but the process is very different:
40-
- annotation processing by gradle/maven instead of standalone binary
41-
- very specific control over the objects you are parsing with context based approach
42-
- more helpers and code validation
3+
## Quick Start
4+
5+
1) Locate your library header files. Usually, it is a single header file, which interconnects all needed structures/classes. E.g.
6+
```shell
7+
git clone --depth 1 https://github.com/curl/curl.git
8+
cd curl/include/curl
9+
pwd
10+
ls curl.h
11+
```
12+
2) Observe the header file and choose what structures/enums/functions you need.
13+
3) Create an interface `Libcurl` in your java project and locate your `systemIncludes` (or skip if you have it in your
14+
system path). Fill in the header path and selected structures as follows:
15+
```java
16+
@NativeMemory(headers = "libcurl/curl/include/curl/curl.h")
17+
@NativeMemoryOptions(systemIncludes = {
18+
"/usr/lib/gcc/x86_64-linux-gnu/12/include/"
19+
}, processRootConstants = true)
20+
@Structs({
21+
@Struct(name = "CURL", javaName = "CurlInstance")
22+
})
23+
@Enums({
24+
@Enum(name = "CURLcode", javaName = "CURLCode"),
25+
@Enum(name = "CURLoption", javaName = "CURLOption")
26+
})
27+
public interface Libcurl {}
28+
```
29+
4) Run build and observe the generated classes in project `build/generated/sources/annotationProcessor/java/main/{you-package-name}`.
30+
The package name defaults to where the `Libcurl` interface created.
31+
5) Locate you dynamic library `libcurl.so` (or `libcurl.dylib`). You can use precompiled binary or compile it by yourself.
32+
6) Set up the native functions mapping (this is a minimum to run hello world example):
33+
```java
34+
public interface Libcurl {
35+
36+
@NativeManualFunction(name = "curl_easy_init", library = "/usr/lib/x86_64-linux-gnu/libcurl.so")
37+
CURL easyInit() throws NativeMemoryException;
38+
39+
@NativeManualFunction(name = "curl_global_init", library = "/usr/lib/x86_64-linux-gnu/libcurl.so")
40+
CURLcode globalInit(long flags) throws NativeMemoryException;
41+
42+
@NativeManualFunction(name = "curl_easy_setopt", library = "/usr/lib/x86_64-linux-gnu/libcurl.so")
43+
CURLcode easySetOpt(CURL curl, CURLoption option, String value) throws NativeMemoryException;
44+
45+
@NativeManualFunction(name = "curl_easy_setopt", library = "/usr/lib/x86_64-linux-gnu/libcurl.so")
46+
CURLcode easySetOpt(CURL curl, CURLoption option, @ByAddress long value) throws NativeMemoryException;
47+
48+
@NativeManualFunction(name = "curl_easy_perform", library = "/usr/lib/x86_64-linux-gnu/libcurl.so")
49+
CURLcode easyPerform(CURL curl) throws NativeMemoryException;
50+
}
51+
```
52+
8) Run build and observe `LibcurlNative` class at the same location.
53+
7) Write your example method
54+
```java
55+
public static void main(String[] args) throws NativeMemoryException {
56+
try(var libcurl = new LibcurlNative()) {
57+
var code = libcurl.globalInit(CurlConstants.CURL_GLOBAL_DEFAULT);
58+
assertEquals(code, CURLcode.CURLE_OK);
59+
var curl = libcurl.easyInit();
60+
code = libcurl.easySetOpt(curl, CURLoption.CURLOPT_URL, "https://example.com");
61+
assertEquals(code, CURLcode.CURLE_OK);
62+
code = libcurl.easySetOpt(curl, CURLoption.CURLOPT_FOLLOWLOCATION, 1L);
63+
assertEquals(code, CURLcode.CURLE_OK);
64+
65+
code = libcurl.easyPerform(curl);
66+
assertEquals(code, CURLcode.CURLE_OK);
67+
}
68+
}
69+
```
70+
8) Run the main method. You should see the html page of `https://example.com` in stdout.
4371

4472
## Example usages
4573
### Example 1
@@ -50,7 +78,7 @@ Generate all structures, enums and unions from `gpio.h` and generate file `GPION
5078
@Enums
5179
@Unions
5280
public interface GPIO {
53-
@NativeFunction(name = "ioctl", useErrno = true, returnType = int.class)
81+
@NativeManualFunction(name = "ioctl", useErrno = true)
5482
int nativeCall(int fd, long command, int data) throws NativeMemoryException;
5583
}
5684
```
@@ -64,7 +92,7 @@ Method declaration will forward the output of `ioctl` to user code, since `retur
6492
@Struct(name = "gpio_v2_line_info", javaName = "LineInfo")
6593
})
6694
public interface GPIO {
67-
@NativeFunction(name = "ioctl", returnType = int.class)
95+
@NativeManualFunction(name = "ioctl")
6896
int nativeCall(int fd, long command, int data) throws NativeMemoryException;
6997
}
7098
```
@@ -83,10 +111,10 @@ You can declare a freshly generated structure to use within the native functions
83111
@Struct(name = "gpio_v2_line_info", javaName = "LineInfo")
84112
})
85113
public interface GPIO {
86-
@NativeFunction(name = "ioctl", returnType = int.class)
114+
@NativeManualFunction(name = "ioctl")
87115
ChipInfo nativeCall(int fd, long command, @Returns ChipInfo data) throws NativeMemoryException;
88116

89-
@NativeFunction(name = "ioctl", useErrno = true, returnType = int.class)
117+
@NativeManualFunction(name = "ioctl", useErrno = true)
90118
long nativeCall(int fd, long command, @Returns @ByAddress long data) throws NativeMemoryException;
91119
}
92120
```
@@ -106,7 +134,7 @@ You can use system properties `-Dversion=6.2.0-39` to pass variables into header
106134
)
107135
@Structs
108136
public interface GPIO {
109-
@NativeFunction(name = "ioctl", returnType = int.class)
137+
@NativeManualFunction(name = "ioctl")
110138
GpiochipInfo nativeCall(int fd, long command, @Returns GpiochipInfo data) throws NativeMemoryException;
111139
}
112140
```
@@ -119,10 +147,10 @@ Generate `some_struct` structure from `library2_header.h` and implement two nati
119147
@NativeMemory(header = "library2_header.h")
120148
@Structs
121149
public interface GPIO {
122-
@NativeFunction(name = "some_func1", library = "/some/path/to/library1.so")
150+
@NativeManualFunction(name = "some_func1", library = "/some/path/to/library1.so")
123151
void call(int data) throws NativeMemoryException;
124152

125-
@NativeFunction(name = "some_func2", library = "library2")
153+
@NativeManualFunction(name = "some_func2", library = "library2")
126154
SomeStruct nativeCall(int param, @Returns SomeStruct data) throws NativeMemoryException;
127155
}
128156
```

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/libcurl/Libcurl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,4 @@ public interface Libcurl {
4040

4141
@NativeManualFunction(name = "curl_easy_perform", library = "/usr/lib/x86_64-linux-gnu/libcurl.so")
4242
CURLcode easyPerform(CURL curl) throws NativeMemoryException;
43-
4443
}

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/libcurl/LibcurlTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,19 @@ public void libcurl() throws NativeMemoryException {
2323
assertEquals(code, CURLcode.CURLE_OK);
2424
}
2525
}
26+
27+
public static void main(String[] args) throws NativeMemoryException {
28+
try(var libcurl = new LibcurlNative()) {
29+
var code = libcurl.globalInit(CurlConstants.CURL_GLOBAL_DEFAULT);
30+
assertEquals(code, CURLcode.CURLE_OK);
31+
var curl = libcurl.easyInit();
32+
code = libcurl.easySetOpt(curl, CURLoption.CURLOPT_URL, "https://example.com");
33+
assertEquals(code, CURLcode.CURLE_OK);
34+
code = libcurl.easySetOpt(curl, CURLoption.CURLOPT_FOLLOWLOCATION, 1L);
35+
assertEquals(code, CURLcode.CURLE_OK);
36+
37+
code = libcurl.easyPerform(curl);
38+
assertEquals(code, CURLcode.CURLE_OK);
39+
}
40+
}
2641
}

annotation/src/main/java/io/github/digitalsmile/annotation/ArenaType.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
package io.github.digitalsmile.annotation;
22

3-
import java.lang.foreign.Arena;
4-
3+
/**
4+
* Class represents <code>Arena</code> type in string representation for code generation.
5+
*/
56
public enum ArenaType {
6-
GLOBAL, AUTO, CONFINED, SHARED;
7+
/**
8+
* Arena.global()
9+
*/
10+
GLOBAL,
11+
/**
12+
* Arena.ofAuto()
13+
*/
14+
AUTO,
15+
/**
16+
* Arena.ofConfined()
17+
*/
18+
CONFINED,
19+
/**
20+
* Arena.ofShared()
21+
*/
22+
SHARED;
723

24+
/**
25+
* Gets the string representation of arena type.
26+
*
27+
* @return string representation of arena type
28+
*/
829
public String arena() {
930
return switch (this) {
1031
case AUTO -> "ofAuto()";

annotation/src/main/java/io/github/digitalsmile/annotation/NativeMemoryOptions.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,44 @@
2020
*/
2121
boolean processRootConstants() default false;
2222

23+
/**
24+
* Defines <code>Arena</code> that should be used within the generated code.
25+
* Defaults to <code>Arena.ofAuto()</code> if not specified.
26+
*
27+
* @return arena type to be used
28+
*/
2329
ArenaType arena() default ArenaType.AUTO;
2430

31+
/**
32+
* Defines the <code>packageName</code> to be used for storing generated code.
33+
* Leave empty to have the interface package name as default.
34+
*
35+
* @return package name to be used
36+
*/
2537
String packageName() default "";
2638

39+
/**
40+
* Defines the native code include search path to be passed to <code>libclang</code>.
41+
* Use if you have library with complex search path.
42+
*
43+
* @return include array to be passed to <code>libclang</code>
44+
*/
2745
String[] includes() default {};
46+
47+
/**
48+
* Defines the system includes search path to be passed to <code>libclang</code>.
49+
* If you have issues with standard C/C++ header files, provide the path to search with this option.
50+
*
51+
* @return system include array to be passed to <code>libclang</code>
52+
*/
2853
String[] systemIncludes() default {};
2954

55+
/**
56+
* Defines if header files are system and should be parsed.
57+
* By default, system header processing is off to simplify the generated output and skip unused kernel/system structures.
58+
*
59+
* @return true if system headers should be parsed
60+
*/
3061
boolean systemHeader() default false;
3162

3263
boolean debugMode() default false;

0 commit comments

Comments
 (0)