Friends of OpenJDK Today

Java Panama Polyglot (C++) Part 1

April 06, 2022

Author(s)

  • Carl Dea

    Carl Dea is a Senior Developer Advocate at Azul. He has authored Java books and has been developing software for 20+ years with many clients, from Fortune 500 companies to ... Learn more

Hello and welcome to the Java Panama Polyglot series where we will be presenting quick tutorials or recipes on how to access native libraries written in other languages.

For example, you will learn about Java Project Panama's (foreign function interface) abilities to access native libraries written in C++, Swift, Python, and others.

What is Polyglot?

Have you heard of polyglot as it relates to developing full stack applications? If your answer is yes, then you probably know about the programming languages needed to build an enterprise web application.

The typical languages you'll encounter are HTML, CSS, JavaScript, Java, SQL, etc. However, in this article we will be focused on Java talking to native languages.

If you are a new(bie) comer to Java's Project Panama you'll want to check out Project Panama for Newbies.

In Part 1 (this article) you will learn how to create a native C++ library to be later called from Java code using Panama's foreign function APIs.

You'll first see some requirements needed to successfully execute the code examples.

Next, you will be shown a problem and solution section then followed-up by code examples. Lastly, is the How it works section explains what is actually going on in the code examples.

Requirements

Problem

As a C++ developer you want to expose functions allowing Java developers to call into.

Solution

Create and export C functions as symbols that are available to linkers. As a C++ developer you will create C based functions that will allow Java's foreign function APIs to access native symbols (CLinker).

As a C++ developer you will create a native C++ library created for a specific operating system such as *.dylib, *.so, and *.dll.

Similar to jar files native libraries are operating system specific and can be compiled along with other C++ based applications. Often times library developers will make functions available to other languages that understand the well known C ABI (Application Binary Interface) standard (convention).

Example

As an example we will create a C++ class representing a rectangle object with private fields width and height.

The Rectangle class will have a single method (member function) area() that calculates the area of the rectangle to be returned to the caller of type int.

Users of this API will instantiate a Rectangle instance using a constructor and invoking the public function area().

Let's create a simple C++ library with the following code (MyRectangle.cpp):

// MyRectangle.cpp
#include <iostream>
#include <cstring>

class Rectangle {
    int width, height;
  public:
    Rectangle(int, int);
    int area() {return width*height;}
};

Rectangle::Rectangle(int w, int h) {
  this->width = w;
  this->height = h;
}

// Expose C ABI for Panama to call into.
extern "C" int rectArea(int, int);
int rectArea(int w, int h) {
    std::cout << "Inside C++ Code " << std::endl;
    Rectangle rect(w,h);
    return rect.area();
}

Let's create a native library using g++. Enter the following to compile the C++ code above:

// MacOS
g++ -dynamiclib -current_version 1.0 -I <include directory> -o libmyrectangle.dylib MyRectangle.cpp

// Linux
g++ -dynamiclib -current_version 1.0 -I <include directory> -o myrectangle.so MyRectangle.cpp

// Windows
g++ -dynamiclib -current_version 1.0 -I <include directory> -o myrectangle.dll MyRectangle.cpp

Above you'll notice switches being used. The following are the switches and their descriptions:

  • -dynamiclib - To indicate this will be a dynamic library, that can be shared with other applications.
  • -current_version - The version of the library
  • -I - Include directories containing .h or .hpp files
  • -o - The output file name of the file

Now that you've created a native library let's see how to load and use Java's Panama (Foreign Function APIs) to access native functions or symbols.

Create a file Java application named RectangleMain.java and enter the following into your main() method:

System.loadLibrary("myrectangle");

var cLinker = CLinker.systemCLinker();

// C function int rectArea(int w, int h)
MethodHandle rectAreaMH = cLinker.downcallHandle(cLinker.lookup("rectArea").get(),
                    FunctionDescriptor.of(C_INT, C_INT, C_INT));

// Return area of rectangle
int w = 8;
int h = 2;
int area = (int) rectAreaMH.invokeExact(w, h);

System.out.printf("MethodHandle calling rectArea(%d, %d) = (%d)\n", w, h, area);

To execute the code do the following:

java --enable-native-access=ALL-UNNAMED \
     --add-modules jdk.incubator.foreign \
     -Djava.library.path=.:/usr/local/lib \
     RectangleMain.java

Below is the output:

Inside C++ Code 
MethodHandle calling rectArea(8, 2) = (16)

How it Works

Looking at the C++ code listing above you'll notice a Rectangle class defined with a constructor definition having two (private) data members width & height and a public function area() that will return the area calculation of the rectangle.

In addition to the Rectangle class is a C style function responsible for instantiating a Rectangle with inbound parameters width and height. Lastly, the rect.area() function will return the area calculation to the caller.

When creating and exporting a C function areaRect() in C++ the code uses the extern "C" followed by the method signature to be exported. The extern binding is prefixed above the C function.

Note: The C function may be named differently, however the signature must be the same. Since C++ supports the C ABI's convention other languages can access these functions.

Shown below is another example of using the extern "C" to export C functions.

extern "C" int doWork(int);
int doWork(int num) {
    std::cout << "Inside C++ Code " << std::endl;
    // do cool stuff here!
    return num;
}

Compiling and Building a Native library

When building the native library the g++ compiler will need the switch -dynamiclib and -I with known include directories. When the -o option is applied the library's file name is named based on the operating system (library naming convention). This allows Java's System.loadLibrary() method to load the library in a portable way across operating systems.

The following are the naming conventions for the respective operating systems:

  • MacOS - lib<name>.dylib
  • Linux - <name>.so
  • Windows - <name>.dll

Note: When running the Java application specify the java.library.path property to the location of the library. If not set correctly you can get the following runtime exception:

java.lang.UnsatisfiedLinkError

Java talking to C++ (C functions)

The code example using Java 18's Panama (FFI) APIs you don't need to use the jextract tool. Here you'll notice the code creating a method handle (MethodHandle) instance by obtaining the native symbol (C function) to be invoked. Shown below is the FunctionDescriptor of the signature for the areaRect() C function.

// (return type int area, int width, int height)
FunctionDescriptor.of(C_INT, C_INT, C_INT)

Conclusion

In a series of articles, we will be exploring other languages that can be accessed using Panama's foreign function interface APIs.

In Part 1, you've learned about how to expose or export C functions (symbols) inside of a C++ libraries using the extern "C" facility.

After successfully building the native library, you've had a chance to create a method handle (MethodHandle) in Java.

As a result, the Java application code is able to invoke the exported C function areaRect() defined earlier.

There you have it! Java Panama Polyglot, in other words: Java talking to C++.

Next, we will look at how Java Panama can talk to the language Swift in Part 2.

As always, comments and feedback are welcome!

Topics:

Related Articles

View All

Author(s)

  • Carl Dea

    Carl Dea is a Senior Developer Advocate at Azul. He has authored Java books and has been developing software for 20+ years with many clients, from Fortune 500 companies to ... Learn more

Comments (4)

Your email address will not be published. Required fields are marked *

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Criss

What directory should be included in that command:

g++ -dynamiclib -current_version 1.0 -I -o libmyrectangle.dylib MyRectangle.cpp

Carl Dea

Criss,
I’m assuming you’ve installed Xcode xcode-select —install. Also check out: https://github.com/carldea/java-panama-workshop/tree/main/installs

If you installed the above the directory should be the following:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/

Carl

Criss

On mac I got error that my library is not found, I indicated java.libary.path but still Java not recognized my library.

Eduardo MErgulhao

How I can fix this :
: expected (Addressable,Addressable,int)int but found (MemoryAddress,MemoryAddress,int)int

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard