Implement a basic plugin system using dynamic libraries
This commit is contained in:
parent
921f12a5f8
commit
5f64836b0c
61
Makefile
Normal file
61
Makefile
Normal 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
4
inc/lmao_syntax.hpp
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#define let auto
|
||||
#define is ==
|
||||
#define None nullptr
|
25
inc/plugin.hpp
Normal file
25
inc/plugin.hpp
Normal 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
24
src/demo.cpp
Normal 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
30
src/main.cpp
Normal 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
59
src/plugin.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user