3

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?

3
  • 2
    One of the few times where it makes sense to include the C and C++ tags Commented Sep 26 at 16:44
  • 4
    C and C++ proceed from the assumption that the programmer correctly described the behaviour they wanted and rarely makes any sanity checks that would have a cost because those costs would be passed on to every program where the programmer didn't screw up. You tend to get faster code, but more programmer hair-pulling Commented Sep 26 at 17:31
  • You should read undefined behaviour (including the linked articles). Commented Sep 27 at 7:44

1 Answer 1

6

The changed code invokes undefined behaviour.

Printer::print is effectively a class ("static") method rather than an instance method. The caller's pointer is passed in to it, but it's never used.

Printer::print doesn't use this (which has the value of the caller's pointer), either implicitly or explicitly. It doesn't access any data members of the object, and it doesn't call any virtual methods on the object. All it needs to know is the type of the variable printer, and that is known at compile-time.

That's why you get away with calling Printer::print on an invalid pointer. But it is undefined behaviour to do this. You can't rely on always getting this behaviour.

Sign up to request clarification or add additional context in comments.

2 Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.