Interoperability of Fortran with C/C++: coding tutorial (2024)

Coding tutorial: the interoperability of Fortran and C/C++, highlighting the seamless integration possible in 2024.
Author

Paul Norvig

Published

January 9, 2024

Introduction

I’ve been working with different programming languages and one of the most interesting areas I’ve explored is how they can work together. This concept, called interoperability, is all about making these languages communicate with each other. For example, I’ve used Fortran for its strengths in computational tasks and C/C++ for system-level programming. I found it fascinating how these languages, which are quite different, can actually be made to collaborate to build powerful applications.

Introduction to Interoperability in Modern Programming

Interoperability is the glue that binds various programming languages in modern software development. When I first encountered the concept, it was a bit baffling; how could languages with different syntax and semantics communicate seamlessly? But as software grows in complexity, the need for leveraging the unique strengths of multiple languages becomes obvious.

Take Fortran and C/C++ for instance. Fortran reigns supreme in numerical computing and algorithms that require heavy computational tasks. On the other hand, C/C++ offers systems-level access and efficiency. To create software that benefits from the strengths of both, we need interoperability.

I started my journey with simple function calls. If you’ve got a Fortran function that you can’t live without, but you’re working within a C environment, no sweat. With the use of ISO C Binding, it’s possible to call Fortran code from C/C++. Here’s a basic example:

! A simple subroutine in Fortran
subroutine greet_fortran(name) bind(C, name="greet_fortran")
use, intrinsic :: iso_c_binding
character(kind=c_char), intent(in) :: name(*)
print *, "Hello from Fortran, ", trim(name)
end subroutine greet_fortran

Now, how do you call this from C? Like so:

// A corresponding C program to call the Fortran subroutine
#include <stdio.h>

// Declare the Fortran subroutine
extern void greet_fortran(const char *name);

int main() {
greet_fortran("C User");
return 0;
}

It’s important to take note of the extern keyword in C. This tells the C compiler that the implementation of the greet_fortran function is external, which, in this case, lives within our Fortran code.

Do remember that data types must match across the languages for arguments passed in and out of functions. This is where ISO C Binding comes into play. It provides an interface between Fortran and C standard data types, easing interoperability woes.

Transitioning to a more advanced topic, consider mixing Fortran and C++ objects. My first attempt at this was riddled with issues. I soon realized that a direct approach isn’t going to work because C++ mangles names to support overloading. However, the extern "C" linkage specification in C++ turns off name mangling, which enabled me to call Fortran code from C++.

// C++ code calling Fortran subroutine
extern "C" {
void greet_fortran(const char *name);
}

int main() {
greet_fortran("C++ User");
return 0;
}

Still, one of the most humbling aspects of programming is understanding and respecting the limitations of integrating different languages. There are memory management quirks to ponder, compiler-specific nuances to consider, and performance implications to weigh.

As you’ll learn later in the “Common Challenges and Solutions in Mixed-language Programming” section, there will be times when you’ll have to make trade-offs or deep dive into documentation. The “Future Perspectives on Interoperability and Language Evolution” will give some hope about the direction in which language communication is heading.

Indeed, interoperability isn’t just some technical chore. It’s a fascinating crossroads of language philosophies, and mastering it can transform you into a more versatile programmer. Fortran and C/C++ may seem worlds apart, but with interoperability, they can work in harmony, creating more robust, efficient, and highly specialized software.

Key Techniques for Fortran and C/C++ Integration

Integrating Fortran with C or C++ can seem daunting at first, but knowing a few key techniques can help streamline the process. Let’s explore how to make these two generations of languages play nice together.

When I began mixing Fortran and C, the iso_c_binding module was a game changer. It’s part of the Fortran 2003 standard and provides a standard interface for C compatibility. So the first thing to do in your Fortran code is to use this module. Here’s what it looks like:

module c_bindings
use iso_c_binding, only: c_int, c_float, c_char, c_ptr
implicit none
! Define additional bindings here.
end module c_bindings

With the bindings ready, consider creating interfaces for your Fortran subroutines or functions that you want to call from C or C++. This tells the C compiler what to expect in terms of arguments and return types. You can do this by adding bind(C) to your Fortran procedures.

subroutine my_fortran_subroutine(arg1, arg2) bind(C)
use iso_c_binding
implicit none
integer(c_int), value :: arg1
real(c_float) :: arg2
! Subroutine body here.
end subroutine my_fortran_subroutine

On the C side, you’ll specify function prototypes to match the Fortran subroutines. Make sure the types match the iso_c_binding types. To call the above Fortran subroutine in C, here’s what the prototype might look like:

extern void my_fortran_subroutine(int arg1, float arg2);

And calling it is just like calling any C function:

int main() {
my_fortran_subroutine(42, 3.14);
return 0;
}

Beyond simple function calls, managing arrays can be a bit tricky due to different indexing (Fortran starts at 1, while C/C++ starts at 0). If you pass an array from C to Fortran, remember to account for this offset when accessing elements in Fortran.

Let’s say you have a C array you want to send to a Fortran subroutine:

float c_array[5] = {1.0, 2.0, 3.0, 4.0, 5.0};

Your Fortran subroutine would take an argument of type real(c_float), dimension(*):

subroutine process_array(c_array) bind(C)
use iso_c_binding
implicit none
real(c_float), dimension(*), intent(inout) :: c_array
! Remember Fortran indexing when manipulating the array.
end subroutine process_array

Compiling this requires some attention. Usually, I compile the Fortran code to an object file, and the C code to another object file, and then link them together. On a Unix-like system, using gfortran and gcc, it would look something like this:

gfortran -c my_fortran_code.f90
gcc -c my_c_code.c
gcc my_fortran_code.o my_c_code.o -o my_mixed_language_app -lgfortran

Do note that the last command links both object files into a single executable, and by including the -lgfortran flag, we tell the linker to link against the Fortran standard library.

For those digging into the details, comprehensive tutorials are often found in university course websites, and snippets or larger integration examples can be seen in open GitHub repos, such as those maintained by scientific computing communities.

Always keep an eye on the typing and memory management when passing data between Fortran and C or C++. And remember, it’s all about the details—small mismatches can lead to big headaches. I learned the hard way that careful type matching and consistent calling conventions make the integration process much smoother. Happy coding!

Best Practices for Writing Interoperable Code

Writing interoperable code requires clear understanding and a good deal of consideration, especially when dealing with languages as distinct as Fortran and C/C++. I’ve woven through this fabric before, and believe me, it pays to adhere to best practices. Here, I’ll share my approach to ensure smooth function calls across the language divide.

Initially, let’s keep an eye on name-mangling. C and C++ compilers modify function names, whereas Fortran compilers usually don’t. To ensure that Fortran can call C functions, you should use the extern "C" block in C++. Here’s a basic pattern:

// C++ code
extern "C" {
void my_c_function() {
// implementation
}
}

In Fortran, use the interface block to declare this C function:

! Fortran code
interface
subroutine my_c_function() bind(C, name="my_c_function")
end subroutine my_c_function
end interface

The next thing to reckon with is data types. Fortran and C/C++ don’t always use the same names or sizes for types. For instance, a Fortran INTEGER typically pairs with a C int, but platform variations can throw a wrench in the works. Always check the size:

! Fortran code
integer(kind=c_int) :: fort_integer
// C code
#include <stdint.h>

int32_t c_integer;

Here, c_int comes from the ISO C Binding module, ensuring we’re talking about the same integer size in both worlds.

Arrays stir up the pot, particularly with indexing. Fortran arrays are column-major, meaning the first index changes fastest, while C is row-major. When passing arrays, if you’re not careful, your data layout gets muddled. To safely pass one-dimensional arrays, which luckily don’t suffer from this indexing confusion, you can do the following:

! Fortran code
real(kind=c_float), dimension(:), intent(in) :: fort_array
// C++ code
extern "C" {
void process_array(float* array, size_t size) {
// Do something with array
}
}

Pointer translation is another hurdle. C uses pointers; Fortran, not so much. But Fortran can work with C pointers using type(c_ptr):

! Fortran code
type(c_ptr) :: cstyle_pointer

Now, don’t even get me started on strings. Fortran strings are fixed-length and padded with spaces; C strings are null-terminated. When passing a Fortran string to C, prepare to remove trailing spaces or ensure your C function handles it gracefully.

For instance, here’s how you might handle strings:

! Fortran code
character(len=30) :: fort_string = "Hello from Fortran"
// C code
void greet_from_c(const char* c_string) {
printf("Greeting from C: %s\n", c_string);
}

Make sure to null-terminate your strings when necessary and be wary of buffer overflows.

Finally, mixed-language build systems can be tricky. I personally prefer using CMake for its flexibility. Start by defining your project and enabling the required languages:

cmake_minimum_required(VERSION 3.14)
project(MixedLangProject C CXX Fortran)

Create your C and Fortran executables, and use target_link_libraries to link them together appropriately.

In summary, while navigating the quirks of interoperable code between Fortran and C/C++, consistency is your ally, and careful, explicit coding will save many headaches. Use language-specific bindings, match data types precisely, respect differing array layouts and string conventions, and wield a build system that juggles multiple languages. Patience and precision are your best tools here, often more useful than any compiler or debugger.

Common Challenges and Solutions in Mixed-language Programming

As developers, we often face challenges when our projects require mixed-language programming, especially interfacing between legacy systems written in Fortran and newer modules in C/C++. Bridging these two worlds can be daunting, but with a few strategies up our sleeves, we can overcome these challenges.

The first issue we typically encounter is calling conventions. C and C++ have different calling conventions from Fortran, which can trip us up if we’re not careful. Let’s look at how we’d normally call a Fortran subroutine from C, which involves understanding the name-mangling scheme used by the Fortran compiler:

extern void fortran_subroutine_(int* param);

int main() {
int argument = 42;
fortran_subroutine_(&argument);
return 0;
}

Notice the underscore after the subroutine name? That’s a common way to match the Fortran compiler’s mangling, which might vary depending on your environment.

Data types are another common pitfall. Fortran and C/C++ don’t always see eye to eye, particularly when it comes to complex numbers or character strings. When I pass a string from C to Fortran, I must be mindful of how Fortran handles character encoding. Here’s a quick example:

extern void fortran_function_(char* str, int len);

void c_function() {
char c_string[] = "Hello, Fortran!";
fortran_function_(c_string, sizeof(c_string) - 1);
}

In Fortran, we would have something like:

subroutine fortran_function(str)
character(len=*), intent(in) :: str
print *, str
end subroutine fortran_function

Array differences are also something to watch out for. Fortran stores arrays in column-major order unlike C’s row-major order. When passing arrays, I typically ensure proper memory layout to avoid unexpected behavior:

/* C code that declares and fills a two-dimensional array */
double c_array[2][3] = {{1,2,3}, {4,5,6}};

/* Fortran subroutine expecting a 3x2 array */
extern void fortran_subroutine_(double* array, int* rows, int* cols);

int main() {
int rows = 2;
int cols = 3;
fortran_subroutine_(&c_array[0][0], &rows, &cols);
return 0;
}

Memory management can be tricky as well. Since Fortran and C manage memory differently, releasing memory allocated in one language in the other language can cause undefined behavior. I tend to allocate and deallocate memory in the same language or use interoperability features provided by Fortran 2003 and later:

! Fortran code
subroutine allocate_array(array, size) bind(c, name="allocate_array")
use iso_c_binding, only: c_ptr, c_size_t, c_f_pointer
type(c_ptr) :: array
integer(c_size_t), intent(in) :: size
real, pointer :: f_array(:)

allocate(f_array(size))
call c_f_pointer(array, f_array)
end subroutine allocate_array
/* C code that uses the Fortran subroutine to allocate an array */
#include <stdlib.h>

extern void allocate_array(float** array, size_t size);

int main() {
float* c_array;
size_t size = 10;
allocate_array(&c_array, size);

/* Use the array */

free(c_array); /* Only if the memory was allocated with malloc */
return 0;
}

While these examples are simplified, addressing each of these issues entails a careful dance between two languages with their own quirks. By keeping these complexities in mind and rigorously verifying the interface between the two languages, you can create a robust mixed-language application. If you’re thirsty for deeper knowledge, diving into documentation and community discussions on platforms like StackOverflow or checking out repositories on GitHub could prove beneficial. There’s a wealth of examples and advice available at your fingertips, often shaped by hard-won experience from developers who’ve been in the trenches with Fortran and C/C++.

Future Perspectives on Interoperability and Language Evolution

The future of programming is inherently tied to the evolution of languages and their ability to interoperate. In the context of combining the robustness of Fortran with the versatility of C/C++, interoperability is more than a convenience—it’s becoming a cornerstone for advanced computational projects.

When I experiment with Fortran and C/C++ together, I’m often reminded that the essence of modern computing is about standing on the shoulders of giants. Fortran’s splendid legacy in numerical computation and C/C++’s widespread system-level application are proof of this synergy. Let’s consider, for instance, a simple scenario where you need to pass arrays between Fortran and C.

In C, you might declare an array and a function to handle it:

#include <stdio.h>

void c_process_array(double *array, int size) {
for (int i = 0; i < size; i++) {
array[i] += 10.0; // Arbitrary processing
}
}

On the Fortran side, you can interface with this function:

subroutine call_c_process_array(arr, size) bind(C, name="c_process_array")
use iso_c_binding, only: c_double, c_int
implicit none
integer(c_int), value :: size
real(c_double), intent(inout) :: arr(size)
end subroutine

Interoperability lies not in the complexity of operations, but in the smoothness with which languages communicate—minimal friction, maximal efficiency.

Looking ahead, I anticipate that language evolution will see an increased focus on making such inter-language communication not just possible but effortlessly intuitive. Emerging tools and methods should streamline the declaration, linking, and runtime aspects of mixed-language programming.

Consider how compilers like GCC and Flang are already pushing the boundaries. Community-driven repositories (you can find examples on GitHub and GitLab) are filled with resources that provide templates and examples on setting up mixed Fortran and C/C++ projects. University research teams continue publishing papers that address interoperability issues—offering insights into successful strategies and highlighting current pitfalls.

Yet, the future of interoperability isn’t solely about compiler features or code snippets; it’s about developing a shared ethos among programmers. Language barriers within computing can be as limiting as they are in human communication. This involves fostering a culture where switching between Fortran and C/C++ is as fluid as a bilingual conversation.

As we move forward, I invite you to think about interoperability from the angle of language evolution—and understand that maintaining this evolving landscape is a community effort. It begs us to consider not just the ‘how’ but the ‘why’ of integrating legacy with modernity. The code we write today is a bridge to tomorrow’s innovations.

To sum up, as we embrace the rapid evolution of programming languages, let’s keep sight of the end goal: to create software that’s more reliable, efficient, and accessible than ever before. With every line of interoperable code, we step closer toward a future where the advantages of each language are not just preserved but amplified through seamless collaboration. It’s a future I’m eagerly contributing to, and I hope you’ll join me on this journey.