extern "C"
is used to declare a function or struct follows C linkage conventions. Essentially it ensures the names (symbols) are unmangled just like bind(C)
in Fortran.
Concerning arguments, there are a few (if not more) caveats:
- C and C++ have different definitions of bool, see Issues interfacing between C++ and Fortran - #15 by ivanpribec. In practice, if you have compatible C and C++ compilers, they boil to the same integer type. There’s an example of interfacing Fortran/C/C++ bools in @scivision’s GitHub repository: https://github.com/scivision/fortran-c-cpp-interface/blob/main/src/cxx/bool_main.cxx
- You cannot call C++ overloaded functions, these are functions with the same name but different types or number of arguments (in Fortran this is called a generic interface). You will have to write wrappers with distinct names instead, e.g.
You can then re-establish a generic interface on the Fortran side.// C++ code: void f(int); void f(double); extern "C" void f_i(int i) { f(i); } extern "C" void f_d(double d) { f(d); }
I’d also recommended going through the following resources:
- C Programming Language | C++ Core guidelines.
- How to mix C and C++ | ISO C++.
- Mixing C and C++ Code in the Same Program | Oracle
A few more practical tips for creating wrappers:
- Think in terms of Fortran interfaces. C++ libraries are often designed around objects that encapsulate state but are rarely interoperable with C directly. When writing a Fortran/C interface, prefer writing subroutines/functions which perform an “action” associated with those objects which however remain hidden in the C/C++ portion of the wrapper. The random number generator example fits under this pattern; instead of writing wrappers to expose the random number engine and distributions, those are kept hidden on the C++ side and only the “action” of filling an array with normally distributed values is exposed. (There are usage cases where this might not be possible, e.g. if the C++ object has non-trivial initialization or you want to keep just a single reference to an object on the Fortran side. This becomes much more complex as you essentially take control over the object’s lifetime.)
- Use [] to indicate arrays in prototypes. A C function which accepts an array can be declared in two ways
The second form however is also used for pointers to scalars. Technically it makes no difference, but it helps as a visual reminder thatvoid f(int arr[]); // or void f(int *arr);
arr
is supposed to be an array, and not a reference to a scalar. This is also known as “array decay”, i.e. the arrayarr[]
decays into a pointer to it’s first element.
- Pass constant scalars by value. It’s easier to add the
value
attribute in Fortran than it is to de-reference a pointer at each point of usage in C. - Use
const
(C) andintent(in)
(Fortran) to match intentions. This applies particularly to variables passed by reference. Be careful not to castconst
away; read “const
does not mean constant” and “Const and Optimization in C”. - Use containers such as
std::span
as type- and bound-safe views into (contiguous) Fortran arrays. Such containers “elevate” a decayed pointer into a STL-compatible container. Other libraries may provide similar domain-specific containers; two examples are theEigen::Map<>
andarma::Mat<>
matrix classes, which can be used as wrappers for dense matrices.