To try out the Java 25 FFM API, I wanted to call C++ code through a C wrapper.
To do that, I created a small C++ class and a C wrapper following an example from here.
Printer.h
#pragma once
#include <iostream>
class Printer
{
public:
void print(const std::string& message);
};
Printer.cpp
#include "Printer.h"
void Printer::print(const std::string& message)
{
std::cout << message << "\n";
}
PrinterWrapper.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Printer Printer;
__declspec(dllexport)
Printer* newPrinter();
__declspec(dllexport)
void Printer_print(Printer*, const char []);
__declspec(dllexport)
void deletePrinter(Printer*);
#ifdef __cplusplus
}
#endif
PrinterWrapper.cpp
#include "Printer.h"
#include "PrinterWrapper.h"
extern "C" {
Printer* newPrinter()
{
return new Printer();
}
void Printer_print(Printer* printer, const char message[])
{
printer->print(message);
}
void deletePrinter(Printer* printer)
{
delete printer;
}
}
Printer.java
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
public class Printer {
private static final MethodHandle NEW_PRINTER;
private static final MethodHandle PRINTER_PRINT;
private static final MethodHandle DELETE_PRINTER;
private MemorySegment printerPointer;
static {
System.load("Path/to/shared/library");
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = SymbolLookup.loaderLookup();
NEW_PRINTER = linker.downcallHandle(lookup.findOrThrow("newPrinter"),
FunctionDescriptor.of(ValueLayout.ADDRESS));
PRINTER_PRINT = linker.downcallHandle(lookup.findOrThrow("Printer_print"),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
DELETE_PRINTER = linker.downcallHandle(lookup.findOrThrow("deletePrinter"),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
}
public Printer() {
try {
printerPointer = (MemorySegment) NEW_PRINTER.invoke();
} catch (Throwable e) {
e.printStackTrace();
}
}
public void print(String message) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment nativeString = arena.allocateFrom(message);
PRINTER_PRINT.invokeExact(printerPointer, nativeString);
} catch (Throwable e) {
e.printStackTrace();
}
}
public void delete() {
try {
DELETE_PRINTER.invokeExact(printerPointer);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Main.java
public class Main {
public static void main(String[] args) {
Printer printer = new Printer();
printer.print("Hello World!");
printer.delete();
}
}
If I run the program, everything works fine. But, here is the unexpected behavior: If I change the variable printerPointer to MemorySegment.NULL or any zero-length memory segment (see zero-length memory segments in the java documentation) of any address, e.g. MemorySegment.ofAddress(73928298), the program still executes correctly.
First change:
public Printer() {
printerPointer = MemorySegment.NULL;
}
Second change:
public Printer() {
printerPointer = MemorySegment.ofAddress(73928298);
}
Both times, Hello World! is printed on the console. But I don't know why it is working both times, as my expectation was that it does not. Is there something wrong with the wrapper? (As I learned, calling C++ code produces undefined behavior). Or, is this intended, for whatever reason? Or, is it just a bug in the API?