模块化编译时arrays扩展

让我们说我在这个场景中:

main.c:

#include  #include  #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.cheader.h

这里的问题不是更改数组,因为它可以轻松地重新分配,但是不要改变main()函数:必须有一种方法可以让我知道文件在这里并编译,并在外出时获取函数原型main()

我想过使用像这样的预处理器技巧,但似乎没有找到合适的方法来做到这一点……

(旁注:这是一个更大项目的精简版本,实际上是支持具有相同输出但输入不同的解析器的基本代码。一些解析器支持某种类型的文件,所以我有一个函数指针数组(每个解析器一个,检查它们是否兼容)然后我根据文件内容调用它们中的每一个。然后,我要求用户选择它想要使用的解析器。我有一个文件,每个解析器,包含一个“检查“函数,查看解析器是否可以处理此文件,以及”解析“函数来实际完成所有的工作。每次添加解析器时都无法更改头文件或main.c文件。)

(旁注2:这个标题很糟糕……如果你有更好的想法,请哦,请随时编辑并删除此说明。谢谢)

您可以使用已知名称的单个符号使每个函数成为模块( 共享对象或Windows的dll ),然后在运行时只需扫描目录以获取.so.dll加载每个函数并创建指向符号,假设您有N个模块,其中第i个模块源代码是

module.ic

 int function(char *parameter) { // Do whatever you want here return THE_RETURN_VALUE; } 

然后你将每个.c文件编译成一个共享对象,我将使用Linux在Windows上进行说明,你可以做类似的事情,而linux解决方案适用于POSIX系统,所以它涵盖了很多

首先使用此脚本生成module.ic文件

 #!/bin/bash for i in {0..100}; do cat > module.$ic < 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  #include  #include  #include  #include  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() ,并用适当的函数替换dlsym()

但同样的想法也会起作用。

有关如何保护加载的模块及其文件夹的更多信息,请参阅此问题

预处理器和标准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 = ; 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的代码。 当然,您也可以使用脚本生成静态表。