From 5f64836b0cc66ee80e735209feb57144dbd8e00a Mon Sep 17 00:00:00 2001 From: John Breaux Date: Sat, 12 Aug 2023 17:18:08 -0500 Subject: [PATCH] Implement a basic plugin system using dynamic libraries --- Makefile | 61 +++++++++++++++++++++++++++++++++++++++++++++ inc/lmao_syntax.hpp | 4 +++ inc/plugin.hpp | 25 +++++++++++++++++++ src/demo.cpp | 24 ++++++++++++++++++ src/main.cpp | 30 ++++++++++++++++++++++ src/plugin.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 203 insertions(+) create mode 100644 Makefile create mode 100644 inc/lmao_syntax.hpp create mode 100644 inc/plugin.hpp create mode 100644 src/demo.cpp create mode 100644 src/main.cpp create mode 100644 src/plugin.cpp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d570659 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +# ---------- Variables listed below --------- # + +# Target directory +BUILD = build +# Paths to source, include, dependency, and object files +SRC = src +INC = inc +DEP = $(BUILD)/dep +OBJ = $(BUILD)/obj +ALL = $(SRC) $(INC) $(BUILD) $(DEP) $(OBJ) + +# Executable +TARGET := $(BUILD)/main.out $(BUILD)/demo.so + +# compiler and compiler flags +CC = g++ +CFLAGS = -I$(INC) +SOFLAG = -fpic -shared -dynamic -ldl + +# list of static object files +SOURCES = $(wildcard $(SRC)/*.cpp) +OBJECTS = $(addprefix $(OBJ)/,$(notdir $(SOURCES:.cpp=.o))) + +# ----------- Targets listed below ---------- # +# Some targets aren't real +.PHONY: all clean run dump +# Don't autodelete object files: +.PRECIOUS: $(OBJ)/%.o + +all: $(DEP) $(OBJ) $(TARGET) + +dump: + @echo "SOURCES: $(SOURCES)" + @echo "OBJECTS: $(OBJECTS)" + @echo " TARGET: $(TARGET)" + @echo " PATHS: $(ALL)" + +clean: + -rm -r $(BUILD) + +run: $(TARGET) + -$(addprefix ./,$(addsuffix ;,$(TARGET))) + +# Create directories for output +$(BUILD) $(DEP) $(OBJ): + mkdir -p $@ + +# Make the executable +$(BUILD)/%.out: $(OBJECTS) + $(CC) $(CFLAGS) -o $@ $^ +# Make the plugins +$(BUILD)/%.so: $(OBJ)/%.o + $(CC) $(CFLAGS) $(SOFLAG) -o $@ $^ +# Make the object and dependency files +$(OBJ)/%.o: $(SRC)/%.cpp + $(CC) $(CFLAGS) -MMD -MF $(DEP)/$(@F:.o=.d) -o $@ -c $< + + +# --------- Inclusions listed below --------- # +# use dependencies when rebuilding +-include $(wildcard $(DEP)/*.d) diff --git a/inc/lmao_syntax.hpp b/inc/lmao_syntax.hpp new file mode 100644 index 0000000..08d009b --- /dev/null +++ b/inc/lmao_syntax.hpp @@ -0,0 +1,4 @@ +#pragma once +#define let auto +#define is == +#define None nullptr diff --git a/inc/plugin.hpp b/inc/plugin.hpp new file mode 100644 index 0000000..4d8ec87 --- /dev/null +++ b/inc/plugin.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +static char* plugin_error; + +// define a schema for plugins +class Plugin { +private: + const char* name; + void* handle; + void* (*fn_init)(); + int (*fn_call)(void*, int argc, char* argv[]); + int (*fn_exit)(void*); + void* data; + // Constructor + void init (char* so_path); +public: + Plugin (char* path); + Plugin (std::string path); + Plugin (Plugin&&) = default; + ~Plugin (); + int call (int argc, char* argv[]); + int operator()(int argc, char* argv[]); +}; diff --git a/src/demo.cpp b/src/demo.cpp new file mode 100644 index 0000000..a4fbadd --- /dev/null +++ b/src/demo.cpp @@ -0,0 +1,24 @@ +// demo shared object +#include + +static int g = 0; + +struct Data { + int number; +}; +extern "C" { + void* plugin_init () { + return new Data{ g++ }; + } + + int plugin_main (void* data, int argc, char** argv) { + printf ("Hello from %s!\n", __FILE__); + printf ("The data is %d!\n", ((Data*)data)->number); + return 0; + } + + int plugin_exit (void* data) { + delete (Data*)data; + return 0; + } +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e649400 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#include "lmao_syntax.hpp" +#include "plugin.hpp" + + +int main (int argc, char* argv[]) { + using namespace std; + string plugin_file; + vector plugins; + while (true) { + printf ("> "); + getline (cin, plugin_file); + if (plugin_file.empty ()) continue; + if (plugin_file == "clear") { + plugins.clear (); + printf ("Cleared.\n"); + continue; + } + try { + plugins.emplace_back (plugin_file); + plugins.back ()(0, {}); + } catch (const std::exception& e) { + std::cerr << e.what () << '\n'; + } + } +} diff --git a/src/plugin.cpp b/src/plugin.cpp new file mode 100644 index 0000000..8f50519 --- /dev/null +++ b/src/plugin.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +#include "lmao_syntax.hpp" +#include "plugin.hpp" + +// Throws domain error +void* resolve (void* handle, const char* name) { + void* symbol = dlsym (handle, (char*)name); + if (symbol is None) { + throw std::runtime_error (dlerror ()); + } + return symbol; +} + +void Plugin::init (char* so_path) { + //dlopen the plugin + handle = dlopen (so_path, RTLD_NOW); + if (handle is None) { + throw std::invalid_argument (dlerror ()); + } + printf ("handle: %p\n", handle); + //find its plugin struct + fn_init = (void* (*)()) resolve (handle, "plugin_init"); + fn_exit = (int (*)(void*)) resolve (handle, "plugin_exit"); + fn_call = (int (*)(void*, int, char* [])) resolve (handle, "plugin_main"); + + data = fn_init (); +} + +Plugin::Plugin (char* so_path) { + init (so_path); +} + +Plugin::Plugin (std::string so_path) { + init ((char*)so_path.c_str ()); +} + +Plugin::~Plugin () { + printf ("Destroyed %p\n", handle); + if (!handle) { + printf ("tried to unload nonexistent plugin\n"); + return; + } + if (dlclose (handle)) { + printf ("failed to close plugin: %s", dlerror ()); + exit (EXIT_FAILURE); + } +} + +int Plugin::call (int argc, char* argv[]) { + return fn_call (data, argc, argv); +} + +int Plugin::operator()(int argc, char* argv[]) { + return call (argc, argv); +}