Skip to content

Commit

Permalink
docs: add getting started (#13)
Browse files Browse the repository at this point in the history
* docs: start getting started section

* docs: finish getting started

* docs: update discord link to https://extism.org/discord

* docs: specify include instead of ...
  • Loading branch information
G4Vi authored Dec 6, 2023
1 parent b6c8dbb commit 9477fef
Showing 1 changed file with 177 additions and 11 deletions.
188 changes: 177 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Extism cpp-sdk

C++ Host SDK for Extism
The C++ SDK for integrating with the [Extism](https://extism.org/) runtime. Add this library in your host C++ applications to run Extism plugins.

## Getting Started
Join the [Extism Discord](https://extism.org/discord) and chat with us!

## Building and Installation

### Install Dependencies

Expand All @@ -14,30 +16,188 @@ C++ Host SDK for Extism
If you wish to do link jsoncpp statically, or do an in-tree build, See
[Alternative Dependency Strategies](#Alternative-Dependency-Strategies).

### Building
### Build and Install cpp-sdk

```bash
cmake -B build
cmake --build build -j
sudo cmake --install build
```

### Testing
## Getting Started

After building, run the following from the build directory:
To add the cpp-sdk to a CMake project:

```bash
make test
```cmake
find_package(extism-cpp)
target_link_libraries(getting-started extism-cpp)
```

### Loading a Plug-in

The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). A plug-in is a code module stored in a `.wasm` file.

Plug-in code can come from a file on disk, object storage or any number of places. Since you may not have one handy, let's load a demo plug-in from the web. Let's
start by creating a main function that loads a plug-in:

```cpp
#include <extism.hpp>

int main(void) {
const auto manifest =
extism::Manifest::wasmURL("https://github.com/extism/plugins/releases/"
"latest/download/count_vowels.wasm");
extism::Plugin plugin(manifest, true);
}
```
### Installation
### Calling A Plug-in's Exports
This plug-in was written in Rust and it does one thing, it counts vowels in a string. It exposes one "export" function: `count_vowels`. We can call exports using `Plugin::call`.
Let's add code to call `count_vowels` to our main func:
```c++
#include <extism.hpp>
#include <iostream>
#include <string>
After building, run the following from the build directory:
int main(void) {
// ...
const std::string hello("Hello, World!");
auto out = plugin.call("count_vowels", hello);
std::string response(out.string());
std::cout << response << std::endl;
// => {"count":3,"total":3,"vowels":"aeiouAEIOU"}
}
```

Build it:

```bash
sudo make install
cmake -B build && cmake --build build
```

Running this should print out the JSON vowel count report:

```shell
./build/getting-started
{"count":3,"total":3,"vowels":"aeiouAEIOU"}
```

All exports have the same interface, optional bytes in and optional 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 between calls using variables. Our count vowels plug-in remembers the total number of counted vowels in the "total" key in the result. You can see this by making subsequent calls to the export:

```cpp
const std::string hello("Hello, World!");
auto out = plugin.call("count_vowels", hello);
std::string response(out.string());
std::cout << response << std::endl;
// => {"count":3,"total":3,"vowels":"aeiouAEIOU"}

out = plugin.call("count_vowels", hello);
response = out.string();
std::cout << response << std::endl;
// => {"count":3,"total":6,"vowels":"aeiouAEIOU"}
```
The state variables will persist until the plug-in is freed or reinitialized.
### 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:
```cpp
#include <extism.hpp>
int main(void) {
auto manifest =
extism::Manifest::wasmURL("https://github.com/extism/plugins/releases/"
"latest/download/count_vowels.wasm");
manifest.setConfig("vowels", "aeiouyAEIOUY");
extism::Plugin plugin(manifest, true);
const std::string hello("Yellow, World!");
auto out = plugin.call("count_vowels", hello);
std::string response(out.string());
std::cout << response << std::endl;
// => {"count":4,"total":4,"vowels":"aeiouyAEIOUY"}
}
```

### Linking

### 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. In this case, they are native C++ functions that are passed to and can can be invoked by the plug-in.

Let's load the manifest like usual but load up the `count_vowels_kvstore` plug-in:

```cpp
const auto manifest =
extism::Manifest::wasmURL("https://github.com/extism/plugins/releases/"
"latest/download/count_vowels_kvstore.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 its import interface for a KV store.
We want to expose two functions to our plugin, `kv_write` which writes a bytes value to a key and `kv_read` which reads the bytes at the given key:
```cpp
// pretend this is Redis or something :)
std::map<std::string, std::vector<uint8_t>> kvStore;
auto t = std::vector<extism::ValType>{extism::ValType::I64};
auto kvRead = extism::Function(
"kv_read", t, t,
[&kvStore](extism::CurrentPlugin plugin, void *user_data) {
const auto it = kvStore.find(plugin.inputString());
if (it == kvStore.end()) {
const std::vector<uint8_t> zeros{0, 0, 0, 0};
plugin.output(zeros.data(), zeros.size());
return;
}
plugin.output(it->second.data(), it->second.size());
});
auto kvWrite = extism::Function(
"kv_write", {extism::ValType::I64, extism::ValType::I64}, {},
[&kvStore](extism::CurrentPlugin plugin, void *user_data) {
const auto key = plugin.inputString(0);
auto value = plugin.inputBuffer(1);
kvStore[key] = value.vector();
});
```

We need to pass these imports to the plug-in to create them. All imports of a plug-in must be satisfied for it to be initialized:

```cpp
extism::Plugin plugin(manifest, true, {kvRead, kvWrite});
```
Now, we can demo it:
```cpp
const std::string hello("Hello, World!");
auto out = plugin.call("count_vowels", hello);
std::string response(out.string());
std::cout << response << std::endl;
// => {"count":3,"total":3,"vowels":"aeiouAEIOU"}
out = plugin.call("count_vowels", hello);
response = out.string();
std::cout << response << std::endl;
// => {"count":3,"total":6,"vowels":"aeiouAEIOU"}
```

## Linking

#### CMake

Expand Down Expand Up @@ -104,3 +264,9 @@ cmake -DEXTISM_CPP_BUILD_IN_TREE=1 -B build && cmake --build build
If `EXTISM_CPP_BUILD_IN_TREE` is not set and `find_package` fails, the dependency is fetched from the internet using `FetchContent` and built from source.

The author believes this is the worst way to deal with the deps as it requires building them from source and doesn't use a centrally managed, flat tree of dependencies. It's provided just as a cross-platform convenience.

## Testing

```bash
cmake --build build --target test
```

0 comments on commit 9477fef

Please sign in to comment.