Skip to content
Shengting Cui edited this page Feb 14, 2024 · 3 revisions

BMI Models Written in C

BMI C Model As Shared Library

For C models, the model must be packaged as a pre-compiled shared library. A CMake cache variables can be configured for controlling whether the framework functionality for working with BMI C libraries is activated. This is found in, or must be added to, the CMakeCache.txt file in the build system directory:

  • NGEN_WITH_BMI_C
    • type: BOOL
    • must be set to ON (or equivalent in CMake) for BMI C shared library functionality to be compiled and active
    • defaults to ON

The CMake build system may need to be regenerated after changing these settings.

See the CMake documentation on the set function or variables for more information on working with CMake variables.

When CMake is able to find the library for the given name, it will automatically set up the dependent, internal, static library to dynamically link to the external shared library at runtime.

Dynamic Loading

Additionally, as noted in the formulation configuration overview, the path to the shared library must be provided in the configuration. This is because C libraries must be loaded dynamically within the execution of the NextGen framework, or else certain limitations of C would prevent using more than one such external C BMI model library at a time.

CFE Example

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

Caveats

Activate/Deactivation Required in CMake Build

BMI C functionality will not work (i.e., will not be compiled or executable) unless set to be active in the CMake build. This requires setting the NGEN_WITH_BMI_C CMake cache variable to ON or TRUE (or equivalent).

Conversely, built executables (and perhaps certain build targets) may not function as expected if NGEN_WITH_BMI_C is ON but the configured shared library is not available.

Additional Bootstrapping Function Needed

BMI models written in C should implement an extra "registration" function in order to be compatible with NextGen. By default, this registration function is expected to be:

Bmi* register_bmi(Bmi *model);

It is possible to configure a different name for the function within the NGen realization config, but the return type and parameter list must be as noted here.

The implemented function must set the member pointers of the passed Bmi struct to the appropriate analogous functions inside the model. E.g., the initialize member of the struct:

int (*initialize)(struct Bmi *self, const char *bmi_init_config)

needs to be set to the module's function the performs the BMI initialization. This will probably be something like:

static int Initialize (Bmi *self, const char *file)

So the registration function may look something like:

Bmi* register_bmi_cfe(Bmi *model) {
    if (model) {
        ...
        model->initialize = Initialize;
        ...

Full examples for how to write this registration function can be found in the local CFE BMI implementation, specifically in extern/cfe/src/bmi_cfe.c, or in the official CSDMS bmi-example-c repo near the bottom of the bmi-heat.c file.

Why?

This is needed both due to the design of the C language variant of BMI, and the limitations of C regarding duplication of function names. The latter becomes significant when more than one BMI C library is used at once. Even if that is actively the case, NextGen is designed to accomodate that case, so this requirement is in place.

Future versions of NextGen will provide alternative ways to declaratively configure function names from a BMI C library so they can individually be dynamically loaded.