🦋 Calling C++ and Fortran functions from Raku using the NativeCall interface

Some time ago, I published an article about using NativeCall in Raku to call functions written in C. Today, let’s see how you can call simple functions written in C++ or in Fortran.

Some time ago, I published an article about using NativeCall in Raku to call functions written in C. Today, let’s see how you can call simple functions written in C++ or in Fortran.

There will be three examples for both languages:

  • A function printing a message
  • A function returning a number
  • A function taking an value and returning a value

Talking to C++ via NativeCall in Raku

The main difference with C is that a C++ compiler mangles the names when creating an object file. The rules are different for different compilers but you can always check how it works. Alternatively, you can ask the compiler not to mangle the names.

1

Let’s have the following C++ function. It does not take or return anything.

#include <iostream>

void hello() {
    std::cout << "Hello, World!\n";
}

To compile it and make a shared library file, use the following command:

$ g++ -shared -o hello.so hello.cpp

You can check the mangled name by looking at the symbol table:

$ nm hello.so

Among the rest of the output, you will see something like this:

0000000000001130 T __Z5hellov

This is how void hello() was transformed to __Z5hellov by the GCC compiler. The _Z prefix (underscore plus a capital letter) is to avoid clashes with user-defined names, 5 is the length of the function name, and v stands for void in place of function parameters.

Having this learnt, you can tell Raku how to find the function by its name (notice that Raku adds another underscore to the name without special announcement).

use NativeCall;

sub hello() is native('hello.so') is symbol('_Z5hellov') {*}

hello();

Run it and you’ll have the greeting in front of you:

$ raku hello.raku
Hello, World!

2

Now let us take something from C++ :) The next function returns an integer:

int my_func() {
    return 42;
}

The return type of a function does not affect its mangled name, so you can conclude that the name you need is __Z7my_funci, where i stands for int.

The corresponding Raku code is:

use NativeCall;

sub my_func() returns int32
    is native('my_func.so')
    is symbol('_Z7my_funcv') {*}

say my_func();

Run the program to see that it prints 42:

$ raku func.raky
42

3

Finally, let us pass a number to the function so that it can process it and return another number in response.

int my_sqr(int x) {
    return x * x;
}

Here, the main difference with the previous example is that the Raku wrapper also needs to take a number:

use NativeCall;

sub my_sqr(int32) returns int32
    is native('my_sqr.so')
    is symbol('_Z6my_sqri') {*}

say my_sqr(5);

The program prints the result as expected:

$ raku sqr.raku
25

4

To avoid the modified names (and the potential differences if you use other compilers), declare the C++ function to have the C export interface:

extern "C" int my_sqr(int x) {
    return x * x;
}

Now, there’s no difference with the program working with a C function:

sub my_sqr(int32) returns int32
    is native('my_sqr_c.so')
    is symbol('my_sqr') {*}

5

The downside of using export "C" it that is makes impossible to use function overloading that is possible in C++. For example, here are the two functions sharing the name but taking arguments of different types:

int my_f(int x) {
    return x * x;
}

double my_f(double x) {
    return x * x * x;
}

The GNU C++ compiler mangles the names of these functions to the two different distinguishable identifiers, which you can pass to Raku as the search instruction:

use NativeCall;

sub f1(int32) returns int32
    is native('my_f.so') is symbol('_Z4my_fi') {*}

sub f2(num64) returns num64
    is native('my_f.so') is symbol('_Z4my_fd') {*}

say f1(5);
say f2(pi);

In this program, I made the names different in Raku (f1 and f2) to avoid the need of converting the Raku types to the native types. You can revert this change as a homework. Also, try making an overloaded function that takes a different number of arguments.

Talking to Fortran via NativeCall in Raku

Fortran is another example of a compiled language that follows the C conventions quite closely. We will examine the same three main cases as before. I will use the GNU Fortran compiler and the Fortran 95 dialect.

1

So, a Hello, World! in Fortran put in a subroutine looks like this:

subroutine hello
    print *, "Hello, World!"
end subroutine hello

You can test it first by calling it from a Fortran program:

program test
    call hello
end program test

Compile and run it:

$ gfortran hello.f95 
$ ./a.out 
 Hello, World!

Now, create the shared library:

$ gfortran -shared -o hello.so hello.f95

Using the nm tool, you can see that the name of the subroutine became _hello_:

0000000000000dba T _hello_

Knowing this fact, we can find it from Raku:

use NativeCall;

sub hello() is native('hello.so') is symbol('hello_') {*}

hello();

That’s it. The program loads the function from the so file and call it:

$ raku hello.raku 
 Hello, World!

2

Is there another way of using a more predictable function name for linking? Actually, yes. You can add the bind modifier and specify the name there:

function hello() bind(c, name="hello")
    print *, "Hello, World!"
end function hello

This time, the shared library keeps the _hello entry:

0000000000000edf T _hello

And you can use it without the is symbol trait in Raku:

use NativeCall;

sub hello() is native('hello-bind.so') {*}

hello();

3

The next program receives a number returned by a Fortran function.

function my_func() result(r)
    integer :: r
    r = 42
end function my_func

You can test this function in Fortran first by linking it with the main program:

program test
    integer :: my_func    
    print *, my_func()
end program test

Here is the command lines to create the shared library and then compile and link the program:

$ gfortran -o my_func.so -shared func.f95 $ gfortran func_test.f95 my_func.so

You already know how to mangle the name of the function to call it from Raku:

use NativeCall;

sub my_func() returns int32
    is native('my_func.so')
    is symbol('my_func_') {*} 

say my_func();

Run the Raku wrapper and confirm you get the result returned from a Fortran function:

$ raku my_func.raku
42

Notice that if you run a pure Fortran version and print from there, you will see the output aligned to the right:

$ gfortran func_test.f95 my_func.so
$ ./a.out
          42

The spaces before the number is the result of aligning the output in Fortran. Although this is beyond the scope of this article, but here is the code to print it aligned to the left (make sure that the function returns not more than two digits, otherwise Fortran will replace the whole result with a single star *):

write(*, "(I2)") my_func()

4

And finally, the most difficult example for today. A Fortran function that takes an integer and returns another integer. Both the input and output values are actually the values of the integer(4) type.

integer function my_sqr(n) result(r)
    integer, value :: n
    integer :: r
    r = n ** 2
end function my_sqr

It is important to mark the input value with the value keyword. Otherwise, the program compiles but the attempt to run it ends up with the Segmentation fault error.

The rest of the procedure is the same as before. Create a shared library:

$ gfortran -shared -o my_sqr.so sqr.f95

And call the function from Raku using NativeCall:

use NativeCall;

sub my_sqr(int32) returns int32
    is native('my_sqr.so')
    is symbol('my_sqr_') {*}

say my_sqr(8);

The program prints the correct result:

$ raku sqr.raku 
64   

Note that this time, printing is done in Raku, as the Fortran function simply returned a number, so it is properly aligned.

* * *

And that’s all for now. I believe that linking functions in D and Rust should be very similar to what was written above. One day, I hope to cover working with more complex data, such as strings and arrays.

On GitHub, you can find the source codes for all the examples in this and the previous articles: github.com/ash/raku-nativecall.

Leave a Reply

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

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code