Fortran for C/C++ developers made easier with CMake

February 29, 2012

Fortran for C/C++ developers made easier with CMake

Love it or hate it, Fortran is the work horse of numerical computing. However, much of the software development in the high performance computing field is done in C and C++.  It is often required that C/C++ software be able to call and link to Fortran libraries. This presents many difficulties in creating cross platform software that will compile and run on Linux, Windows, and Mac OS/X. The CMake build system now contains many powerful features to make cross platform combined C/C++ and Fortran development easier than ever. The following list of CMake features are useful for combined C/C++ and Fortran linkage:

  • For Windows C/C++ developers, the ability to easily use the MinGW gfortran compiler to create dlls and .lib files that can be used by the Windows  Visual Studio compiler.
  • The ability to build Fortran code, including Fortran 95 in the correct order depending on modules created and imported.
  • The ability to automatically determine the name mangling used in Fortran in order to be able to call the Fortran routine from C/C++.
  • The ability to detect and automatically determine the Fortran run time libraries required to link to a Fortran library from a C/C++ application.

 This article will describe how to apply those features in any CMake project requiring mixed C or C++ Fortran development.

Using MinGW gfortran from Visual Studio

If you are going to be using Fortran code or Fortran based library (for example the widely used Linear Algebra PACKage LAPACK from http://www.netlib/lapack) in a project, you will obviously need to be able to link your C code against your Fortran code or your Fortran based library. As you may know gfortran, a free GNU Fortran compiler, is now available on all major platforms. The main problem is if you are working under Microsoft Visual Studio, only commercial (and thus not free!) compilers are available: Intel Compilers, PGI compilers, Silverforst, etc. The gfortran compiler is indeed only available within Cygwin or MinGW, and no direct gfortran plugin for Visual Studio is available.  Integrating gfortran within VS is feasible but not trivial.

CMake now contains a new feature to make gfortran integration much easier.  The new functionality comes in two parts:

  • The ability to create Visual Studio style .lib files from mingw gfortran created .dll shared libraries.
  • A new  CMake module called CMakeAddFortranSubdirectory.cmake. This module adds a subdirectory to a project that contains a Fortran only sub-project. The module will automatically enable Fortran support under Visual Studio by either using the current VS enabled Fortran Compiler  (INTEL Fortran, PGI Fortran, etc..) or directly use the MinGW gfortran compiler.

Creating Visual Studio .lib files from MinGW gfortran

By default when building with the MinGW toolchain and the gfortran compiler, it of course produces GNU style .a libraries. However, if you want to use the MinGW compilers to create libraries that are usable from native Visual Studio (VS) compilers, you will need to create VS .lib files. To do this the VS lib.exe tool is used. As a result, to create VS .lib files you will need both the MinGW toolchain and a version of the VS tool chain on the computer.  However, since the goal is to use gfortran from VS, that means you should already have both installed.

As of CMake 2.8.7, if you are building a dll with gfortran, and you have a version of VS installed on the machine, if you set CMAKE_GNUtoMS=ON in the project or in the cache for the project, CMake will automatically create .lib import libraries for any dll’s created.

When on, the CMAKE_GNUtoMS flag will locate the lib.exe tool from Visual Studio and run it on the .dll.a archive created by the MinGW  tool chain. This will create a Visual Studio .lib import library matching the .dll. The MinGW Fortran libraries are required to be dlls because it is not possible to easily link the GNU Fortran runtime library into a Visual Studio project. However, if the library is built as a dll, then the GNU Fortran runtime will be built into the dll, and it will not be required for the Visual Studio C/C++ program to directly link to the GNU Fortran runtime.

To enable this, the project that is built needs to have the following flags:

  -DCMAKE_GNUtoMS=ON and -DBUILD_SHARED_LIBS=ON  (or add_library with the SHARED option)

Side note, dll’s and Intel Fortran

One possible issue that you can run into if the project supports the Intel Fortran compiler as well as gfortran, you will need to make sure symbols are exported from the library to create the .lib file that goes with the .dll. The MinGW tool chain will work much like a Unix compiler and can export all the symbols from a dll automatically. With VS and the Intel windows compilers, you must explicitly instruct the compiler which symbols need to be exported.

This can be done by creating a .def  file that lists the symbols you want to export.  Alternatively, you can mark up the Fortran code with special comments. To markup Fortran code, a special comment is used as follows:

hello.f

!DEC$ ATTRIBUTES DLLEXPORT :: HELLO
        SUBROUTINE HELLO
        PRINT *, ‘Hello’

cmake_add_fortran_subdirectory Function

The CMake team is developing a new module called CMakeAddFortranSubdirectory.cmake. This module contains a function which works like add_subdirectory. The new function is called cmake_add_fortran_subdirectory

The CMakeAddFortranSubdirectory.cmake module can be found in the master branch of CMake git.  You will need to use a nightly snapshot of CMake for your project because there are C++ changes required to make this feature fully functional.

To use the module add this include to your project:

include(CMakeAddFortranSubdirectory)

Then use the cmake_add_fortran_subdirectory instead of add_subdirectory. The directory passed into cmake_add_fortran_subdirectory must contain only Fortran code.  There cannot be c/c++ code in the subdirectory. When the MinGW Fortran build is activated, the libraries will be built as shared dlls by passing the BUILD_SHARED_LIBS=ON flag to the project contained in the subdirectory. In addition the new CMAKE_GNUtoMS=ON variable will also be passed to the project.

The function will first detect if the selected compiler tool chain supports direct Fortran compilation. If a valid Fortran compiler is found, then the function will simply pass the directory to add_subdirectory. This will allow the Intel Windows Fortran compiler to be used if it is available. On Unix/Linux/Mac systems, the function also acts as a pass through to add_subdirectory.

 The “magic” happens when you are building a project on a machine with the Visual Studio compiler that does not have the Intel Fortran compiler installed. In this case, CMake will find the MinGW install directory, and use the ExternalProject command to build the Fortran sub directory with the MinGW tool chain. It will then create imported targets for the shared Fortran libraries within the subdirectory.  These imported targets can be linked to any CMake target as if they had been natively built with the Visual Studio compiler.

Unlike the usual ExternalProject command usage, the targets can be used with in the same project. This is because it is known exactly where the final product of the build will reside. However, you will have to give some extra information to the cmake_add_fortran_subdirectory so that it can create the import libraries. The usage of the function is as follows:

      cmake_add_fortran_subdirectory(
          <subdir>                           # name of subdirectory
          PROJECT <project_name>  # project name in subdir top CMakeLists.txt
          ARCHIVE_DIR <dir>          # dir where project places .lib files
          RUNTIME_DIR <dir>          # dir where project places .dll files
          LIBRARIES <lib>…            # names of library targets to import
          LINK_LIBRARIES               # link interface libraries for LIBRARIES
           [LINK_LIBS <lib> <dep>…]…
          CMAKE_COMMAND_LINE …  # extra command line flags to pass to cmake
          )

 

Relative paths in ARCHIVE_DIR and RUNTIME_DIR are interpreted with respect to the build directory corresponding to the source directory  in which the function is invoked.

Here is a simple project that builds LAPACK as a subdirectory:

 

cmake_minimum_required(VERSION 2.8.7.20120206)
project(fortranc)
include(CMakeAddFortranSubdirectory)
# add the lapack subdirectory as a fortran project
# the subdir is lapack, the project is LAPACK
cmake_add_fortran_subdirectory(lapack
  PROJECT LAPACK  # project name in toplevel CMakeLists.txt in lapack
  ARCHIVE_DIR lapack/lib # .lib location relative to root binary tree
  RUNTIME_DIR lapack/bin # .dll location relative to root binary tree
  LIBRARIES blas lapack # target libraries created
  LINK_LIBRARIES  # link interface libraries
  LINK_LIBS lapack blas  # lapack needs blas to link
   )

add_executable(testc test.c)
target_link_libraries(testc lapack)

This example can be downloaded here: http://www.cmake.org/files/lapack_test.tar.gz

 The visual studio project looks like this:

(Example LAPACK application built and compiled in the VS 9 IDE using MinGW for the Fortran compiler)

Building Fortran in the correct order

Fortran 90 supports a module system that allows Fortran to import and export modules from source files. This adds a huge complexity to the build problem as the Fortran code must be compiled in the correct order to ensure that module files are available for import after they have been exported, but before they are imported. To accomplish this, CMake contains a full Fortran parser. When CMake computes depend information at build time, it will parse all of the Fortran sources. The parser determines which files are producers and which files are the consumers, and then it uses that information to order the build. There is no user interaction with CMake in order to use this feature. It is only used with Makefile builds. The Intel VS Fortran IDE plugin handles this itself.

Automatically detect the Fortran runtime library and use it

A tricky part of using Fortran from C/C++ is the linking of the Fortran runtime libraries. The usual mode of operation for a Fortran developer is to run the Fortran compiler in a verbose mode, and look for the –L and –l flags used by the compiler. The flags are then used with the C/C++ compiler.  This process is iterated over until the build works. The problem is that you have to do that for each platform supported. CMake will automatically do this for a project that builds both C or C++ and Fortran. To enable this feature, you need only to specify Fortran and C or C++ in the project command:

project(foobar Fortran C)
add_library(foo foo.f)
add_executable(bar bar.c)
target_link_libraries(bar foo)

Automatically Determine Symbol Mangling Used by Fortran Compiler

To use Fortran for C/C++ code, it is required to know what symbol decoration the Fortran compiler uses. Different compilers append or prepend “_”, or use upper or lower case for the function names. CMake contains a module that can be used to determine the mangling scheme used by the compiler. This can then be used to create C/C++ header files defining macros that perform the correct mapping for the C/C++ code.

cmake_minimum_required(VERSION 2.8.6)
project(fortranc Fortran C)
include(FortranCInterface)
FortranCInterface_HEADER(FC.h MACRO_NAMESPACE “FC_”)
#This creates a “FC.h” header that defines mangling macros
# FC_GLOBAL(), FC_GLOBAL_(), FC_MODULE(), and FC_MODULE_().

For more information run cmake –help-module FortranCInterface, or see the online documentation for the FortranCInterface module.

What CMake Does NOT Do for You

Although CMake does make calling Fortran from C/C++ easier by taking care of many of the compiler issues. There are still coding level issues that must be dealt with. For example, Fortran arrays are not stored in the same way as C arrays, and passing strings and other values between the languages can be tricky.  A great reference on those details can be found here: http://www.math.utah.edu/software/c-with-fortran.html.

Summary

In summary, CMake has a rich set of tools to enable the cross platform development of C/C++ code that compiles and links to Fortran source code. CMake can determine compiler symbol mangling as well as the correct run time libraries required to use the Fortran code from C/C++. The new cmake_add_fortran_subdirectory function currently under development will give VS studio users a free Fortran compiler available for use from within the Visual Studio IDE using the MinGW gfortran compiler. If you are interested in this feature and have suggestions we would like to have your input before the next release of CMake.

To make sure I did not make any mistakes with my discussion about Fortran and LAPACK, I asked Julie Langou the current maintainer of LAPACK to review this blog post before I shared it. Julie had some great suggestions and we are now working on an extended version of this blog in article form.  I will post again when that article is published.

I would also like to thank the Dakota and Trilinos teams at Sandia National labs for funding much of the Fortran support found in CMake.

Thanks, and happy C/C++/Fortran hacking!

9 comments to Fortran for C/C++ developers made easier with CMake

  1. HI, thanks for the post. I have the following problems using CMAKE-GUI:

    – when I turn the shared libs option on an error highlighted in red says: VCVARSAMD64-NOTFOUND

    – when I turn the GNUtoMS option on I get the following error: CMAKE_GNUtoMS_VCVARS-NOTFOUND

    For both I should specify a batch file I think but I dont know which ones.

    Thank you in advance for the replay

    Andrea

  2. Hello, thanks for the post and the article.

    1) Could you provide us with working links to the examples 1-4 tgz-archives described in your article?

    2) I’ve got stumbling block in accessing Fortran 90 library from C/C++. The fortran library is designed as follows:
    – it contains numerous low-level subroutines and one interface module containing several high-level subroutines for the end-user. This makes access to the library easier;
    – the problem is that I/O to the module subroutines is organized through the fortran derived data types, defined in the interfacing module. So it’s not obvious how to organize I/O on the C/C++ side.
    Of course, I can try to write additional fortran wrapping module to the library using for I/O only fundamental data types and recompile library or, even, rewrite interfacing module in C/C++ using low-level library functions. But it doesn’t look effective solution and I’m not sure that access to the low-level functions organized through fundamental data types.
    I tried to write definitions for I/O data types on C/C++ side but failed. More over, I couldn’t find any information about fortran derived data types in compiled shared library file, I used DUMPBIN command in visual studio cmd with /ALL option to see dll structure. It seems, definitions of the derived data types contains fortran MOD-file.

    Could you give me an advice or make a hint to help me figure out how to get access to the library from C/C++.

    Thank you

    Alexey

    1. I have put back the missing .tar.gz example file. I don’t have any suggestions on the Fortran data type issue you are having.

  3. Hi,
    I run into the following errors using the example in the tar.gz file:
    lapack/lib/liblapack.a(xerbla.f.o): In function xerbla_':
    xerbla.f:(.text+0x79): undefined reference to
    _gfortran_st_write’
    xerbla.f:(.text+0x90): undefined reference to _gfortran_string_len_trim'
    xerbla.f:(.text+0xb3): undefined reference to
    _gfortran_transfer_character_write’
    xerbla.f:(.text+0xd1): undefined reference to _gfortran_transfer_integer_write'
    xerbla.f:(.text+0xe0): undefined reference to
    _gfortran_st_write_done’
    xerbla.f:(.text+0xef): undefined reference to `_gfortran_stop_string’
    collect2: error: ld returned 1 exit status
    make[2]: *** [testc] Error 1
    make[1]: *** [CMakeFiles/testc.dir/all] Error 2

    Please help. Thanks!

  4. What versions of VS and mingw are you running? I just tried with VS 2015 and it still worked for me with CMake 3.12.0. It is odd that you have a .a file.
    I have this:
    ls lapack/lib/
    libblas.dll.a libblas.lib liblapack.exp libtmglib.dll.a libtmglib.lib
    libblas.exp liblapack.dll.a liblapack.lib libtmglib.exp

Leave a Reply to Bill HoffmanCancel reply