N-Dependency injection in C - better way than link

2019-08-05 18:47发布

问题:

Given a library module, in the following called Runner, which resides as a reusable component (no recompilation required, i.e. static link library) in the app partition of the architecture, not the main partition. Note that it only contains main() for demo purposes.

Given a set (order irrelevant) of other modules / objects called Callables, i.e. Callable1, Callable2 and Callable3, which also reside as reusable components in the app partition.

Runner has a runtime dependency on the Callables in that Runner needs to know each and every of these Callables in order to get data from them (access a structure) or have them perform an operation (call a function).

Runner shall not have a compile dependency on the Callables. Instead the dependencies shall be injected via the linker into a fictive module Callables. The concept shall be usable not only in a hosted environment but also a freestanding environment. Therefore, loader-based mechanisms like loading the Callables as shared objects at runtime cannot be used.

How can one inject the dependencies using the linker?

My working solution is by letting the Callables define pointers in a dedicated section, in this case named Callables, which the linker would collect so that the Runner can access it, gaining the necessary information at link time.

Callable.h

#ifndef CALLABLE_H
#define CALLABLE_H
typedef void (*Callable)(void);
#endif

Callables.h

#ifndef CALLABLES_H
#define CALLABLES_H
#include "Callable.h"
extern Callable Callables_start[];
extern Callable Callables_end[];
#endif

Runner.c

#include "Callables.h"
void Runner_run(void) {
    for (Callable *callables = Callables_start; callables < Callables_end; callables++)
        (*callables)();
}
int main(void) {
    Runner_run();
    return 0;
}

Callable1.c

#include <stdio.h>
#include "Callable.h"
static void Callable1_call(void) {
    printf("Callable 1\n");
}
static Callable thisCallable __attribute__((section("Callables"))) = &Callable1_call;

Callable2.c and Callable3.c look the same except for a different message in printf().

Callables.ld

PROVIDE(Callables_start = LOADADDR(Callables));
PROVIDE(Callables_end = Callables_start + SIZEOF(Callables));

Makefile

CFLAGS+=--std=c99
callables:=Callable1 Callable2 Callable3
.PHONY: all
all: Runner

Runner: Callables.ld Runner.o $(addsuffix .o, $(callables))

Output

$ make -s && ./Runner
Callable 1
Callable 2
Callable 3

The output shows that the solution works, but I'm not satisfied with the solution. What are alternative, preferably better ways to inject dependencies using the linker than the solution that I've found?

Note

The environment is GCC / ld on x86_64 with -ffreestanding, but I'm very much interested in solutions that are less toolchain-specific / more portable than the solution which I've found so far.