"дай интерфейс такой-то (версии)". Она тебе указатель на эту структуру. И ты уже через структуру используешь все её функции.
В обратную сторону аналогично: чтоб получить интерфейс, например, нужно в свою очередь передать не только версию/номер/имя интерфейса, но и указатель на структуру с неким набором импортируемых функций, соответствующих интерфейсу. И обратно получишь структуру с импортированными функциями. И работай. Библиотека может реализовывать более одного интерфейса потому, например, что интерфейс первой версии, например, импортирует набор функций бутлоадера первой версии, а интерфейс второй версии хочет расширенный набор функций бутлоадера. И с двумя интерфейсами ты можешь работать с обоими бутлоадерами: с новым через второй интерфейс, и со старым через первый. И в обратную сторону, новый бутлоадер сможет работать со старой библотекой. Ну и ещё эта единственная функция должна реализовывать механизм перечисления интерфейсов, чтоб бутлоадер посмотрел, что библиотека может и выбрал что-нибудь. Можно, например, через callback: библиотека итерируется по своим интерфейсам и зовёт callback с описателем интерфейса, ноль возвращённый из callback'а прерывает процесс.
Сейчас попробую изобразить... пол ночи красноглазил, держи нетленку (https://coliru.stacked-crooked.com/a/71aedbcbdb9ee107 -- тут поиграйся с параметрами команной строки).
Завтра допишу детали реализации.
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
// Common architecture thesis:
//
// 1. whole process might consist of multiple libraries;
//
// 2. each library might implement one or more interfaces;
//
// 3. interface -- is a set of functions, which can be used
// by other libraries;
//
// 4. each interfaces has some unique identifier, which determines it's
// name and version;
//
// 5. interface of next version might inherit previously implemented
// interface and extend it or might be completely different;
// 6. interface is singletone by the nature, there is no possibility
// to have multiple instances of one interface;
//
// 7. initially each library provides exactly only one entry point
// (function) via which functons can exchange with pointers to interfaces;
//
// 8. program can enumerate all interfaces available in the whole system;
//
// 9. program can select which one exactly interface from multiple
// available ones it will use;
//
// 10. all library functions called by loader have no any relations to library
// initialization -- these functions intendent only to linking/loading process,
// initialization shoul be performed via separate user-defined functions.
///// Common definitions.
#define DECONST(v) ((void*)(intptr_t)(v))
struct LibLoader;
// Interface descriptor.
typedef struct
{
// Interface name, which also defines interface version. Should be unique.
const char *name;
// Function which returns implementation of the interface described by this instance of IDescriptor.
// Return value is the actual interface.
// Function should not be called by libraries itself -- it is indendent for use by Loader.
const void* (*_GetInterface)(struct LibLoader *);
}
IDescriptor;
// This is a type of callback function, which used by EnumerateInterface function (see below).
typedef const void* IEnumCallback(const IDescriptor *interface, void *thiz);
// Interface which should be implemented by each library, which is used
// to enumerate all interfaces provided by this library. Library might have
// multiple interfaces, main purpose for which is that the library might
// have disctinct incompatible interface versions. It's preferable to make
// this interface to be a base class for all other interfaces: this allows
// to enumerate all other interfaces by only having any other interface
// (which is usually acessible by other libraries during dependency resolving):
// without this interfaces can only be enumerated via loader.
typedef struct
{
// Functions calls supplied callback function `cb' for each interface
// which current library implements. Parameter `thiz' is passed to `cb' as is.
// The return value might be NULL (all interfaces enumerated) or the result
// of callback function -- some particular interface. Enumeration stops
// immediately if callback function returns non-NULL value.
const void* (*EnumerateInterfaces)(IEnumCallback *cb, void *thiz);
}
IEnumerator;
// Interface which represents the linker/loader.
typedef struct
{
// Base class for this interface, allows loader interfaces enumeration
// (for future extension).
IEnumerator base;
// This function allows enumerate all interfaces available in whole system
// (ones which reside in different libraries). In most cases this function
// isn't needed for user code.
const void* (*EnumerateInterfaces)(IEnumCallback *cb, void *thiz);
// In most times only this one function is required to be used from user code:
// it is needed to resolve dependencies -- to get other interfaces on which
// this current one depends (this function should be called from _GetInterface
// method implementetion for each particular interface).
const void* (*GetInterface)(struct LibLoader *, const char *name);
}
ILoader;
// Current version of the loader.
#define ILoaderVer "LibLoader1.0"
// This class represents loader class instance, which might be used by user code
// to perform other actions via ILoader interface.
typedef struct LibLoader
{
const ILoader *iloader;
}
LibLoader;
// This function mostly indentent for using by loader itself, in rare cases
// (in multithreaded environment) it can be used by user code.
void GetLoaderInstance(struct LibLoader *);
// This is a type of "main" interface function, which every library should provide.
// This function is only should perform actions required to provide list of available
// interfaces to the loader. Library initialization functions MUST NOT be performed
// in this function. For any initialization tasks, especially, if it required to use
// other interfaces (provided by other libraries), TWO PHASE INITIALIZATION must be
// used. For this the interface can provide specially designated function, which can
// be called after all libraries loaded and linked.
typedef const IEnumerator* LibFuncType(void);
///////////////////////// This is header file for lib3 (which is recursive dependent on lib1
// Functions provided by the library (including base class implementing IEnumeration --
// which is always required for future extensions).
typedef struct
{
IEnumerator base;
int (*func3)(int);
}
ILib3;
// Interface name and version of Lib3.
#define Lib3Version "Lib3Ver1.0"
//////////////////////////// This is header file for lib2
typedef struct
{
IEnumerator base;
int (*func0)(int);
}
ILib2;
#define Lib2Version "Lib2Ver1.0"
//////////////////////////// This is headers of Lib1
//////////////////////////// This library implements two interfaces simulaneously.
// First interface implemented by library;
typedef struct
{
IEnumerator base;
void (*func1)(void);
int (*func2)(int);
}
ILib1_1;
// Second interface implemented by this library, which EXTENDS first interface.
typedef struct
{
ILib1_1 base;
void (*func3)(int);
}
ILib1_2;
// Currently available Lib1 version (last).
#define Lib1Version "Lib1Ver2.0"
////////////////////////////////// This is implementation of Lib3
static const void* lib3_enumerator(IEnumCallback *cb, void *thiz);
// This variable contains reference to interface of Lib1 which will be
// resolved in runtime (see below).
static const ILib1_2 *lib1_impl;
// function(s) implemented by lib3
static int lib3_func3(int x)
{
lib1_impl->func3(x + x + x);
return 42;
}
// list of interfaces implemented by lib3
// currently lib3 implements only two interfaces: IEnumerator and ILib3.
static const ILib3 lib3_interfaces =
{
{ lib3_enumerator },
lib3_func3
};
// Function returns first version (only one implemented) of ILib1 interface.
// If Lib1 use some other interfaces: in this function it must resolve all
// dependencies of current interface via calls to the loader and store
// pointers to foreign interfaces in own data segment.
// Note: all interfaces ARE SINGLETONES.
static const void* lib3_get_ver1(LibLoader *loader)
{
if (lib1_impl == NULL)
{
fputs("lib3_get_ver1\n", stderr);
// resolve dependencies
const ILib1_2 *result = loader->iloader->GetInterface(loader, Lib1Version);
if (!result)
return NULL;
lib1_impl = result;
}
return &lib3_interfaces;
}
// IEnumerator.EnumerateInterfaces implementation for this library
static const void* lib3_enumerator(IEnumCallback *cb, void *thiz)
{
static const IDescriptor desc = { Lib3Version, lib3_get_ver1 };
return cb(&desc, thiz);
}
// Main function of the library -- only this function is initially externally visible.
const IEnumerator *library3_main(void)
{
return &lib3_interfaces.base;
}
///////////////////////////////////////// This is implementation of Lib2
static const void* lib2_enumerator(IEnumCallback *cb, void *thiz);
// function(s) implemented by library.
static int lib2_func0(int x)
{
return x*x + 1;
}
// All interfaces implemented by library.
static const ILib2 lib2_interfaces =
{
{ lib2_enumerator },
lib2_func0
};
// Function returns implemented (only one) library interface.
static const void* lib2_get_ver1(LibLoader *loader)
{
// this library has no dependencies
(void)loader;
fputs("lib2_get_ver1\n", stderr);
return &lib2_interfaces;
}
// IEnumerator.EnumerateInterfaces implementation for this library
static const void* lib2_enumerator(IEnumCallback *cb, void *thiz)
{
static const IDescriptor desc = { Lib2Version, lib2_get_ver1 };
return cb(&desc, thiz);
}
// Main function of the library -- only this function is initially externally visible.
const IEnumerator *library2_main(void)
{
return &lib2_interfaces.base;
}
///////////////////////////////////////// This is implementation of Lib1
static const void* lib1_enumerator(IEnumCallback *cb, void *thiz);
// These variable contain reference to interfaces of other libraries
// which will be resolved in runtime (see below).
static const ILib2 *lib2_impl;
static const ILib3 *lib3_impl;
// function(s) implemented by library (from all version of interface);.
static void lib1_func1(void)
{
}
// function(s) implemented by library (from all versions of interface).
static int lib1_func2(int x)
{
return lib2_impl->func0(x) / 2 - 1;
}
// function(s) implemented by library (from second version of interface)
static void lib1_func3(int x)
{
(void)(lib2_impl->func0(x) * 2 + lib3_impl->func3(x));
}
// All interfaces implemented by library.
static const ILib1_2 lib1_interfaces =
{
{
{
lib1_enumerator
},
lib1_func1,
lib1_func2,
},
lib1_func3
};
// Function returns initial (first version) library interface.
static const void* lib1_get_ver1(LibLoader *loader)
{
if (lib2_impl == NULL)
{
fprintf(stderr, "lib1_get_ver1\n");
// resolve dependencies
const ILib2 *result = loader->iloader->GetInterface(loader, Lib2Version);
if (!result)
return NULL;
lib2_impl = result;
}
return &lib1_interfaces.base;
}
// Function returns extended version of library interface.
static const void* lib1_get_ver2(LibLoader *loader)
{
if (lib2_impl == NULL)
{
fprintf(stderr, "lib1_get_ver2\n");
// This function should resolve dependencies for two libraries:
// Lib2 and Lib3, as both used in new function (lib1_func3).
const ILib2 *result2 = loader->iloader->GetInterface(loader, Lib2Version);
if (!result2)
return NULL;
const ILib3 *result3 = loader->iloader->GetInterface(loader, Lib3Version);
if (!result3)
return NULL;
lib2_impl = result2;
lib3_impl = result3;
}
return &lib1_interfaces;
}
// IEnumerator.EnumerateInterfaces implementation for this library
static const void* lib1_enumerator(IEnumCallback *cb, void *thiz)
{
static const IDescriptor list[] =
{
{ "Lib1Ver1.0", lib1_get_ver1 },
{ "Lib1Ver2.0", lib1_get_ver2 }
};
const IDescriptor *desc = list;
unsigned n = sizeof(list)/sizeof(list[0]);
while (n --> 0)
{
const void *result = cb(desc, thiz);
if (result != NULL)
return result;
++desc;
}
return NULL;
}
// Main function of the library -- only this function is initially externally visible.
const IEnumerator *library1_main(void)
{
return &lib1_interfaces.base.base;
}
//////////////////////////// This is loader module. /////////////////////////////////////////////
//
// Due to the fact, that GetLoaderInstance() function should be available to main() function,
// the loader itself should be part of main program, at same time it is represented in system
// as separate module which implements "LoaderVer1.0" interface.
static const ILoader loader_vtable;
static const IEnumerator *loader_main(void);
// Loader must know all main functions of all libraries.
static LibFuncType *library_list[] =
{
loader_main,
library1_main,
library2_main,
library3_main
};
// data type used in loader internally
typedef struct LoaderChain
{
const IDescriptor *desc;
struct LoaderChain *next;
}
LoaderChain;
// data type used in loader internally
typedef struct
{
LibLoader base;
LoaderChain *chain;
}
RealLibLoader;
// ILoader.EnumerateInterfaces function implementation.
static const void* loader_enum(IEnumCallback *cb, void *thiz)
{
unsigned n = sizeof(library_list)/sizeof(library_list[0]);
LibFuncType **func = library_list;
while (n --> 0)
{
const IEnumerator *ie = (*func)();
const void *result = ie->EnumerateInterfaces(cb, thiz);
if (result != NULL)
return result;
++func;
}
return NULL;
}
// function used internally by the loader
static const void* byname_cb(const IDescriptor *desc, void *thiz)
{
return !strcmp(desc->name, thiz) ? desc : NULL;
}
// ILoader.GetInterface function implementation.
static const void* loader_get(LibLoader *loader, const char *name)
{
RealLibLoader *thiz = (void*)loader;
const IDescriptor *desc = loader->iloader->EnumerateInterfaces(byname_cb, DECONST(name));
if (desc == NULL)
{
fprintf(stderr, "fatal error: dependent interface '%s' not found!\n", name);
return NULL;
}
// Break recursion: in case of recursion function returns NULL in place of interface
// expecting that previously called lookup will return real pointer to interface.
// As a result, interface pointers obtained in functions imlementing _GetInterface()
// should not be used directly. Instead two phase initialization required: at first
// phase (call to _GetInterface()) variables holding pointers to interfaces should
// only be initialized, but interface should not be used. And if interface usage required
// during initialization, then specially designated function should be called later,
// after all interface lookups finished, and only then perform actual initialization.
LoaderChain *chain = thiz->chain;
while (chain != NULL)
{
if (desc == chain->desc)
{
fprintf(stderr, "avoided recursion for %s\n", desc->name);
return NULL;
}
chain = chain->next;
}
LoaderChain link = { desc, thiz->chain };
thiz->chain = &link;
const void *result = desc->_GetInterface(loader);
thiz->chain = link.next;
return result;
}
// Function returns first version (only one implemented) of ILoaderInterface.
static const void* loader_get_ver1(LibLoader *loader)
{
(void)loader;
fputs("loader_get_ver1()\n", stderr);
return &loader_vtable;
}
// Function implements IEnumerator.EnumerateInterface function for the loader itself.
static const void* loader_self(IEnumCallback *cb, void *thiz)
{
static const IDescriptor desc = { ILoaderVer, loader_get_ver1 };
return cb(&desc, thiz);
}
// ILoader instance.
static const ILoader loader_vtable =
{
{ loader_self },
loader_enum,
loader_get
};
// Main function of the loader -- it shouldn't be exposed, used internally.
const IEnumerator *loader_main(void)
{
return &loader_vtable.base;
}
// This function creates new loader instance for each thread.
void GetLoaderInstance(LibLoader *loader)
{
RealLibLoader *thiz = (void*)loader;
loader->iloader = &loader_vtable;
thiz->chain = NULL;
}
/////////////////// Better to represent main program as separate module,
/////////////////// but in this example it only demonstrates linking process.
static const void* enum_cb(const IDescriptor *desc, void *thiz)
{
(void)thiz;
puts(desc->name);
return NULL;
}
int main(int argc, char *argv[])
{
LibLoader loader;
GetLoaderInstance(&loader);
// List all available modules.
puts("Available interfaces:");
loader.iloader->EnumerateInterfaces(enum_cb, NULL);
puts("");
fflush(stdout);
if (argc <= 1)
return puts("please specify interfaces which should be instantiated"), 1;
// Load modules specified at command line.
unsigned n;
for (n = 1; n < (unsigned)argc; n++)
{
printf("loading %s\n", argv[n]), fflush(stdout);
loader.iloader->GetInterface(&loader, argv[n]);
}
puts("");
return 0;
}