Busy Java Developer's Guide to Native Code

ted.neward@newardassociates.com | Blog: http://blogs.newardassociates.com | Github: tedneward | LinkedIn: tedneward

Objectives

What are we here to do today?

JVM FFI

The Foreign Function Interface

JVM FFI

The JVM wants/needs to interact with the platform beneath it

JVM FFI

Definitions

FFI: foreign function interface

The interface by which control transitions between VM-controlled code and "native" code outside the VM's sight or control

JVM FFI

FFIs usually offer two things; sometimes three

JVM FFI

NOTE: Using any FFI usually requires native knowledge

JVM FFI

JVM FFI is documented in Java Native Interface (JNI) spec

JVM FFI

Recent (circa 2018) efforts

Java Native Interface

The Foreign Function Interface for the JVM

JNI

History

JNI

Love-hate relationship

JNI

Three forms

JNI

JNIEnv

Java-calling-native

Going from Java code to native code

Steps

Write the Java

    Declare a native method in Java class

    Compile the Java code

Steps

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/JNIExample.java NOT FOUND>>

Steps

Write the native implementation

    (Optional) Create a C/C++ header

    Implement the expected function endpoint

Steps

Write the native implementation

    (Optional) Implement load/unload entry points

Steps

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/linux/JNIExample.c NOT FOUND>>

Steps

When in doubt, verify exported symbols

Steps

Load the native library in Java code

Steps

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/JNIExample.java NOT FOUND>>

Java-calling-native

Native note

Java-calling-native

Swift (macOS)

Swift/macOS

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/macos/JNIExample.swift NOT FOUND>>

Swift/macOS

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/macos/JNIExample.swift NOT FOUND>>

Java-calling-native

Rust (Linux)

Java-calling-native

Create Rust project

Rust/linux

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/linux-rust/Cargo.toml NOT FOUND>>

Java-calling-native

Write the exported function

Rust/linux

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/linux-rust/src/lib.rs NOT FOUND>>

Java-calling-native

Build! Run!

Java-calling-native

Nim

Java-calling-native

Write the exported function

Java-calling-native

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingNative/linux-nim/JNIExample.nim NOT FOUND>>

Java-calling-native

Build! Run!

Native-calling-Java

Going back into the JVM from native code

Native-calling-Java

Native code calling Java takes two forms

Native-calling-Java

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/CallingJava/win32/JNIExample.cpp NOT FOUND>>

Native-calling-Java

Be careful of platform considerations

JNI Invocation

Bringing up the JVM in your native process

JNI Invocation

JVM is really "just" a set of libraries

JNI Invocation

Steps

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/Main.java NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/Launcher.cpp NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/Launcher.cpp NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/Launcher.cpp NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/Launcher.cpp NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/Launcher.cpp NOT FOUND>>

JNI Invocation

Build

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/win32/build.bat NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/linux/build.sh NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/macos/build.sh NOT FOUND>>

JNI Invocation

Run

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/win32/run.bat NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/linux/run.sh NOT FOUND>>

JNI Invocation

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNI/code/Launcher/macos/run.sh NOT FOUND>>

JNI Resources

Resources

Online

Java Native Access (JNA)

A convenience layer atop JNI

Java Native Access (JNA)

Java Native Access

Installing JNA

Hooking it all up

Installing JNA

Download

Installing JNA

Maven install


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


Installing JNA

Loading JNA

JNA Examples

Exploring with code

JNA Examples

Using Standard C library -- any platform

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNA/code/JNAHelloWorld.java NOT FOUND>>

JNA Examples

Using Win32 MessageBox -- Windows only

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNA/code/Win32Hello.java NOT FOUND>>

JNA Examples

Using SDL2 -- any platform (theoretically)

<</home/runner/work/Slides/Slides/Content/JVM/FFI/JNA/code/SDLHello.java NOT FOUND>>

GraalVM

Overview

GraalVM

What is it?

GraalVM

What is it?

GraalVM

Several distinct "flavors"

Graal LLVM

Interoperability via LLVM bitcode

Graal LLVM

GraalVM can run LLVM bitcode

Graal LLVM

Simple native function, LLVM side

Graal LLVM

extern "C" void sayHello() {
    cout << "Hello, from LLVM!" << endl;
}

Graal LLVM

Simple native function, Java side

Graal LLVM

        Context polyglot = Context.newBuilder().
        		               allowAllAccess(true).
                               build();
        File file = new File("./hello.so");
        Source source = Source.newBuilder("llvm", file).build();
        Value clib = polyglot.eval(source);

Graal LLVM

Simple native function, Java side

Graal LLVM

        Value sayHello = clib.getMember("sayHello");
        sayHello.execute();

Graal LLVM

Build! Run!

Graal LLVM

CPP = $(LLVM_TOOLCHAIN)/clang++
CPPFLAGS = -shared -c -O -emit-llvm

LIBS=-lgraalvm-llvm

.PHONY: all clean toolchain-check

all: toolchain-check hello.so LLVMExample.class

toolchain-check:
	echo LLVM_TOOLCHAIN = $(LLVM_TOOLCHAIN)

hello.so: hello.cpp
	$(CPP) $(CPPFLAGS) hello.cpp -o hello.so $(LIBS)

LLVMExample.class: LLVMExample.java
	javac LLVMExample.java

clean:
	rm *.so
	rm *.class

Graal LLVM

Working with Java values

Graal LLVM

extern "C" void printMessage(polyglot_value message) {
    if (::polyglot_is_string(message)) {
        uint64_t size = ::polyglot_get_string_size(message) + 1; // Make sure we account for a NULL
        char buffer[size];
        memset(buffer, 0, size);
        uint64_t written = ::polyglot_as_string(message, buffer, size, "utf-8");
        cout << "Your message was " << buffer << endl;
    }
    else {
        cout << "Not sure what we've got here" << endl;
    }
}

Graal LLVM

Calling back into the JVM

Graal LLVM

extern "C" void sysout(polyglot_value message) {
    bool result = ::polyglot_is_string(message); assert(result);
    uint64_t size = ::polyglot_get_string_size(message) + 1; // Make sure we account for a NULL
    char buffer[size];
    memset(buffer, 0, size);
    uint64_t written = ::polyglot_as_string(message, buffer, size, "utf-8");
    
    polyglot_value java_lang_System_class = ::polyglot_java_type("java.lang.System");
    polyglot_value java_lang_System_out = ::polyglot_get_member(java_lang_System_class, "out");
    ::polyglot_invoke(java_lang_System_out, "println", message);
}

Graal Resources

Where to go to get more

Graal Resources

Official

Graal Resources

Books

Videos

Project Panama

Interconnecting JVM and native code

Project Panama

Proposed enhancement to JVM

Summary

Native code represents a powerful extension to Java code, but with risks

Credentials

Who is this guy?