模块化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.cheader.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。