根本問題 - 如何實現提供不同類型的功能插件 - 很有趣。
例如,考慮一個簡單的Reverse Polish calculator,帶有添加新運算符的插件接口。
而不是讓主程序使用dlsym()
找到每個符號,插件導出只有一個 - 比方說,plugin_init()
- - 將註冊函數作爲函數指針參數。然後,每個插件會根據每個希望添加的功能調用一次註冊函數。
RPN計算器基於堆棧。如果我們假設每個運營商可以調整堆棧,操作函數原型爲基本
int operation(double **stackptr, int *countptr, int *maxcountptr);
其中*stackptr
是指向當前堆棧,*countptr
是double
S IN堆的數量,*maxcountptr
告訴尺寸分配(在double
s)堆棧。如果操作成功執行,則會返回0
,否則返回非零值errno
錯誤代碼。
請考慮這個應用程序。Ç:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
struct operator {
struct operator *next;
int (*func)(double **, int *, int *);
char name[];
};
struct operator *operators = NULL;
static int register_operator(const char *name,
int (*func)(double **, int *, int *))
{
const size_t namelen = (name) ? strlen(name) : 0;
struct operator *curr;
/* Make sure name and func are valid. */
if (!namelen || !func)
return EINVAL;
/* See if name is already used. */
for (curr = operators; curr != NULL; curr = curr->next)
if (!strcmp(name, curr->name))
return EEXIST;
/* Allocate memory for this operator. */
curr = malloc(namelen + 1 + sizeof (struct operator));
if (!curr)
return ENOMEM;
/* Copy function pointer and name. */
curr->func = func;
memcpy(curr->name, name, namelen + 1); /* Include terminating '\0'. */
/* Prepend to list. */
curr->next = operators;
operators = curr;
/* Success. */
return 0;
}
static int list_operators(double **stack, int *count, int *maxcount)
{
struct operator *curr;
fprintf(stderr, "Known operators:\n");
for (curr = operators; curr != NULL; curr = curr->next)
fprintf(stderr, "\t'%s'\n", curr->name);
return 0;
}
int main(int argc, char *argv[])
{
double *stack = NULL;
int count = 0;
int maxcount = 0;
int arg;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s ./plugin ... NUMBER [ OPERATOR | NUMBER ] ...\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
if (register_operator("list", list_operators)) {
fprintf(stderr, "Failed to register built-in 'list' operator.\n");
return EXIT_FAILURE;
}
for (arg = 1; arg < argc; arg++) {
struct operator *op;
double val;
char dummy;
/* Check if argument is a plugin path, starting with "./". */
if (argv[arg][0] == '.' && argv[arg][1] == '/') {
void *handle = dlopen(argv[arg], RTLD_NOW);
if (handle) {
int (*func)(int (*)(const char *, int (*)(double **, int *, int *))) = dlsym(handle, "plugin_init");
if (func) {
int failure = func(register_operator);
if (failure) {
fprintf(stderr, "%s: Operator registration failed: %s.\n", argv[arg], strerror(failure));
return EXIT_FAILURE;
}
} else
dlclose(handle);
continue;
}
}
/* Check if argument is a known operator. */
for (op = operators; op != NULL; op = op->next)
if (!strcmp(op->name, argv[arg]))
break;
if (op) {
int failure = op->func(&stack, &count, &maxcount);
if (failure) {
fprintf(stderr, "%s: Cannot apply operator: %s.\n", argv[arg], strerror(failure));
return EXIT_FAILURE;
}
continue;
}
/* Parse as a number. */
if (sscanf(argv[arg], " %lf %c", &val, &dummy) != 1) {
fprintf(stderr, "%s: Unknown operator.\n", argv[arg]);
return EXIT_FAILURE;
}
/* Make sure stack has enough room for an additional number. */
if (count >= maxcount) {
double *temp;
maxcount = (count | 255) + 257;
temp = realloc(stack, maxcount * sizeof *stack);
if (!temp) {
fprintf(stderr, "%s.\n", strerror(ENOMEM));
return EXIT_FAILURE;
}
stack = temp;
}
/* Push val to top of stack. */
stack[count++] = val;
}
for (arg = 0; arg < count; arg++)
printf("[%d] = %g\n", arg + 1, stack[arg]);
return (count == 1) ? EXIT_SUCCESS : EXIT_FAILURE;
}
的struct operator *operators
是著名運營商的全球單鏈表。 register_operator()
函數將新運算符添加到列表中,除非名稱已被使用。
唯一的內置運算符是list
,因此您可以列出已知的運算符。
我們來看幾個不同的插件實現。首先,plugin_basic.c:
#include <errno.h>
static int op_add(double **stack, int *count, int *maxcount)
{
if (*count < 2)
return EINVAL;
(*stack)[*count - 2] = (*stack)[*count - 1] + (*stack)[*count - 2];
(*count)--;
return 0;
}
static int op_sub(double **stack, int *count, int *maxcount)
{
if (*count < 2)
return EINVAL;
(*stack)[*count - 2] = (*stack)[*count - 1] - (*stack)[*count - 2];
(*count)--;
return 0;
}
static int op_mul(double **stack, int *count, int *maxcount)
{
if (*count < 2)
return EINVAL;
(*stack)[*count - 2] = (*stack)[*count - 1] * (*stack)[*count - 2];
(*count)--;
return 0;
}
static int op_div(double **stack, int *count, int *maxcount)
{
if (*count < 2)
return EINVAL;
(*stack)[*count - 2] = (*stack)[*count - 1]/(*stack)[*count - 2];
(*count)--;
return 0;
}
int plugin_init(int (*register_operator)(const char *name,
int (*func)(double **, int *, int *)))
{
int failure;
if ((failure = register_operator("+", op_add)))
return failure;
if ((failure = register_operator("-", op_sub)))
return failure;
if ((failure = register_operator("x", op_mul)))
return failure;
if ((failure = register_operator("/", op_div)))
return failure;
return 0;
}
它提供了四種基本的運營商+
,-
,x
,和/
;和plugin_sincos.c:
#include <math.h>
#include <errno.h>
static int op_sin(double **stack, int *count, int *maxcount)
{
if (*count < 1)
return EINVAL;
(*stack)[*count - 1] = sin((*stack)[*count - 1]);
return 0;
}
static int op_cos(double **stack, int *count, int *maxcount)
{
if (*count < 1)
return EINVAL;
(*stack)[*count - 1] = sin((*stack)[*count - 1]);
return 0;
}
int plugin_init(int (*register_operator)(const char *name,
int (*func)(double **, int *, int *)))
{
int failure;
if ((failure = register_operator("sin", op_sin)))
return failure;
if ((failure = register_operator("cos", op_cos)))
return failure;
return 0;
}
提供sin
和cos
功能。
因爲只有plugin_init()
功能需要動態出口,讓我們添加一個共同的符號文件,plugin.syms:
{
plugin_init;
};
請注意,我已經明確標明很多功能static
。這是爲了避免命名空間污染:確保它們對其他編譯單元不可見,否則可能會導致衝突。 (儘管符號文件應確保只有plugin_init()
動態出口的,static
提醒我,這是不應該的功能在任何情況下要導出的程序員。)
最後,的Makefile綁定一起:
CC := gcc
CFLAGS := -Wall -O2
LD := $(CC)
LDFLAGS := -lm -ldl
.PHONY: clean all
all: rpcalc basic.plugin sincos.plugin
clean:
rm -f rpcalc basic.plugin sincos.plugin
rpcalc: application.c
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o [email protected]
basic.plugin: plugin_basic.c
$(CC) $(CFLAGS) -fPIC -shared $^ $(LDFLAGS) -Wl,-dynamic-list,plugin.syms -Wl,-soname,[email protected] -o [email protected]
sincos.plugin: plugin_sincos.c
$(CC) $(CFLAGS) -fPIC -shared $^ $(LDFLAGS) -Wl,-dynamic-list,plugin.syms -Wl,-soname,[email protected] -o [email protected]
注意,打算行必須以標籤,而不是八個空格開始。如果您不確定,請運行sed -e 's|^ *|\t|' -i Makefile
進行修復。如果您運行
./rpcalc list
它會告訴你,唯一支持的運營商是list
本身
make clean all
:
編譯計算器及其插件。但是,如果您運行例如
./rpcalc ./basic.plugin list
./rpcalc ./*.plugin list
它會顯示由插件實現的操作符。
它也是一個工作計算器。如果你想計算,也就是說,sin(0.785398) x cos(0.785398)
,運行
./rpcalc ./*.plugin 0.785398 sin 0.785398 cos x
,並計劃將輸出[1] = 0.5
,正如你所期望。
只有編譯器能夠編譯跟蹤返回類型的當前翻譯單元。一旦編譯完成,就沒有關於參數或返回類型的信息。使用錯誤的返回類型會導致*未定義的行爲*。 –
這同樣適用於參數。沒有辦法知道函數期望的參數。 C沒有定義任何反射數據。 – JeremyP
'dlsym'只會返回函數(或其他共享對象)已加載到內存中的地址(作爲void *')。取決於你的代碼將返回的'void *'轉換爲所需的類型。在某些方面,您的問題與使用不正確原型聲明外部函數的問題類似。它會編譯和鏈接,但在運行時調用它時會生成_undefined behavior_。 –