# Extism C SDK This crate contains no actual code, but is used to generated `libextism` from the [extism](../runtime) crate. The C SDK is a little different from the other languages because it is generated from the Rust source using cbindgen. It operates at a lower level than the other SDKs because they build higher level abstractions on top of it. ## Building from source `libextism` can be built using the `Makefile` in the root of the repository: ```shell make ``` `libextism` will be built in `target/release/libextism.*` and the header file can be found in `runtime/extism.h` ## Installation The [Extism CLI](https://github.com/extism/cli) can be used to install releases from Github: ```shell sudo PATH="$PATH" env extism lib install ``` Or from source: ```shell sudo make install DEST=/usr/local ``` This will install the shared object into `/usr/local/lib` and `extism.h` into `/usr/local/include`. ## Getting Started To use libextism you should include the header file: ```c #include ``` and link the library: ``` -lextism ``` ### Creating A Plug-in The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file. Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web: ```c #include #include #include #include #include void print_plugin_output(ExtismPlugin *plugin, int32_t rc){ if (rc != EXTISM_SUCCESS) { fprintf(stderr, "ERROR: %s\n", extism_plugin_error(plugin)); return; } size_t outlen = extism_plugin_output_length(plugin); const uint8_t *out = extism_plugin_output_data(plugin); write(STDOUT_FILENO, out, outlen); } int main(void) { const char *manifest = "{\"wasm\": [{\"url\": " "\"https://github.com/extism/plugins/releases/latest/" "download/count_vowels.wasm\"}]}"; char *errmsg = NULL; ExtismPlugin *plugin = extism_plugin_new( (const uint8_t *)manifest, strlen(manifest), NULL, 0, true, &errmsg); if (plugin == NULL) { fprintf(stderr, "ERROR: %s\n", errmsg); extism_plugin_new_error_free(errmsg); exit(1); } const char *input = "Hello, world!"; print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); extism_plugin_free(plugin); return 0; } ``` > **Note**: In this case the manifest is a string constant, however it has a rich schema and a lot of options, see the [extism-manifest docs](https://docs.rs/extism-manifest/latest/extism_manifest/) for more details. ### Calling A Plug-in's Exports This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using `extism_plugin_call`, then will use `extism_plugin_output_length` and `extism_plugin_output_data` to get the result: ```c int32_t rc = extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input)); if (rc != EXTISM_SUCCESS) { fprintf(stderr, "ERROR: %s\n", extism_plugin_error(plugin)); exit(2); } size_t outlen = extism_plugin_output_length(plugin); const uint8_t *out = extism_plugin_output_data(plugin); write(STDOUT_FILENO, out, outlen); ``` Will print ``` {"count": 3, "total": 3, "vowels": "aeiouAEIOU"} ``` All exports have a simple interface of bytes-in and bytes-out. This plug-in happens to take a string and return a JSON encoded string with a report of results. ### Plug-in State Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export: ```c print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); # => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"} print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); # => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"} ``` These variables will persist until this plug-in is freed or you initialize a new one. ### Configuration Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example: ```c const char *input = "Yellow, world!"; print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); # => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"} const char * config = "{\"vowels\": \"aeiouyAEIOUY\"}"; extism_plugin_config(plugin, config, strlen(config)); print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); # => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"} ``` ### Host Functions Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store! Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in. [Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some C functions you write which can be passed down and invoked from any language inside the plug-in. Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in from `https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm` > *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages. Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store. We want to expose two functions to our plugin, `kv_write(key: String, value: Bytes)` which writes a bytes value to a key and `kv_read(key: String) -> Bytes` which reads the bytes at the given `key`. ```c #include #include #include #include #include // A stubbed out KV store typedef struct KVStore KVStore; extern KVStore *fake_kv_store_new(); extern void fake_kv_store_free(KVStore *kv); extern void fake_kv_store_set(KVStore *kv, const char *key, size_t keylen, uint32_t); extern const uint32_t fake_kv_store_get(KVStore *kv, const char *key, size_t keylen); // Our host functions to access the fake KV store void kv_get(ExtismCurrentPlugin *plugin, const ExtismVal *inputs, size_t ninputs, ExtismVal *outputs, size_t noutputs, void *userdata) { // Cast the userdata pointer KVStore *kv = (KVStore *)userdata; // Get the offset to the key in the plugin memory uint64_t offs = inputs[0].v.i64; size_t keylen = extism_current_plugin_memory_length(plugin, offs); // Allocate a new block to return uint64_t outoffs = extism_current_plugin_memory_alloc(plugin, sizeof(uint32_t)); // Load the value from our k/v store uint64_t value = fake_kv_store_get( kv, (const char *)extism_current_plugin_memory(plugin) + offs, keylen); // Update the plugin memory *(uint64_t *)(extism_current_plugin_memory(plugin) + outoffs) = value; // Return the offset to our allocated block outputs[0].t = PTR; outputs[0].v.i64 = outoffs; } void kv_set(ExtismCurrentPlugin *plugin, const ExtismVal *inputs, size_t ninputs, ExtismVal *outputs, size_t noutputs, void *userdata) { // Cast the userdata pointer KVStore *kv = (KVStore *)userdata; // Get the offset to the key in the plugin memory uint64_t keyoffs = inputs[0].v.i64; size_t keylen = extism_current_plugin_memory_length(plugin, keyoffs); // Get the offset to the value in the plugin memory uint64_t valueoffs = inputs[1].v.i64; size_t valuelen = extism_current_plugin_memory_length(plugin, valueoffs); // Set key => value fake_kv_store_set( kv, (const char *)extism_current_plugin_memory(plugin) + keyoffs, keylen, *(uint32_t *)(extism_current_plugin_memory(plugin) + keyoffs)); } int main(void) { KVStore *kv = fake_kv_store_new(); const char *manifest = "{\"wasm\": [{\"url\": " "\"https://github.com/extism/plugins/releases/latest/" "download/count_vowels_kvstore.wasm\"}]}"; const ExtismValType kv_get_inputs[] = {PTR}; const ExtismValType kv_get_outputs[] = {PTR}; ExtismFunction *kv_get_fn = extism_function_new( "kv_get", kv_get_inputs, 1, kv_get_outputs, 1, kv_get, kv, NULL); const ExtismValType kv_set_inputs[] = {PTR}; const ExtismValType kv_set_outputs[] = {PTR}; ExtismFunction *kv_set_fn = extism_function_new( "kv_set", kv_set_inputs, 1, kv_set_outputs, 1, kv_set, kv, NULL); const ExtismFunction *functions[] = {kv_get_fn}; char *errmsg = NULL; ExtismPlugin *plugin = extism_plugin_new( (const uint8_t *)manifest, strlen(manifest), functions, 1, true, &errmsg); if (plugin == NULL) { fprintf(stderr, "ERROR: %s\n", errmsg); extism_plugin_new_error_free(errmsg); exit(1); } const char *input = "Hello, world!"; print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); extism_plugin_free(plugin); extism_function_free(kv_get_fn); extism_function_free(kv_set_fn); fake_kv_store_free(kv); return 0; } ``` > *Note*: In order to write host functions you should get familiar with the `extism_current_plugin_*` functions. Now when we invoke the event: ```c print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); # => Read from key=count-vowels" # => Writing value=3 from key=count-vowels" # => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"} print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels", (const uint8_t *)input, strlen(input))); # => Read from key=count-vowels" # => Writing value=6 from key=count-vowels" # => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"} ```