Skip to content

BMI Cpp

program-- edited this page Oct 11, 2023 · 2 revisions

BMI Models Written in C++

You can implement a model in C++ by writing an object which implements the BMI C++ interface.

BMI C++ Model As Shared Library

For C++ models, the model should be packaged as a pre-compiled shared library. Support for loading of C++ modules/libraries is always enabled, so no build system flags are required.

Dynamic Loading

As noted above, the path to the shared library must be provided in the configuration so that the module can be loaded at runtime.

Additional Bootstrapping Functions Needed

BMI models written in C++ should implement two C functions declared with extern "C". These functions instantiate and destroy a C++ BMI model object. By default, these functions are expected to be named bmi_model_create and bmi_model_destroy, and have signatures like the following:

    extern "C"
    {
      /**
      * @brief Construct this BMI instance as a normal C++ object, to be returned to the framework.
      * @return A pointer to the newly allocated instance.
      */
      MyBmiModelClass *bmi_model_create()
      {
        /* You can do anything necessary to set up a model instance here, but do NOT call `Initialize()`. */
        return new MyBmiModelClass(/* e.g. any applicable constructor parameters */);
      }

      /**
        * @brief Destroy/free an instance created with @see bmi_model_create
        * @param ptr 
        */
      void bmi_model_destroy(MyBmiModelClass *ptr)
      {
        /* You can do anything necessary to dispose of a model instance here, but note that `Finalize()` 
         * will already have been called!
         */
        delete ptr;
      }
    }

It is possible to configure different names for the functions within the NGen realization config by using the keys create_function and destroy_function, but the return types and parameters must be as shown above.

An example of implementing these functions can be found in the test harness implementation at /extern/test_bmi_cpp/include/test_bmi_cpp.hpp.

Why?

Counterintuitively, loading C++ shared libraries into a C++ executable (such as the NextGen framework) requires the use of standard C functions. This is because all C++ compilers "mangle" the names of C++ functions and classes in order to support polymorphism and other scenarios where C++ symbols are allowed to have the same name (which is not possible in standard C). This "mangling" algorithm is not specified or defined so different compilers may use different methods--and even different versions of the same compiler can vary--such that it is not possible to predict the symbol name for any C++ class or function in a compiled shared library. Only by using extern "C" will the compiler produce a library with a predictable symbol name (and no two functions having the extern "C" declaration may have the same name!), so this mechanism is used whenever dynamic loading of C++ library classes is needed.

Similarly, different compilers (or different compiler versions) may implement delete differently, or layout private memory of an object differently. This is why the bmi_model_destroy function should be implemented in the library where the object was instantiated: to prevent compiler behavior differences from potentially freeing memory incorrectly.

BMI C++ Example

An example implementation for an appropriate BMI model as a C++ shared library is provided in the project here.