CPPFMU is a set of interfaces and helper functions for writing FMI-compliant model/slave code in C++.
CPPFMU lets you write object-oriented C++ code, using high-level features such as exceptions and automatic memory management, rather than implement the low-level C functions specified by FMI.
Currently, only FMI for Co-simulation is supported. Support for Model Exchange is planned. (Therefore, we'll continue to write "model/slave code", even though it's only the latter which is relevant for the time being.)
CPPFMU was developed as part of the R&D project Virtual Prototyping of Maritime Systems and Operations (ViProMa), and is currently maintained by SINTEF Ocean.
If you have a question, a bug report or an enhancement request, please use the GitHub issue tracker. We appreciate if you do a quick search first to see if anyone has already brought the issue up, and we will also be very happy if you label your issue appropriately.
Contributions are very welcome, and should be submitted as pull requests on GitHub.
CPPFMU can be used by just including the headers in your model/slave
code and compile the .cpp
files together with yours, and you're good to go.
A good tip is to include the CPPFMU repository as a Git submodule in
your own repository.
Note that this repository does not contain the FMI header files; you'll have to get them yourself and make sure your compiler can find them.
You'll also need a compiler with decent C++11 support. The following are known to work (and newer versions of these ought to work as well):
- Microsoft Visual C++ 12.0 (Visual Studio 2013)
- GCC 4.9
We also provide a Conan recipe. This recipe builds a static library
for CPPFMU that you can use in your code, but you still need to compile
fmi_functions.cpp
. The package can be created with conan create . --user sintef --channel stable
. The recipe and some precompiled binaries are available on Sintef
Ocean's public artifactory], which can be added with conan remote add sintef-public https://artifactory.smd.sintef.no/artifactory/api/conan/conan-local
. Note that when using
the conan recipe, FMI 1 or 2 is added as a dependency, so you do not need to fetch them
yourself. To use CPPFMU with conan, add the following lines to your conanfile.py
and
CMakeLists.txt
:
conanfile.py
:
...
def requirements(self):
self.requires("cppfmu/1.0@sintef/stable")
def generate(self):
# Copy fmi_function.cpp to your binary directory
for require, dep in self.dependencies.items():
if require.build or require.test:
continue
if dep.ref.name == "cppfmu":
copy(self, "fmi_functions.cpp",
dep.cpp_info.srcdirs[0],
path.join(self.build_folder, dep.ref.name),
keep_path=False)
CMakeLists.txt
:
find_package(cppfmu REQUIRED)
add_library(FmuModuleTarget MODULE
${fmuSourceFiles}
${CMAKE_BINARY_DIR}/cppfmu/fmi_functions.cpp
)
target_link_libraries(FmuModuleTarget PUBLIC cppfmu::cppfmu)
It's simple: We have already implemented all the FMI C functions
for you in fmi_functions.cpp
. These forward to the C++ functions
defined by you. They also ensure that exceptions are caught,
logged and turned into the appropriate error codes.
To implement a co-simulation slave, this is what you have to do:
-
Include the
cppfmu_cs.hpp
header in your sources. -
Create a class that publicly derives from
cppfmu::SlaveInstance
, and override the latter's virtual member functions as required. Look at the class definition in the header file for information about them. -
Define the function
CppfmuInstantiateSlave()
so that it creates and returns instances of your slave class. This function is declared in the header file, in the global namespace, and you must define it with the exact same signature. You'll find more information about this in the header too.
That's more or less it. Read on below to learn how to deal with errors, memory management, and logging.
CPPFMU uses exceptions to signal errors, and expects the same of
model/slave code. Any CPPFMU function which is not marked with
CPPFMU_NOEXCEPT
may throw. All exceptions are required to derive
from std::exception
.
If an exception is thrown from within model/slave code, the model/slave instance in question is considered unusable, and no further calls will be made to any of its member functions except the destructor.
The message associated with the exception will be logged using the
mechanism provided by the simulation environment (the logger
callback
in the C API), and the currently executing FMI function will return an
error code. For most exception types this will be fmiError
.
If the nature of the error is such that all other instances have also
become unusable, an exception of type cppfmu::FatalError
(or a derived
type) must be thrown instead. This will result in an fmiFatal
error
code.
CPPFMU_NOEXCEPT
and cppfmu::FatalError
are both defined in
cppfmu_common.hpp
.
FMI 1.0 specifies that all memory allocations and deallocations
must be performed using functions provided by the simulation
environment. This is a tall order for some high-level programming
languages, so it was relaxed in FMI 2.0, which lets you specify the
canNotUseMemoryManagementFunctions
flag in modelDescription.xml
.
In C++ it is certainly possible to use custom memory allocation
mechanisms, but in high-level code that uses containers, std::string
,
smart pointers and so on, it is somewhat of a hassle, as they all use
the built-in operator new
by default. CPPFMU aims to make this a
bit easier.
The cppfmu_common.hpp
header contains a set of classes and functions
that wrap the low-level FMI memory management callbacks in a
higher-level C++ interface. These are:
-
cppfmu::Memory
, which simply bundles the two FMI callbacks (allocateMemory
andfreeMemory
) in a single object. This is passed toCppfmuInstantiateSlave()
, and must be further passed on to any code that needs to allocate memory. All of the following classes and functions need to be passed aMemory
object. -
cppfmu::Allocator
, a class which satisfies the C++ Allocator concept, and which can therefore be used to manage memory for the standard container classes, among others. -
cppfmu::String
, which is a type alias forstd::basic_string
using the above allocator, and the convenience functioncppfmu::CopyString()
, which creates aString
from a C-style character array. -
cppfmu::New
andcppfmu::Delete
, which are more or less equivalent to the built-in operatorsnew
anddelete
. -
cppfmu::UniquePtr
, which is a type alias forstd::unique_ptr
with a custom deleter, andcppfmu::AllocateUnique
, which allocates and constructs an object managed by aUniquePtr
.
FMI includes a logging mechanism which model/slave code can use to
pass messages to the simulation environment. Mostly, this is used
for error messages, but those are handled automatically by CPPFMU's
exception handling mechanism. For other cases, such as debugging
information, you can use cppfmu::Logger
.
An object of this type is passed to CppfmuInstantiateSlave()
and
must be passed on to any code that is to perform logging.
The Logger
class is defined and documented in cppfmu_common.hpp
.
CPPFMU is subject to the terms of the Mozilla Public License, v. 2.0. For easily-understandable information about what this means for you, check out the MPL 2.0 FAQ.