Skip to main content

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"

extern "C" bool scrii_plugin_init(scrii::plugin::Registry &reg) {
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:

MemberDescription
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_namesNames 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 &reg) {
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; mismatches are caught by the version check rather than by undefined behavior.

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

PlatformGeneratorBuild commandOutput
Linuxbashmake.so
macOSbashmake.dylib
WindowsGit Bash / WSLmingw32-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.hpp interface — see Embedding.