模块化compile-time数组扩展
Modular compile-time array expansion
假设我在这个位置:
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
添加到 pointer-to-functions processingFunctions
的数组而不更改 main.c
或 header.h
每次我需要加一个 ?
这里的问题不是更改数组,因为它可以很容易地重新分配,而是不更改 main()
函数:必须有一种方法我可以知道文件在这里并被编译,并且在 main()
之外获取函数原型
我考虑过使用像 this one 这样的预处理器技巧,但似乎没有找到合适的方法...
(Side-note :这是一个更大项目的 trimmed-down 版本,它实际上是支持具有相同输出但不同输入的解析器的基本代码。一些解析器支持某些类型的文件,所以我有一组函数指针(每个解析器一个,以检查它们是否兼容),我根据文件内容调用它们中的每一个。然后,我要求用户选择它想要使用的解析器。我每个解析器有一个文件,包含一个 "check" 函数,以查看解析器是否可以处理该文件,以及一个 "parse" 函数来实际完成所有艰苦的工作。我无法更改 header 或每次添加解析器时 main.c 文件。)
(Side-note 2:这个标题太糟糕了...如果您有更好的想法,请哦,请随意编辑它并删除此注释。谢谢)
您可以使每个函数成为一个模块(共享对象或 windows 的 dll),带有已知名称的单个符号,然后在运行时只需扫描一个.sos 或 .dlls 的目录加载每一个并创建一个指向符号的指针,假设你有 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()
,并替换dlsym()
具有适当的功能。
但同样的想法也行得通。
有关如何保护您加载的模块及其文件夹的更多信息,请参见
预处理器和标准 C 不会有太大帮助。最简单的解决方案是使用脚本生成样板文件。
这可以很容易地用完全 portable 标准 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
并根据需要分别调用它们。
请注意,您可以将函数指针设为静态 table,这样可以避免 addAllProcessingFunctions
中的代码。当然,您也可以使用脚本生成静态 table。
假设我在这个位置:
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
添加到 pointer-to-functions processingFunctions
的数组而不更改 main.c
或 header.h
每次我需要加一个 ?
这里的问题不是更改数组,因为它可以很容易地重新分配,而是不更改 main()
函数:必须有一种方法我可以知道文件在这里并被编译,并且在 main()
我考虑过使用像 this one 这样的预处理器技巧,但似乎没有找到合适的方法...
(Side-note :这是一个更大项目的 trimmed-down 版本,它实际上是支持具有相同输出但不同输入的解析器的基本代码。一些解析器支持某些类型的文件,所以我有一组函数指针(每个解析器一个,以检查它们是否兼容),我根据文件内容调用它们中的每一个。然后,我要求用户选择它想要使用的解析器。我每个解析器有一个文件,包含一个 "check" 函数,以查看解析器是否可以处理该文件,以及一个 "parse" 函数来实际完成所有艰苦的工作。我无法更改 header 或每次添加解析器时 main.c 文件。)
(Side-note 2:这个标题太糟糕了...如果您有更好的想法,请哦,请随意编辑它并删除此注释。谢谢)
您可以使每个函数成为一个模块(共享对象或 windows 的 dll),带有已知名称的单个符号,然后在运行时只需扫描一个.sos 或 .dlls 的目录加载每一个并创建一个指向符号的指针,假设你有 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()
,并替换dlsym()
具有适当的功能。
但同样的想法也行得通。
有关如何保护您加载的模块及其文件夹的更多信息,请参见
预处理器和标准 C 不会有太大帮助。最简单的解决方案是使用脚本生成样板文件。
这可以很容易地用完全 portable 标准 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
并根据需要分别调用它们。
请注意,您可以将函数指针设为静态 table,这样可以避免 addAllProcessingFunctions
中的代码。当然,您也可以使用脚本生成静态 table。