Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixed programming - Including C++ header to Fortran

I am trying to use a function from library written in C++ in my program written in Fortran. The C++ library is summarized in one header file so that if you want to use it in another C++ program you only do #include functions.h I would like to find out how to do something similar in Fortran.

From my research I've created this minimal viable example:

clib/functions.h:

#ifndef ADD_H
#define ADD_H
extern "C"
{
int __stdcall add(int x, int y);
} 
#endif

clib/functions.cpp:

extern "C"
{
int __stdcall add(int x, int y)
{
    return x + y;
}
}

cinclude.c

#include "clib/functions.h"

cinterface.f95:

module cinterface
  use,intrinsic::ISO_C_BINDING
  integer(C_INT)::a,b
  interface
    integer(C_INT) function add(a,b) bind(C,name="add")
      use,intrinsic::ISO_C_BINDING
      implicit none
!GCC$ ATTRIBUTES STDCALL :: add
!DEC$ ATTRIBUTES STDCALL :: add
      integer(C_INT), value ::a,b
    end function add
  end interface
end module cinterface

main.f90

 program main
   use cinterface      
   implicit none
   integer :: c
   c = add(1,2)
   write(*,*) c
 end program

makefile:

FC = gfortran
CC = g++
LD = gfortran
FFLAGS = -c -O2
CFLAGS = -c -O2
OBJ=main.o 
DEP = \
    cinterface.o cinclude.o

.SUFFIXES: .f90 .f95 .c .o
# default rule to make .o files from .f files
.f90.o  : ;       $(FC) $(FFLAGS) $*.f90 -o $*.o
.f95.o  : ;       $(FC) $(FFLAGS) $*.f95 -o $*.o
.c.o  : ;       $(CC) $(CFLAGS) $*.c -o $*.o
%.o: %.mod
#
main.ex: ${DEP} ${OBJ}
    $(LD)  ${DEP}  ${OBJ} -o prog.exe
#

When I try to make this project using Cygwin I get a following error:

main.o:main.f90:(.text+0x13): undefined reference to `add'
main.o:main.f90:(.text+0x13): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `add'
collect2: error: ld returned 1 exit status
make: *** [makefile:19: main.ex] Error 1

How can I make add function in Fortran work?

like image 460
T.Brown Avatar asked Jul 10 '18 13:07

T.Brown


People also ask

Can we include .C file in header?

The answer to the above is yes. header files are simply files in which you can declare your own functions that you can use in your main program or these can be used while writing large C programs. NOTE:Header files generally contain definitions of data types, function prototypes and C preprocessor commands.

Can I include CPP file in header?

You make the declarations in a header file, then use the #include directive in every . cpp file or other header file that requires that declaration. The #include directive inserts a copy of the header file directly into the . cpp file prior to compilation.

Can C++ include C headers?

Now you can #include your C header without any extern "C" nonsense in your C++ code: // This is C++ code.

Do you need to compile header files C?

You don't need to compile header files. It doesn't actually do anything, so there's no point in trying to run it. However, it is a great way to check for typos and mistakes and bugs, so it'll be easier later.


2 Answers

You are most of the way there. There are two things you need to address to make this work: linkage and argument passing conventions.

Linkage

As francescalus noted, the Fortran compiler doesn't understand how to parse a C/C++ header file. So your functions.h and cinclude.c files aren't going to be of any use in this example.

Don't throw away your functions.h yet, though. In it you declare the add function as:

extern "C"
{
    int __stdcall add(int x, int y);
} 

The extern "C" is the important part. This tells g++ that the symbols in the following block of code aren't subject to all of the C++ name mangling. You'll need the same surrounding the add definition in functions.cpp.

extern "C"
{
    int add(int x, int y)
    {
        return x + y;
    }
}

Once you've done that all you will need to link are functions.o, cinterface.o/mod, and main.o.

Argument passing conventions

The way add is declared the arguments x and y are passed to the function by value. That is the default behavior for C/C++ function arguments. Fortran, on the other hand, defaults to passing arguments to functions/subroutines by reference. In C++ this would look like int add(int* x, int* y). There are two ways to address this problem.

The first option is to redefine your add function with integer pointers for the arguments and dereference them inside the function.

extern "C"
{
    int add(int* x, int* y)
    {
        return *x + *y;
    }
}

The second option (IMHO the preferred option) is to declare the Fortran interface to pass the arguments by value. They aren't being modified in the add function...why pass them by reference? If you choose this option, then your cinterface.f95 will need to contain the following declaration of add:

integer(C_INT) function add(a,b) bind(C,name="add")
    use,intrinsic::ISO_C_BINDING
    implicit none
    integer(C_INT),value::a,b
end function add

Note the additional value decoration on the variables a and b. No matter which option you go with, without it on my machine I get 8393540 printed out as the result of the add function call. After addressing the argument passing conventions I get 3 printed out as expected.

like image 151
Kenneth Davis Avatar answered Oct 19 '22 23:10

Kenneth Davis


A build system can simplify this significantly (albeit at the cost of introducing a complex build system). Assuming the directory layout in your question (although without the the cinclude.c since I don't see what purpose it serves)

$ tree
.
├── cinterface.f90
├── clib
│   ├── CMakeLists.txt
│   ├── functions.cpp
│   └── functions.h
├── CMakeLists.txt
└── main.f90

The contents of the The cmake files are

$ cat CMakeLists.txt 
cmake_minimum_required(VERSION 3.9)

project(cpp-add LANGUAGES C CXX Fortran)

add_subdirectory(clib)

add_executable(glue cinterface.f90 main.f90)
target_link_libraries(glue PUBLIC cpp-clib)

and

$ cat clib/CMakeLists.txt 
add_library(cpp-clib functions.cpp)

The project can then be configured and built in the usual way:

$ cmake -H. -Bbuild && cmake --build build

Execution:

$ build/glue
           3
like image 33
ptb Avatar answered Oct 20 '22 00:10

ptb