Implement a basic plugin system using dynamic libraries

This commit is contained in:
John 2023-08-12 17:18:08 -05:00
parent 921f12a5f8
commit 5f64836b0c
6 changed files with 203 additions and 0 deletions

61
Makefile Normal file
View File

@ -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)

4
inc/lmao_syntax.hpp Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#define let auto
#define is ==
#define None nullptr

25
inc/plugin.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <string>
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[]);
};

24
src/demo.cpp Normal file
View File

@ -0,0 +1,24 @@
// demo shared object
#include <cstdio>
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;
}
}

30
src/main.cpp Normal file
View File

@ -0,0 +1,30 @@
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include "lmao_syntax.hpp"
#include "plugin.hpp"
int main (int argc, char* argv[]) {
using namespace std;
string plugin_file;
vector<Plugin> 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';
}
}
}

59
src/plugin.cpp Normal file
View File

@ -0,0 +1,59 @@
#include <stdexcept>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#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);
}