Native Plugins
Native plugins are shared libraries that register C++ functions and modules
directly into the scripting namespace. They are the fastest way to extend
Scrii with host functionality.
Scaffolding a plugin
The module/ folder ships a generator:
./module/create_module.sh mymod
make -C mymod # -> mymod.so / .dylib / .dll
../build/interpreter/scrii_repl mymod/example.scr
This creates:
mymod/
├── mymod.cpp plugin source — implements scrii_plugin_init()
├── include/ copies of the scrii headers it compiles against
│ ├── plugin.hpp Registry interface + ABI version
│ ├── parser/result.hpp status enum, result type
│ ├── parser/error.hpp ScriptError for argument validation
│ └── var/var.hpp var::Var value type
├── Makefile -> .so / .dylib / .dll
└── example.scr load_plugin(...) demo
The generated mymod.cpp registers a restricted module with one sample
function. Edit it, add your own functions to the module, and rebuild.
The plugin entry point
#include "plugin.hpp"
// Checked by the host BEFORE init() runs: a mismatched plugin is refused
// without executing any of its code.
extern "C" int scrii_plugin_abi_version() {
return scrii::plugin::kAbiVersion;
}
extern "C" bool scrii_plugin_init(scrii::plugin::Registry ®) {
if (reg.abi_version() != scrii::plugin::kAbiVersion)
return false;
reg.add_function("shout",
[](std::vector<scrii::var::Var> a) -> scrii::var::Var {
return scrii::var::Var(a[0].to_string() + "!!!",
scrii::var::var_type::STRING);
},
"shout(s) - Uppercase s with emphasis");
reg.add_module("mymod", "my module description", { ... }); # optional restricted module object
return true;
}
Build with the same compiler and headers as the host interpreter. The plugin
does not link against scrii — the host exports the symbols that the plugin
resolves against, which is why the generated Makefile has no link step.
The Registry API
The scrii::plugin::Registry passed to scrii_plugin_init is the plugin’s
whole interface to the interpreter:
| Member | Description |
|---|---|
int abi_version() |
Returns kAbiVersion. Refuse to load if it doesn’t match what the plugin was compiled against. |
const std::string &engine_version() |
The host engine’s version string. |
void add(name, var::Var value) |
Registers value as a restricted top-level global. Re-registering replaces the previous value. |
void add_function(name, fn, description) |
Convenience wrapper for add registering a function global. |
void add_module(name, description, members) |
Registers a restricted module object holding the given member table. |
std::vector<std::string> registered_names |
Names registered through this Registry (used for unload bookkeeping). |
module in the sample above is built with add_module:
extern "C" bool scrii_plugin_init(scrii::plugin::Registry ®) {
if (reg.abi_version() != scrii::plugin::kAbiVersion)
return false;
reg.add_module("mymod", "My demo module.", {
{"greet", scrii::var::Var(
[](std::vector<scrii::var::Var> a) -> scrii::var::Var {
return scrii::var::Var("hi " + a[0].to_string(),
scrii::var::var_type::STRING);
},
scrii::var::var_type::FUNCTION)},
});
return true;
}
load_plugin("./mymod.so")
print(mymod.greet("world")) # hi world
The ABI version is the one stable contract between host and plugin. Bump your
plugin’s compiled headers in lockstep with the host. There are two layers of
checking:
- Host-side gate (hard): if the plugin exports
scrii_plugin_abi_version()— the scaffolding generates it — the host
compares it before callinginit()and refuses mismatched plugins
without running any plugin code. - Plugin-side check (backstop): the
reg.abi_version()comparison
insideinit()covers older plugins that predate the exported symbol.
A refused plugin surfaces as PLUGIN_LOAD_FAILED naming both ABI versions;
rebuild the plugin against the host’s headers to fix it.
Loading from a script
load_plugin("./mymod.so") # true on success
print(shout("hi")) # HI!
- Registered names become restricted globals — scripts can call them but
never overwrite them. - Duplicate loads of the same file are no-ops.
unload_plugin(path)removes the module’s globals immediately; guarded
calls then throw"plugin module is unloaded". The library itself stays
mapped until engine teardown (deferred close), and re-loading works
normally afterwards.
Platforms
| Platform | Generator | Build command | Output |
|---|---|---|---|
| Linux | bash | make |
.so |
| macOS | bash | make |
.dylib |
| Windows | Git Bash / WSL | mingw32-make (MinGW) |
.dll |
MSVC one-liner equivalent:
cl /std:c++23 /LD /EHsc /Iinclude mymod.cpp /Fe:mymod.dll
Gotchas
- Keep the plugin’s compiler/headers matching the host binary’s toolchain —
C++ ABIs are not stable across compilers or versions. - Throw
scrii::ScriptError{status::..., msg}for clean script-visible
errors; other exceptions are caught and surfaced too. - The embeddable core provides the same
plugin.hppinterface — see
Embedding.