让我说我在这个场景中:
main.c:
#include <stdio.h> #include <stdlib.h> #include "header.h" int iCanProcess (char* gimmeSmthToProcess); int processingFunctionsCount = 0; int (*(*processingFunctions)) (char*) = NULL; int addProcessingFunction(int (*fct)(char*)) { processingFunctionsCount++; processingFunctions = realloc(processingFunctions, sizeof(int (*)(char*))*ProcessingFunctionsCount); processingFunctions[processingFunctionsCount-1] = fct; } int main(int argc, char *argv[]) { char* dataToProcess = "I am some veeeery lenghty data"; addProcessingFunction(iCanProcess); [ ... ] for(unsigned int i = 0; i < processingFunctionsCount; i++) { processingFunctions[i](dataToProcess); } free(processingFunctions); return 0; } int iCanProcess (char* gimmeSmthToProcess) { ... }
somefile.c:
#include "header.h" int aFunction(char* someDataToProcess) { ... }
header.h:
#ifndef HEADER_DEF #define HEADER_DEF extern int processingFunctionsCount; extern int (*(*processingFunctions)) (char*); int addProcessingFunction(int (*fct)(char*)); #endif
有没有办法,使用宏或任何其他技巧,我可以将aFunction
添加到指针到函数processingFunctions
的数组,而不会更改main.c
或{{每次我需要添加一个?
这里的问题不是更改数组,因为它可以轻松地重新分配,但是不要改变header.h
函数:必须有一种方法我可以知道文件在这里并编译,并获取停留在main()
之外的函数原型
我考虑使用像this one这样的预处理器技巧,但似乎没有找到合适的方法来实现它......
(旁注:这是一个更大项目的精简版本,实际上是支持具有相同输出但输入不同的解析器的基本代码。一些解析器支持某种类型的文件,所以我有一个数组函数指针(每个解析器一个,检查它们是否兼容),然后我根据文件内容调用它们中的每一个。然后,我要求用户选择它想要使用的解析器。每个解析器我有一个文件,包含一个“检查”函数,看看解析器是否可以处理这个文件,还有一个“解析”函数来实际完成所有的工作。我每次添加一个解析器时都无法更改头文件或main.c文件。)
(旁注2:这个标题很糟糕......如果你有更好的想法,请哦,请随时编辑并删除此说明。谢谢)
答案 0 :(得分:3)
您可以使用已知名称的单个符号使每个函数成为模块(共享对象或Windows的),然后在运行时只需扫描目录中的 .so < / em> s或 .dll 加载每个并创建指向符号的指针,假设您有 N 模块,其中 i 模块源代码是
<强> module.i.c 强>
int function(char *parameter)
{
// Do whatever you want here
return THE_RETURN_VALUE;
}
然后你将每个 .c 文件编译成一个共享对象,我将使用Linux在windows上进行说明,你可以做类似的事情,而linux解决方案适用于POSIX系统,因此它涵盖了很多
首先使用此脚本生成 module.i.c 文件
#!/bin/bash
for i in {0..100};
do
cat > module.$i.c <<EOT
#include <stdlib.h>
int
function(char *parameter)
{
// Deal with parameter
return $i;
}
EOT
done
现在像这样创建一个 Makefile
CC = gcc
LDFLAGS =
CFLAGS = -Wall -Werror -g3 -O0
FUNCTIONS = $(patsubst %.c,%.so, $(wildcard *.*.c))
all: $(FUNCTIONS)
$(CC) $(CFLAGS) $(LDFLAGS) main.c -o main -ldl
%.so: %.c
$(CC) -shared $(CFLAGS) $(LDFLAGS) $< -o $@
clean:
@rm -fv *.so *.o main
加载模块的程序(我们假设它们与可执行文件位于同一目录中)
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <dlfcn.h>
int
main(void)
{
DIR *dir;
struct dirent *entry;
dir = opendir(".");
if (dir == NULL)
return -1;
while ((entry = readdir(dir)) != NULL)
{
void *handle;
char path[PATH_MAX];
int (*function)(char *);
if (strstr(entry->d_name, ".so") == NULL)
continue;
if (snprintf(path, sizeof(path), "./%s", entry->d_name) >= sizeof(path))
continue;
handle = dlopen(path, RTLD_LAZY);
if (handle == NULL)
continue; // Better: report the error with `dlerror()'
function = (int (*)(char *)) dlsym(handle, "function");
if (function != NULL)
fprintf(stdout, "function: %d\n", function("example"));
else
fprintf(stderr, "symbol-not-found: %s\n", entry->d_name);
dlclose(handle);
}
closedir(dir);
return 0;
}
在Windows上,这个想法是一样的,虽然你不能像上面的代码那样遍历目录,你需要使用LoadLibrary()
代替dlopen()
,并替换{{1使用适当的函数。
但同样的想法也会奏效。
有关如何保护加载的模块及其文件夹的更多信息,请参阅this question
答案 1 :(得分:1)
预处理器和标准C不会有太大帮助。最简单的解决方案是使用脚本生成样板。
使用完全便携的标准C可以轻松完成。
如果将所有处理函数放在一个目录中,并且可能用/* PROCESSOR */
之类的注释标记它们,那么使用正则表达式来理解必要的原型信息很简单。 Perl很适合这种事情:
use strict;
sub emit_header_file {
my $protos = shift;
open(F, "> table_protos.h") || die $!;
print F <<"END";
#ifndef TABLE_PROTOS_H
#define TABLE_PROTOS_H
void addAllProcessingFunctions(void);
void addProcessingFunction(int (*)(char *));
END
foreach my $proto (@$protos) {
print F "int $proto->[0](char *$proto->[1]);\n";
}
print F "#endif\n";
close F;
}
sub emit_code_file {
my $protos = shift;
open(F, "> table_builder.c") || die $!;
print F <<"END";
#include "table_protos.h"
void addAllProcessingFunctions(void) {
END
foreach my $proto (@$protos) {
print F " addProcessingFunction($proto->[0]);\n";
}
print F "}\n";
close F;
}
sub main {
my @protos;
my $dir = $ARGV[0];
opendir(DIR, $dir) || die $!;
while (my $fn = readdir(DIR)) {
next unless $fn =~ /\.c$/;
local $/;
open(F, "$dir/$fn") || die "$!: $fn";
my $s = <F>;
my @proto = $s =~ m|/\*\s*PROCESSOR\s*\*/\s*int\s*(\w+)\s*\(\s*char\s*\*\s*(\w+)\s*\)|;
push @protos, \@proto if @proto;
print STDERR "Failed to find proto in $fn\n" unless @proto;
close(F);
}
closedir(DIR);
@protos = sort { $a->[0] cmp $b->[0] } @protos;
emit_header_file(\@protos);
emit_code_file(\@protos);
}
main;
因此,如果我创建一个名为foo
的目录并在其中放置三个处理文件:
/* p1.c */
#include "table_protos.h"
// This is a processor.
/* PROCESSOR */
int procA(char *s) {
return 0;
}
/* p2.c */
#include "table_protos.h"
/*PROCESSOR*/ int procB (
char *
string_to_parse)
{ return 0; }
p3.c
类似。我改变了空格只是为了检查正则表达式。
然后我们运行
perl grok.pl foo
我们最终得到table_protos.h
:
#ifndef TABLE_PROTOS_H
#define TABLE_PROTOS_H
void addAllProcessingFunctions(void);
void addProcessingFunction(int (*)(char *));
int procA(char *s);
int procB(char *string_to_parse);
int procC(char *param);
#endif
和table_builder.c
:
#include "table_protos.h"
void addAllProcessingFunctions(void) {
addProcessingFunction(procA);
addProcessingFunction(procB);
addProcessingFunction(procC);
}
您可以#include
分别根据需要调用这些内容。
请注意,您可以创建函数指针的静态表,这样可以避免addAllProcessingFunctions
中的代码。当然,您也可以使用脚本生成静态表。