Reader small image

You're reading from  Java Coding Problems - Second Edition

Product typeBook
Published inMar 2024
PublisherPackt
ISBN-139781837633944
Edition2nd Edition
Right arrow
Author (1)
Anghel Leonard
Anghel Leonard
author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard

Right arrow

Foreign (Function) Memory API

This chapter includes 28 problems covering the Foreign Memory API and Foreign Linker API. We’ll start with the classical approaches for calling foreign functions relying on the JNI API and the open-source JNA/JNR libraries. Next, we’ll introduce the new approach delivered under the code name Project Panama (third review in JDK 21 and final release in JDK 22 as JEP 454). We’ll dissect the most relevant APIs, such as Arena, MemorySegment, MemoryLayout, and so on. Finally, we’ll focus on the Foreign Linker API and the Jextract tool for calling foreign functions with different types of signatures, including callback functions.

By the end of this chapter, you’ll be skilled in putting JNI, JNA, JNR, and, of course, Project Panama to work and you’ll be able to confidently answer any interview questions with this topic on the menu.

Problems

Use the following problems to test your programming prowess in manipulating off-heap memory and calling native foreign functions from Java. I strongly encourage you to give each problem a try before you turn to the solutions and download the example programs:

  1. Introducing Java Native Interface (JNI): Write a Java application that calls a C/C++ native foreign function via the JNI API (for instance, implement in C a function with the following signature: long sumTwoInt(int x, int y)).
  2. Introducing Java Native Access (JNA): Write a Java application that calls a C/C++ native foreign function via the JNA API.
  3. Introducing Java Native Runtime (JNR): Write a Java application that calls a C/C++ native foreign function via the JNR API.
  4. Motivating and introducing Project Panama: Provide a theoretical and meaningful transition from classical approaches of manipulating off-heap memory and foreign functions to the new Project Panama.
  5. Introducing Panama...

144. Introducing Java Native Interface (JNI)

Java Native Interface (JNI) was the first Java API meant to act as a bridge between JVM bytecode and native code written in another programming language (typically C/C++).

Let’s suppose that we plan to call via JNI a C function on a Windows 10, 64-bit machine.

For instance, let’s consider that we have a C function for summing two integers called sumTwoInt(int x, int y). This function is defined in a C shared library named math.dll. Calling such functions from Java (generally speaking, functions implemented by native shared libraries) starts with loading the proper shared native library via System.loadLibrary(String library). Next, we declare the C function in Java via the native keyword. Finally, we call it with the following code:

package modern.challenge;
public class Main {
  static { 
    System.loadLibrary("math");
  }
  private native long sumTwoInt(int x, int y);
  public static void main(String...

145. Introducing Java Native Access (JNA)

Java Native Access (JNA) is a brave open-source attempt to address JNI complexity via a more intuitive and easy-to-use API. Being a third-party library, JNA must be added as a dependency in our project:

<dependency>
  <groupId>net.java.dev.jna</groupId>
  <artifactId>jna-platform</artifactId>
  <version>5.8.0</version>
</dependency>

Next, let’s try to call the same sumTwoInt() method from Problem 144. This function is defined in a C native shared library named math.dll and stored in our project in the jna/cpp folder.

We start by writing a Java interface that extends JNA’s Library interface. This interface contains declarations of methods and types that we plan to call from Java and are defined in native code. We write the SimpleMath interface containing the sumTwoInt() declaration as follows:

public interface SimpleMath extends Library { 
  long sumTwoInt...

146. Introducing Java Native Runtime (JNR)

Java Native Runtime (JNR) is another open-source attempt to address JNI’s complexity. It is a serious competitor for JNA, having a more intuitive and powerful API than JNI.

We can add it as a dependency as follows:

<dependency>
    <groupId>com.github.jnr</groupId>
    <artifactId>jnr-ffi</artifactId>
    <version>2.2.13</version>
</dependency>

Let’s assume that we have the exact same C method (sumTwoInt()) and the native shared library (math.dll) from Problem 145.

We start by writing a Java interface containing the declarations of methods and types that we plan to call from Java and are defined in native code. We write the SimpleMath interface containing the sumTwoInt() declaration as follows:

public interface SimpleMath { 
  @IgnoreError
  long sumTwoInt(int x, int y);
}

The @IgnoreError annotation instructs JNR to not save the errno value (https:/...

147. Motivating and introducing Project Panama

Project Panama, or the Foreign Function & Memory (FFM) API, is an elegant way of saying goodbye to JNI. This project started in JDK 17 as JEP 412 (first incubator). It continued in JDK 18 as JEP 419 (second incubator), JDK 19 as JEP 424 (first preview), JDK 20 as JEP 434 (second preview), and JDK 21 as JEP 442 (third preview). This is where things are at the time of writing.

To understand the goals of this project, we have to talk about accessing off-heap memory from Java applications. By off-heap memory, we mean the memory that is outside the JVM heap and is not managed by the garbage collector.

Surfing off-heap is the job of JNI, JNA, and JNR. In one way or another, these APIs can work in off-heap land to handle different tasks. Among these tasks, we can enumerate the following:

  • Use native libraries (for instance, some common libraries are Open CL/GL, CUDA, TensorFlow, Vulkan, OpenSSL, V8, BLAS, cuDNN, and so...

148. Introducing Panama’s architecture and terminology

When we talk about architecture, it helps to present a meaningful diagram, so here it is:

Figure 7.8.png

Figure 7.8: Project Panama architecture

This diagram reveals the interoperability of Panama’s components. The climax of this diagram is the Jextract tool. As you’ll see in this chapter, Jextract is a very handy tool capable of consuming the headers of native libraries and producing low-level Java native bindings. These bindings are the unit of work for two major APIs of Project Panama:

  • Foreign Memory API – used to allocate/deallocate off-heap/on-heap memory
  • Foreign Linker API – used to call foreign functions directly from Java and vice versa

The process described so far is entirely mechanical. When these APIs and the low-level Java native bindings are not enough for our tasks, then we can take things a step further and create a set of higher-level Java bindings. Of...

149. Introducing Arena and MemorySegment

A MemorySegment shapes a heap or native memory segment. A heap segment accesses on-heap memory, while a native segment accesses off-heap memory. In both cases, we talk about a contiguous region of memory that has a lifespan bounded by space and time.

Among its characteristics, a memory segment has a size in bytes, an alignment of bytes, and a scope. The scope is shaped via the java.lang.foreign.MemorySegment.Scope sealed interface and represents the lifespan of the memory segment. A native memory segment lifespan is controlled by a java.lang.foreign.Arena instance. An Arena has a scope that can be:

The arena global scope (or global arena): The memory segments with the arena global scope are always accessible. In other words, the regions of memory allocated to these segments are never deallocated and their global scope remains alive forever.

Attempting to close (close()) this scope will result in UnsupportedOperationException. Here...

150. Allocating arrays into memory segments

Now that we know how to create memory segments for storing single values, let’s take it a step further and try to store an array of integers. For instance, let’s define a memory segment for storing the following array: [11, 21, 12, 7, 33, 1, 3, 6].

A Java int needs 4 bytes (32 bits) and we have 8 integers, so we need a memory segment of 4 bytes x 8 = 32 bytes = 256 bits. If we try to represent this memory segment, then we can do it as in the following figure:

Figure 7.9.png

Figure 7.10: A memory segment of 8 integers

In code lines, we can allocate this memory segment via any of the following approaches (arena is an instance of Arena):

MemorySegment segment = arena.allocate(32);
MemorySegment segment = arena.allocate(4 * 8);
MemorySegment segment = arena.allocate(
  ValueLayout.JAVA_INT.byteSize() * 8);
MemorySegment segment = arena.allocate(Integer.SIZE/8 * 8);
MemorySegment segment = arena.allocate(Integer.BYTES * 8...

151. Understanding addresses (pointers)

A memory segment has a memory address (pointer) expressed as a long number. An off-heap memory segment has a physical address that points out the memory region that backs the segment (base address). Each memory layout stored in this segment has its own memory address as well. For instance, here is an example of querying the base address of a memory segment via the address() method (arena is an instance of Arena):

MemorySegment segment = arena
  .allocate(ValueLayout.JAVA_INT, 1000);
long addr = segment.address(); // 2620870760384

On the other hand, an on-heap memory segment has a non-physical stable virtualized address typically representing an offset within the memory region of that segment (the client sees a stable address while the garbage collector can reallocate the region of memory inside the heap). For instance, an on-heap segment created via one of the ofArray() factory methods has an address of 0.

Next, let’s focus...

152. Introducing the sequence layout

In Problem 149, we already covered the ValueLayout for basic data types. Next, let’s talk about the sequence layout (java.lang.foreign.SequenceLayout).

But before introducing the sequence layout, let’s take a moment to analyze the following snippet of code:

try (Arena arena = Arena.ofConfined()) {
  MemorySegment segment = arena.allocate(
    ValueLayout.JAVA_DOUBLE.byteSize() * 10,
    ValueLayout.JAVA_DOUBLE.byteAlignment());
  for (int i = 0; i < 10; i++) {
    segment.setAtIndex(ValueLayout.JAVA_DOUBLE,
      i, Math.random());
  }
  for (int i = 0; i < 10; i++) {
    System.out.printf("\nx = %.2f",
      segment.getAtIndex(ValueLayout.JAVA_DOUBLE, i));
  }
}

We start by creating a native memory segment for storing 10 double values. Next, we rely on setAtIndex() to set these double values. Finally, we print them.

So, basically, we repeat the ValueLayout.JAVA_DOUBLE 10 times. When an element layout...

153. Shaping C-like structs into memory segments

Let’s consider the C-like struct from the following figure:

Figure 7.16.png

Figure 7.13: A C-like structure

So, in Figure 7.13, we have a C-like struct named point to shape an (x, y) pair of double values. Moreover, we have 5 such pairs declared under the name pointarr. We can try to shape a memory segment to fit this model as follows (arena is an instance of Arena):

MemorySegment segment = arena.allocate(
  2 * ValueLayout.JAVA_DOUBLE.byteSize() * 5,
  ValueLayout.JAVA_DOUBLE.byteAlignment());

Next, we should set (x, y) pairs into this segment. For this, we can visualize it as follows:

Figure 7.17.png

Figure 7.14: Memory segment to store (x, y) pairs

Based on this diagram, we can easily come up with the following snippet of code for setting the (x, y) pairs:

for (int i = 0; i < 5; i++) {
  segment.setAtIndex(
    ValueLayout.JAVA_DOUBLE, i * 2, Math.random());
  segment.setAtIndex(
    ValueLayout.JAVA_DOUBLE, i *...

154. Shaping C-like unions into memory segments

Let’s consider the C-like union from the following figure (the members of a C union share the same memory location (the member’s largest data type dictates the size of the memory location), so only one of the members has a value at any moment in time):

Figure 7.18.png

Figure 7.15: A C-like union

In Figure 7.15, we have a C-like union named product to shape two members, price (double) and sku (int), while only one can have a value at any moment in time. We can shape a memory segment to fit this model as follows (arena is an instance of Arena):

MemorySegment segment = arena.allocate(
  ValueLayout.JAVA_DOUBLE.byteSize(),
  ValueLayout.JAVA_DOUBLE.byteAlignment());

Because double needs 8 bytes and int needs only 4 bytes, we choose ValueLayout.JAVA_DOUBLE to shape the size of the memory segment. This way, the segment can accommodate a double and an int at the same offset.

Next, we can set the price or the sku and...

155. Introducing PaddingLayout

Data types are typically characterized by several properties: size, alignment, stride, padding, and order of bytes.

The padding layout (java.lang.foreign.PaddingLayout) allows us to specify the padding. In other words, PaddingLayout allows us to add at certain offsets some extra space that is usually ignored by the applications but is needed to align the member layouts of a memory segment.

For instance, let’s consider the following two memory segments (the left-hand side is a memory segment without padding, while the right-hand side is a memory segment with two paddings of 4 bytes each).

Figure 7.19.png

Figure 7.16: Memory segments with (right-hand side)/without (left-hand side) padding

In code lines, the padding-free memory segment can be shaped as follows:

StructLayout npStruct = MemoryLayout.structLayout( 
  ValueLayout.JAVA_INT.withName("x"), 
  ValueLayout.JAVA_INT.withName("y")
);

Since the size of JAVA_INT...

156. Copying and slicing memory segments

Let’s consider the following memory segment (arena is an instance of Arena):

MemorySegment srcSegment = arena.allocateArray(
  ValueLayout.JAVA_INT, 1, 2, 3, 4, -1, -1, -1, 
                        52, 22, 33, -1, -1, -1, -1, -1, 4);

Next, let’s see how we can copy the content of this segment.

Copying a segment

We can make a copy of this memory segment via copyFrom(MemorySegment src) as follows:

MemorySegment copySegment = srcSegment.copyFrom(srcSegment);

We can easily see if the data was copied as follows:

System.out.println("Data: " + Arrays.toString(
  copySegment.toArray(ValueLayout.JAVA_INT)));

This is a bulk operation that creates a full copy of the given memory segment.

Copying a part of the segment into another segment (1)

Let’s suppose that we want to copy only a part of srcSegment into another segment (dstSegment). For instance, if we wanted to copy the last...

157. Tackling the slicing allocator

Let’s consider the following three Java regular int arrays:

int[] arr1 = new int[]{1, 2, 3, 4, 5, 6};
int[] arr2 = new int[]{7, 8, 9};
int[] arr3 = new int[]{10, 11, 12, 13, 14};

Next, we want to allocate a memory segment to each of these arrays. A straightforward approach relies on Arena.allocateArray() introduced in Problem 150:

try (Arena arena = Arena.ofConfined()) {
  MemorySegment segment1 
    = arena.allocateArray(ValueLayout.JAVA_INT, arr1);
  MemorySegment segment2 
    = arena.allocateArray(ValueLayout.JAVA_INT, arr2);
  MemorySegment segment3 
    = arena.allocateArray(ValueLayout.JAVA_INT, arr3);
}

This approach allocates enough memory to accommodate each of the given arrays. But, sometimes, we want to allocate only a certain amount of memory. If this fixed amount is not enough, then we want to tackle the problem differently. For this, we can rely on a java.lang.foreign.SegmentAllocator. Of course, there are...

158. Introducing the slice handle

Let’s suppose that we have the following nested model (10 sequences of 5 double values each):

SequenceLayout innerSeq
  = MemoryLayout.sequenceLayout(5, ValueLayout.JAVA_DOUBLE);
SequenceLayout outerSeq
  = MemoryLayout.sequenceLayout(10, innerSeq);

Next, we define a VarHandle via PathElement and we populate this model accordingly with some random data:

VarHandle handle = outerSeq.varHandle(
  PathElement.sequenceElement(),
  PathElement.sequenceElement());
try (Arena arena = Arena.ofConfined()) {
  MemorySegment segment = arena.allocate(outerSeq);
  for (int i = 0; i < outerSeq.elementCount(); i++) {
    for (int j = 0; j < innerSeq.elementCount(); j++) {
      handle.set(segment, i, j, Math.random());
    }
  }
}

OK, you should be familiar with this code, so nothing new so far. Next, we plan to extract from this model the third sequence from 10 containing 5 sequences of double values. We can accomplish this via the...

159. Introducing layout flattening

Let’s suppose that we have the following nested model (the exact same model as in Problem 158):

SequenceLayout innerSeq
  = MemoryLayout.sequenceLayout(5, ValueLayout.JAVA_DOUBLE);
SequenceLayout outerSeq
  = MemoryLayout.sequenceLayout(10, innerSeq);

Next, we define a VarHandle via PathElement and we populate this model accordingly with some random data in a memory segment named segment (you can see the code listed in Problem 158).

Our goal is to take this nested model and obtain a flat model. So, instead of having 10 sequences of 5 double values each, we want one sequence of 50 double values. This can be achieved via the flatten() method, as follows:

SequenceLayout flatten = outerSeq.flatten();
VarHandle fhandle = flatten.varHandle(
  PathElement.sequenceElement());
for (int i = 0; i < flatten.elementCount(); i++) {
  System.out.printf("\nx = %.2f", fhandle.get(segment, i));
}

Notice the PathElement, which...

160. Introducing layout reshaping

Let’s suppose that we have the following nested model (the exact same model as in Problem 158):

SequenceLayout innerSeq
  = MemoryLayout.sequenceLayout(5, ValueLayout.JAVA_DOUBLE);
SequenceLayout outerSeq
  = MemoryLayout.sequenceLayout(10, innerSeq);

Next, we define a VarHandle via PathElement and we populate this model accordingly with some random data (you can see the code listed in Problem 158).

Our goal is to reshape this model to look as follows:

SequenceLayout innerSeq
  = MemoryLayout.sequenceLayout(25, ValueLayout.JAVA_DOUBLE);
SequenceLayout outerSeq
  = MemoryLayout.sequenceLayout(2, innerSeq);

So, instead of having 10 sequences of 5 double values each, we want 25 sequences of 2 double values each. In order to accomplish this reshaping goal, we can rely on the reshape(long... elementCounts) method. This method takes the elements of this sequence layout and re-arranges them into a multi-dimensional sequence layout...

161. Introducing the layout spreader

Let’s suppose that we have the following nested model (the exact same model as in Problem 158):

SequenceLayout innerSeq
  = MemoryLayout.sequenceLayout(5, ValueLayout.JAVA_DOUBLE);
SequenceLayout outerSeq
  = MemoryLayout.sequenceLayout(10, innerSeq);

Next, we define a VarHandle via PathElement and we populate this model accordingly with some random data in a memory segment named segment (you can see the code listed in Problem 158).

Next, let’s assume that we want to extract the third double value from the seventh sequence (count starts from 0). Among the approaches, we can rely on sliceHandle() introduced in Problem 158, as follows:

MethodHandle mHandle = outerSeq.sliceHandle(
  PathElement.sequenceElement(),
  PathElement.sequenceElement());
MemorySegment ms = (MemorySegment)     
  mHandle.invokeExact(segment, 7L, 3L);
System.out.println(ms.get(ValueLayout.JAVA_DOUBLE, 0));

Another approach consists of using...

162. Introducing the memory segment view VarHandle

Let’s consider the following simple memory segment for storing an int (arena is an instance of Arena):

MemorySegment segment = arena.allocate(ValueLayout.JAVA_INT);

We know that we can create a VarHandle via PathElement:

// VarHandle[varType=int, 
// coord=[interface java.lang.foreign.MemorySegment]]
VarHandle handle = ValueLayout.JAVA_INT.varHandle();

Or, via arrayElementVarHandle():

// VarHandle[varType=int, 
// coord=[interface java.lang.foreign.MemorySegment, long]]
VarHandle arrhandle
  = ValueLayout.JAVA_INT.arrayElementVarHandle();

The MethodHandles.memorySegmentViewVarHandle(ValueLayout layout) is another approach for creating a VarHandle that can be used to access a memory segment. The returned VarHandle perceives/views the content of the memory segment as a sequence of the given ValueLayout. In our case, the code looks as follows:

// VarHandle[varType=int, 
// coord=[interface java.lang...

163. Streaming memory segments

Combining the Java Stream API with memory segments can be achieved via the elements(MemoryLayout elementLayout) method. This method gets an element layout and returns a Stream<MemorySegment>, which is a sequential stream over disjointed slices in this segment. The stream size matches the size of the specified layout.

Let’s consider the following memory layout:

SequenceLayout xy = MemoryLayout
  .sequenceLayout(2, MemoryLayout.structLayout(
    ValueLayout.JAVA_INT.withName("x"),
    ValueLayout.JAVA_INT.withName("y")));

Next, we declare two VarHandle and set some data:

VarHandle xHandle = xy.varHandle(
  PathElement.sequenceElement(),
  PathElement.groupElement("x"));
VarHandle yHandle = xy.varHandle(
  PathElement.sequenceElement(), 
  PathElement.groupElement("y"));
try (Arena arena = Arena.ofShared()) {
  MemorySegment segment = arena.allocate(xy);
  xHandle.set(segment, 0, 5);...

164. Tackling mapped memory segments

We know that a computer has limited physical memory, referred to as RAM. Common sense, though, tells us that we cannot allocate a memory segment larger than the available RAM (this should lead to an out-of-memory error). But this is not quite true! Here is where mapped memory segments come into the discussion.

The mapped memory segment represents virtual memory and can be huge (gigabytes, terabytes, or whatever you may think of). This virtual memory is actually memory mapped by files or shortly memory-mapped files (a file can be from a regular file to any other kind of file descriptor).

Obviously, at any time, only a part of the virtual memory lives in the real memory. This is why we can allocate terabytes of virtual memory on a laptop with much less real RAM. Practically, a portion of missing mapped memory is loaded on demand in the real RAM. While loading, the process operating on this memory is temporarily suspended.

The goal of...

165. Introducing the Foreign Linker API

The main goal of the Foreign Linker API is to provide a robust and easy-to-use API (no need to write C/C++ code) for sustaining interoperability between the Java code and C/C++ foreign functions of native shared libraries (in the future, other programming languages will be supported via this API).

The journey of calling foreign code starts with the java.lang.foreign.SymbolLookup functional interface. This interface represents the entry point and consists of looking up the address of a given symbol in a loaded native shared library. There are three ways of doing this, as follows:

Linker.defaultLookup() – as its name suggests, defaultLookup() represents a default lookup that scans and locates all the symbols of the commonly used native shared libraries depending on the current operating system:

Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();

SymbolLookup.loaderLookup() – represents...

166. Calling the sumTwoInt() foreign function

Do you remember the sumTwoInt() function? We have defined this C function in a native shared library named math.dll (check Problems 144, 145, and 146). Let’s assume that we have placed the math.dll library in the project folder under the lib/cpp path.

We can call this foreign function in almost the same manner as we’ve called _getpid(). Since math.dll is a user-defined library that is not commonly used, it cannot be loaded via defaultLookup(). The solution is to explicitly load the library from the lib/cpp path, as follows:

Linker linker = Linker.nativeLinker();
Path path = Paths.get("lib/cpp/math.dll");
try (Arena arena = Arena.ofConfined()) { 
  SymbolLookup libLookup = SymbolLookup.libraryLookup(
    path, arena);
  ...

Next, we have to find in math.dll the foreign function by name. If your C compiler (for instance, G++) has applied the mangling (or name decoration) technique, then sumTwoInt will...

167. Calling the modf() foreign function

Let’s consider that we want to call the modf() foreign function. This function is part of the C standard library with the following syntax (https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/modf-modff-modfl):

double modf(double x, double *intptr);

This method gets a double x and returns the signed fractional part of x. The intptr is a pointer argument used to point to the memory address where the integer part should be stored as a double value.

Since this method is part of UCRT, it can be found via defaultLookup():

Linker linker = Linker.nativeLinker();
SymbolLookup libLookup = linker.defaultLookup();
try (Arena arena = Arena.ofConfined()) {
  MemorySegment segmentModf = libLookup.find("modf").get();
  ...

Nothing new so far! Next, we need to define the proper MethodHandle. Because the second argument of modf() is a pointer, we need to specify a value layout of type ADDRESS:

  MethodHandle...

168. Calling the strcat() foreign function

The strcat() foreign function is part of the C standard library and has the following signature (https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strcat-wcscat-mbscat):

char *strcat(char *strDestination, const char *strSource);

This function appends the strSource at the end of the strDestination. The function doesn’t get these strings. It gets two pointers to these strings (so, two ADDRESS) and doesn’t return a value, so we rely on FunctionDescriptor.ofVoid(), as follows:

Linker linker = Linker.nativeLinker();
SymbolLookup libLookup = linker.defaultLookup();
try (Arena arena = Arena.ofConfined()) {
  MemorySegment segmentStrcat
    = libLookup.find("strcat").get();
  MethodHandle func = linker.downcallHandle(
    segmentStrcat, FunctionDescriptor.ofVoid(
      ValueLayout.ADDRESS, ValueLayout.ADDRESS));
  ...

Since the arguments of strcat() are two pointers (ADDRESS), we have to create...

169. Calling the bsearch() foreign function

The bsearch() foreign function is part of the C standard library and has the following signature (https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/bsearch):

void *bsearch(
  const void *key,
  const void *base,
  size_t num,
  size_t width,
  int ( __cdecl *compare ) (
    const void *key, const void *datum)
);

In a nutshell, this method gets pointers to a key, a sorted array (base), and a comparator. Its goal is to use the given comparator to perform a binary search of the given key in the given array. More precisely, bsearch() gets a pointer to the key, a pointer to the array, the number of elements in the array (num), the size of an element in bytes (width), and the comparator as a callback function.

The callback function gets a pointer to key and a pointer to the current element of the array to be compared with key. It returns the result of comparing these two elements.

The bsearch() function returns...

170. Introducing Jextract

Jextract (https://github.com/openjdk/jextract) is a very handy tool capable of consuming the headers of native libraries (*.h files) and producing low-level Java native bindings. Via this tool, we can save a lot of time since we can focus only on calling native code without caring about the mechanical steps of loading libraries, writing method handles, or downcall and upcall stubs.

Jextract is a command-line tool that can be downloaded from https://jdk.java.net/jextract. The main options of this tool are listed here:

  • --source: When we write jextract --source, we instruct Jextract to generate from the given header file the corresponding source files without classes. When this option is omitted, Jextract will generate classes.
  • -- output path: By default, the generated files are placed in the current folder. Via this option, we can point out the path where these files should be placed.
  • -t <package>: By default, Jextract uses...

171. Generating native binding for modf()

In Problem 160, we located, prepared, and called the modf() foreign function via the Foreign Linker API. Now, let’s use Jextract to generate the native binding needed to call modf().

For Windows, the modf() foreign function is described in the math.h header file. If you have installed MinGW (https://sourceforge.net/projects/mingw-w64/) for 64-bit, then this header file is available in the mingw64\x86_64-w64-mingw32\include folder. If we want to generate the native bindings for math.h, we can do it as follows:

Figure 7.25.png

Figure 7.26: Generating the native bindings from math.h

Or, as plain text:

C:\SBPBP\GitHub\Java-Coding-Problems-Second-Edition\Chapter07\P171_JextractAndModf>
  jextract --source --output src\main\java -t c.lib.math
  -I C:\MinGW64\mingw64\x86_64-w64-mingw32\include 
  C:\MinGW64\mingw64\x86_64-w64-mingw32\include\math.h

So, we generated the source files (--sources) in the src\main\java subfolder of...

Summary

This chapter covered 28 problems. Most of them were focused on the new Foreign (Function) Memory APIs, or Project Panama. As you saw, this API is much more intuitive and powerful than the classical approaches of using JNI, JNA, and JNR. Moreover, the Jextract tool is very handy for generating native bindings from the headers of native shared libraries and saves us a lot of mechanical work.

Join our community on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://discord.gg/8mgytp5DGQ

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024Publisher: PacktISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard