rename the *_load_time examples, start GUIDE.txt, write the xlist base layer example

main
Allen Webster 2024-04-23 20:26:45 -07:00
parent a3befc6dcd
commit 40183c2eed
17 changed files with 262 additions and 0 deletions

21
GUIDE.txt Normal file
View File

@ -0,0 +1,21 @@
The main goal of this investigation is to organize shared data and code across
multiple binary files. This is especially important for something like a base
layer that will be used in a program that supports hot-reloading or plugins.
Each isolated example in this repository explores a way to set up the base
layer, plugin, and main program.
The examples:
*_concrete - Concrete examples for each operating system I have investigated
showing how to setup and use various types of dynamic linking. In
these the "base" layer goes through load-time linking, the
"plugin" layer goes through run-time linking, and the "main" layer
acts as the executable that must bind it all together.
xlist - My prefered solution to the problem posed in the investigation which
relies only on run-time linking. It uses an xlist to manage the
maintenance burden of run-time linking, and a basic outline of the
abstracted form of each layer.

72
xlist/base.c Normal file
View File

@ -0,0 +1,72 @@
// base symbols shared
#if defined(BASE_IMPLEMENTOR)
#include <stdio.h>
int x = 0;
void
base_func(void){
printf("x = %d\n", x);
x += 1;
}
#endif
// dynamic function linkage protocol
#if defined(BASE_IMPLEMENTOR)
EXPORT_SYMBOL BASE_Funcs*
base_export_functions(void){
static BASE_Funcs funcs = {0};
if (funcs.base_func == 0){
#define X(N,R,P) funcs.N = N;
#include "base.xlist.h"
#undef X
}
return(&funcs);
}
#endif
#if !defined(BASE_IMPLEMENTOR)
#if OS_WINDOWS
# include <Windows.h>
#elif OS_LINUX
# include <dlfcn.h>
#endif
BEFORE_MAIN(base_dynamic_user_init){
BASE_Funcs *funcs = 0;
#if OS_WINDOWS
HMODULE module = LoadLibraryA("base.dll");
if (module != 0){
BASE_ExportFuncs *base_export_functions = (BASE_ExportFuncs*)GetProcAddress(module, "base_export_functions");
if (base_export_functions != 0){
funcs = base_export_functions();
}
}
#elif OS_LINUX
void *module = dlopen("./base.so", RTLD_NOW);
if (module != 0){
BASE_ExportFuncs *base_export_functions = (BASE_ExportFuncs*)dlsym(module, "base_export_functions");
if (base_export_functions != 0){
funcs = base_export_functions();
}
}
#else
# error base_import_functions not completed for this OS
#endif
#define X(N,R,P) N = funcs->N;
#include "base.xlist.h"
#undef X
}
#endif

79
xlist/base.h Normal file
View File

@ -0,0 +1,79 @@
// baby context cracking
#if defined(_WIN32)
# define OS_WINDOWS 1
#elif defined(__gnu_linux__)
# define OS_LINUX 1
#else
# error this configuration of compiler & OS is not supported
#endif
#if !defined(OS_WINDOWS)
# define OS_WINDOWS 0
#endif
#if !defined(OS_LINUX)
# define OS_LINUX 0
#endif
// linkage keyword abstraction
#if OS_WINDOWS
# define EXPORT_SYMBOL __declspec(dllexport)
#elif OS_LINUX
# define EXPORT_SYMBOL __attribute__ ((visibility ("default")))
#else
# error keyword abstractions missing for this OS
#endif
#if OS_WINDOWS
# pragma section(".CRT$XCU", read)
# define BEFORE_MAIN(n) extern void n(void); \
__declspec(allocate(".CRT$XCU")) void (*n##__)(void) = n; \
__pragma(comment(linker, "/include:" #n)) extern void n(void)
#elif OS_LINUX
# define BEFORE_MAIN(n) \
__attribute__((constructor)) static void n(void)
#else
# error BEFORE_MAIN missing for this OS
#endif
// base layer types
typedef void BASE_Library;
// base symbols shared
#if defined(BASE_IMPLEMENTOR)
void base_func(void);
#endif
// dynamic function linkage protocol
typedef struct BASE_Funcs{
#define X(N,R,P) R (*N) P;
#include "base.xlist.h"
#undef X
} BASE_Funcs;
typedef BASE_Funcs* BASE_ExportFuncs(void);
#if defined(BASE_IMPLEMENTOR)
EXPORT_SYMBOL BASE_Funcs* base_export_functions(void);
#endif
#if !defined(BASE_IMPLEMENTOR)
#define X(N,R,P) R (*N) P = 0;
#include "base.xlist.h"
#undef X
#endif

3
xlist/base.target.c Normal file
View File

@ -0,0 +1,3 @@
#define BASE_IMPLEMENTOR 1
#include "base.h"
#include "base.c"

1
xlist/base.xlist.h Normal file
View File

@ -0,0 +1 @@
X(base_func, void, (void))

17
xlist/build.bat Normal file
View File

@ -0,0 +1,17 @@
@echo off
REM: Path setup
SET src=%cd%
cd ..
if not exist "build\" mkdir build
cd build
REM: Build the base layer
cl /nologo /Zi /LD %src%\base.target.c /Febase.dll
REM: Build the main layer
cl /nologo /Zi %src%\main.c
REM: Build the plugin layer
cl /nologo /Zi /LD %src%\plugin.c /Feplugin.dll

16
xlist/build.sh Normal file
View File

@ -0,0 +1,16 @@
#!/bin/bash
# Path setup
src=$PWD
cd ..
mkdir -p build
cd build
# Build the base layer as base.so
gcc -fvisibility=hidden -fPIC -shared $src/base.target.c -o base.so
# Build the main layer
gcc -fvisibility=hidden $src/main.c -o main
# Build the plugin layer
gcc -fvisibility=hidden -fPIC -shared $src/plugin.c -o plugin.so

38
xlist/main.c Normal file
View File

@ -0,0 +1,38 @@
#include "base.h"
#include "base.c"
// setup plugin_func as a function pointer so that it can be linked at run time
typedef void PLUGIN_Func(void);
PLUGIN_Func *plugin_func = 0;
int main(){
// base layer initialization is automatic and runs before main - base functions begin working right away
base_func();
// to call a function with run-time linking, we must manually load and link it
#if OS_WINDOWS
HMODULE module = LoadLibraryA("plugin.dll");
if (module != 0){
plugin_func = (PLUGIN_Func*)GetProcAddress(module, "plugin_func");
}
#elif OS_LINUX
void *module = dlopen("./plugin.so", RTLD_NOW);
if (module != 0){
plugin_func = (PLUGIN_Func*)dlsym(module, "plugin_func");
}
#endif
// calls to plugin_func only work after the "plugin" loaded successfully
if (plugin_func != 0){
plugin_func();
}
// demonstration: the data structures contained in the 'base' are not duplicated in each binary
base_func();
}

15
xlist/plugin.c Normal file
View File

@ -0,0 +1,15 @@
#include "base.h"
#include "base.c"
#include <stdio.h>
EXPORT_SYMBOL void
plugin_func(void){
printf("provided by plugin: {\n");
printf(" ");
base_func();
printf(" ");
base_func();
printf("}\n");
}