diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 90d015b2..6a637010 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -37,17 +37,39 @@ jobs: path: ./stan/ key: ${{ runner.os }}-stan-${{ hashFiles('stan/src/stan/version.hpp') }}-v${{ env.CACHE_VERSION }} + + - name: Set up TBB for C example + if: matrix.os == 'windows-latest' + run: | + Add-Content $env:GITHUB_PATH "$(pwd)/stan/lib/stan_math/lib/tbb" + + - name: Build C example (Windows) + if: matrix.os == 'windows-latest' + run: | + cd c-example/ + make -j4 example.exe example_static.exe example_runtime.exe + rm ../src/bridgestan.o ../test_models/full/*.a + + echo "Dynamically linked example" + ./example.exe + echo "Statically linked example" + ./example_static.exe + echo "Runtime loading example" + ./example_runtime.exe ../test_models/full/full_model.so + - name: Build C example (Unix) if: matrix.os != 'windows-latest' run: | cd c-example/ - make example -j4 - make example_static - rm ../src/bridgestan.o - rm ../test_models/full/full_model.a + make -j4 example example_static example_runtime + rm ../src/bridgestan.o ../test_models/full/*.a + echo "Dynamically linked example" ./example + echo "Statically linked example" ./example_static + echo "Runtime loading example" + ./example_runtime ../test_models/full/full_model.so shell: bash # we use the cache here to build the Stan models once for multiple interfaces diff --git a/.gitignore b/.gitignore index 464f4d7f..b4bfdf15 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.a c-example/example c-example/example_static +c-example/example_runtime *.exe make/local diff --git a/c-example/Makefile b/c-example/Makefile index 6db8302a..b2e81e77 100644 --- a/c-example/Makefile +++ b/c-example/Makefile @@ -28,3 +28,6 @@ example_static$(EXE): example.c ../test_models/$(MODEL)/$(MODEL)_model.a $(CC) -c -I ../src example.c -o example.o $(LINK.cpp) -o example_static$(EXE) example.o ../test_models/$(MODEL)/$(MODEL)_model.a $(LDLIBS) $(LIBSUNDIALS) $(MPI_TARGETS) $(TBB_TARGETS) $(RM) example.o + +example_runtime$(EXE): runtime_loading.c + $(CC) -I ../src runtime_loading.c -o example_runtime$(EXE) -ldl diff --git a/c-example/README.md b/c-example/README.md index 694ce513..3de48584 100644 --- a/c-example/README.md +++ b/c-example/README.md @@ -7,7 +7,9 @@ This shows how one could write a C program which calls BridgeStan. Any compiled language with a C foreign function interface and the ability to link against C libraries should be able to work similarly. -## Usage with dynamic linking +## Binding a specific model at build type + +### Dynamic linking It is possible to link against the same `name_model.so` object used by the other BridgeStan interfaces. This creates a dynamic link. @@ -26,17 +28,20 @@ It has 1 parameters. You can change the test model by specifying `MODEL` on the command line. Models which require data can have a path passed in as the first argument. + ```shell make MODEL=multi example ./example ../test_models/multi/multi.data.json ``` + This will output + ``` This model's name is multi_model. It has 10 parameters. ``` -### Notes +#### Notes The basic steps for using with a generic BridgeStan model are @@ -54,7 +59,7 @@ in the same folder as the executable, or on your `PATH`. On all platforms, dynamic linking requires that the original `NAME_model.so` object still exist when the executable is run. -## Usage with static linking +### Static linking The makefile here also shows how to create a `.a` static library using the BridgeStan source, and then compiling an executable which is independent of the location of the model. @@ -70,3 +75,23 @@ Will output the same as the above. Note that some Stan libraries such as TBB are still dynamically linked. `MODEL` can also be used to specify which model to statically link. + +## Loading a model at runtime + +The `runtime_loading.c` file shows how to use `dlfcn.h` to load a model at runtime. +This is useful if you want to load a model based on user input, or if you want to +load different models in the same executable. + +```shell +make example_runtime +./example_runtime ../test_models/full/full_model.so +``` + +will output + +``` +This model's name is full_model. +It has 1 parameters. +``` + +The same executable can be passed different models without recompiling. diff --git a/c-example/runtime_loading.c b/c-example/runtime_loading.c new file mode 100644 index 00000000..6965f908 --- /dev/null +++ b/c-example/runtime_loading.c @@ -0,0 +1,66 @@ +#include "bridgestan.h" +#include +#include + +#if __STDC_VERSION__ < 202000 +#define typeof __typeof__ +#endif + +int main(int argc, char** argv) { + char* lib; + char* data; + + // require at least the library name + if (argc > 2) { + lib = argv[1]; + data = argv[2]; + } else if (argc > 1) { + lib = argv[1]; + data = NULL; + } else { + fprintf(stderr, "Usage: %s [data]\n", argv[0]); + return 1; + } + + // load the shared library + void* handle = dlopen(lib, RTLD_LAZY); + if (!handle) { + fprintf(stderr, "Error: %s\n", dlerror()); + return 1; + } + + int major = *(int*)dlsym(handle, "bs_major_version"); + int minor = *(int*)dlsym(handle, "bs_minor_version"); + int patch = *(int*)dlsym(handle, "bs_patch_version"); + fprintf(stderr, "Using BridgeStan version %d.%d.%d\n", major, minor, patch); + + // Get function pointers. Uses C23's typeof to re-use bridgestan.h definitions. + // We could also write out the types and not include bridgestan.h + typeof(&bs_model_construct) bs_model_construct + = dlsym(handle, "bs_model_construct"); + typeof(&bs_free_error_msg) bs_free_error_msg + = dlsym(handle, "bs_free_error_msg"); + typeof(&bs_model_destruct) bs_model_destruct + = dlsym(handle, "bs_model_destruct"); + typeof(&bs_name) bs_name = dlsym(handle, "bs_name"); + typeof(&bs_param_num) bs_param_num = dlsym(handle, "bs_param_num"); + + // from here on, the code is exactly the same as example.c + + // this could potentially error, and we may get information back about why. + char* err; + bs_model* model = bs_model_construct(data, 123, &err); + if (!model) { + if (err) { + printf("Error: %s", err); + bs_free_error_msg(err); + } + return 1; + } + + printf("This model's name is %s.\n", bs_name(model)); + printf("It has %d parameters.\n", bs_param_num(model, 0, 0)); + + bs_model_destruct(model); + return 0; +} diff --git a/docs/languages/c-api.rst b/docs/languages/c-api.rst index cf7d8b8e..d1a9b411 100644 --- a/docs/languages/c-api.rst +++ b/docs/languages/c-api.rst @@ -13,9 +13,12 @@ BridgeStan's pre-requisites and downloaded a copy of the BridgeStan source code. Example Program --------------- -An example program is provided alongside the BridgeStan source in :file:`c-example/`. +Two example programs are provided alongside the BridgeStan source in :file:`c-example/`. Details for building the example can be found in :file:`c-example/Makefile`. +The first assumes you wish to link a specific model into the program, +and the second demonstrates how to load a model at runtime + .. raw:: html
@@ -30,6 +33,20 @@ Details for building the example can be found in :file:`c-example/Makefile`.
+.. raw:: html + +
+ Show runtime_loading.c + + +.. literalinclude:: ../../c-example/runtime_loading.c + :language: c + +.. raw:: html + +
+ + API Reference -------------