模块化编译时阵列扩展

时间:2016-01-10 01:04:52

标签: c arrays function pointers c-preprocessor

让我说我在这个场景中:

  

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:这个标题很糟糕......如果你有更好的想法,请哦,请随时编辑并删除此说明。谢谢)

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中的代码。当然,您也可以使用脚本生成静态表。