Shroud is a tool for creating a Fortran or Python interface to a C or C++ library. It can also create a C API for a C++ library.
The user creates a YAML file with the C/C++ declarations to be wrapped along with some annotations to provide semantic information and code generation options. Shroud produces a wrapper for the library. The generated code is highly-readable and intended to be similar to code that would be hand-written to create the bindings.
Input is read from the YAML file which describes the types, variables, enumerations, functions, structures and classes to wrap. This file must be created by the user. Shroud does not parse C++ code to extract the API. That was considered a large task and not needed for the size of the API of the library that inspired Shroud’s development. In addition, there is a lot of semantic information which must be provided by the user that may be difficult to infer from the source alone. However, the task of creating the input file is simplified since the C++ declarations can be cut-and-pasted into the YAML file.
In some sense, Shroud can be thought of as a fancy macro processor. It takes the function declarations from the YAML file, breaks them down into a series of contexts (library, class, function, argument) and defines a dictionary of format macros of the form key=value. There are then a series of macro templates which are expanded to create the wrapper functions. The overall structure of the generated code is defined by the classes and functions in the YAML file as well as the requirements of C++ and Fortran syntax.
Each declaration can have annotations which provide semantic information. This information is used to create more idiomatic wrappers. Shroud started as a tool for creating a Fortran wrapper for a C++ library. The declarations and annotations in the input file also provide enough information to create a Python wrapper.
- Simplify the creating of wrapper for a C++ library.
- Preserves the object-oriented style of C++ classes.
- Create an idiomatic wrapper API from the C++ API.
- Generate code which is easy to understand.
- No dependent runtime library.
The Fortran wrapper is created by using the interoperability with C features
added in Fortran 2003.
This includes the
iso_c_binding module and the
Fortran cannot interoperate with C++ directly and uses C as the lingua franca.
C++ can communicate with C via a common heritage and the
extern "C" keyword.
A C API for the C++ API is produced as a byproduct of the Fortran wrapping.
Using a C++ API to create an object and call a method:
Instance * inst = new Instance; inst->method(1);
In Fortran this becomes:
type(instance) inst inst = instance_new() call inst%method(1)
The ability to generate C++ wrappers for Fortran is not supported.
There is a long history of ad-hoc solutions to provide C and Fortran interoperability. Any solution must address several problems:
- Name mangling of externals. This includes namespaces and operator overloading in C++.
- Call-by-reference vs call-by-value differences
- Length of string arguments.
- Blank filled vs null terminated strings.
The 2003 Fortran standard added several features for interoperability with C:
- iso_c_binding - intrinsic module which defines fortran kinds for matching with C’s types.
BINDkeyword to control name mangling of externals.
VALUEattribute to allow pass-by-value.
In addition, Fortran 2003 provides object oriented programming facilities:
- Type extension
- Procedure Polymorphism with Type-Bound Procedures
- Enumerations compatible with C
Further Interoperability of Fortran with C, Technical Specification TS 29113, now part of Fortran 2019, introduced additional features:
POINTERattributes may be specified for a dummy argument in a procedure interface that has the
Shroud uses the features of Fortran 2003 as well as additional generated code to solve the interoperability problem to create an idiomatic interface.
Fortran wrappers are generated as free-form source and require a Fortran 2003 compiler. C code requires C99.
The Python wrappers use the CPython API to create a wrapper for the library.
The generated code will require
- Python 2.7 or Python 3.4+
- NumPy can be used when using pointers with rank, dimension or allocatable, attributes.