在C中调用字符串变量中指定的函数

wan*_*eek 53 c function

我想使用变量调用函数.这可能在C?

实际上,我想要做的是,从用户获取函数名称并将其存储在变量中.现在我想调用存储其名称的函数.谁能告诉我如何在C中完成这项工作?

我想为双人游戏开发AI游戏引擎.没有实现赢得游戏逻辑的主要功能的两个程序将被馈送到游戏引擎.让我明确一下,程序名称将与实现赢得游戏逻辑的程序中的主要功能相同.

因此,当用户输入第一个和第二个玩家的名字时,我可以将它们存储在2个不同的变量中.现在,由于函数名称与程序名称相同,我打算用包含prog名称的变量调用函数.

Bla*_*rad 41

C不支持这种操作(具有反射的语言).您能够做的最好的事情是创建一个从函数名到函数指针的查找表,并使用它来确定要调用的函数.或者您可以使用switch语句.

  • 你在说什么`switch`? (6认同)

GMa*_*ckG 37

你能做的最好的事情是这样的:

#include <stdio.h>

// functions
void foo(int i);
void bar(int i);

// function type
typedef void (*FunctionCallback)(int);
FunctionCallback functions[] = {&foo, &bar};

int main(void)
{
    // get function id
    int i = 0;
    scanf("%i", &i);

    // check id
    if( i >= sizeof(functions))
    {
        printf("Invalid function id: %i", i);
        return 1;
    }

    // call function
    functions[i](i);

    return 0;
}

void foo(int i)
{
    printf("In foo() with: %i", i);
}

void bar(int i)
{
    printf("In bar() with: %i", i);
}
Run Code Online (Sandbox Code Playgroud)

这使用数字而不是字符串来标识函数,但使用字符串执行它只是将字符串转换为正确的函数.

你在干什么?如果只是为了好奇,你可以去,但如果你想解决这个问题,我相信有一种方法更适合你的任务.

编辑

关于你的编辑,你肯定会想到一个人的答案.

您希望您的用户构建动态库(即Linux中的共享对象[.so]和Windows中的动态链接库[.dll]).

一旦你这样做,如果他们为你提供了他们的库的名称,你可以要求操作系统为你加载该库,并请求指向该库中的函数的指针.

  • 为"如果你正试图解决这个问题,我确信有一种方法更适合你的任务." (2认同)

Fal*_*ina 27

虽然这不是一个实际的解决方案,但我敢打赌你当然可以通过让一个程序读入它自己的可执行文件并解析符号表来调用函数.符号表应包含函数的名称以及它的第一个指令地址.然后,您可以将此地址放在函数指针变量中并调用它.

我想我可以尝试鞭打它.

编辑:请不要写这样的真实代码,但是这里是如何使用字符串为具有完整符号表的Linux ELF二进制文件调用函数(需要libelf):

#include <fcntl.h>
#include <stdio.h>
#include <elf.h>
#include <libelf.h>
#include <stdlib.h>
#include <string.h>

void callMe() {
  printf("callMe called\n");
}

int main(int argc, char **argv) {
  Elf64_Shdr *    shdr;
  Elf64_Ehdr *    ehdr;
  Elf *        elf;
  Elf_Scn *    scn;
  Elf_Data *    data;
  int cnt;
  void (*fp)() = NULL;

  int fd = 0;

  /* This is probably Linux specific - Read in our own executable*/
  if ((fd = open("/proc/self/exe", O_RDONLY)) == -1)
    exit(1);

  elf_version(EV_CURRENT);

  if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
    fprintf(stderr, "file is not an ELF binary\n");
    exit(1);
  }
    /* Let's get the elf sections */
    if (((ehdr = elf64_getehdr(elf)) == NULL) ||
    ((scn = elf_getscn(elf, ehdr->e_shstrndx)) == NULL) ||
    ((data = elf_getdata(scn, NULL)) == NULL)) {
      fprintf(stderr, "Failed to get SOMETHING\n");
      exit(1);
    }

    /* Let's go through each elf section looking for the symbol table */
    for (cnt = 1, scn = NULL; scn = elf_nextscn(elf, scn); cnt++) {
      if ((shdr = elf64_getshdr(scn)) == NULL)
    exit(1);

      if (shdr->sh_type == SHT_SYMTAB) {
    char *name;
    char *strName;
    data = 0;
    if ((data = elf_getdata(scn, data)) == 0 || data->d_size == 0) {
      fprintf(stderr, "No data in symbol table\n");
      exit(1);
    }

    Elf64_Sym *esym = (Elf64_Sym*) data->d_buf;
    Elf64_Sym *lastsym = (Elf64_Sym*) ((char*) data->d_buf + data->d_size);

    /* Look through all symbols */ 
    for (; esym < lastsym; esym++) {
      if ((esym->st_value == 0) ||
          (ELF64_ST_BIND(esym->st_info)== STB_WEAK) ||
          (ELF64_ST_BIND(esym->st_info)== STB_NUM) ||
          (ELF64_ST_TYPE(esym->st_info)!= STT_FUNC)) 
        continue;

      name = elf_strptr(elf,shdr->sh_link , (size_t)esym->st_name);

      if(!name){
        fprintf(stderr,"%sn",elf_errmsg(elf_errno()));
        exit(-1);
      }
      /* This could obviously be a generic string */
      if(strcmp("callMe", name) == 0 ) {
        printf("Found callMe @ %x\n", esym->st_value);
        fp = esym->st_value;
      }
    }    
    /* Call and hope we don't segfault!*/
    fp();
    elf_end(elf);
    return 0;
  }   
Run Code Online (Sandbox Code Playgroud)

  • 当你打开/ proc/self/exe时,你知道你正在做一些特别的事情......我自己去过那里;) (5认同)
  • 你也可以dlopen(0,...)和dlsym(符号)而不是循环. (3认同)

Ste*_*sop 15

这在纯C中是不可能的,但是你可以用dll玩弄技巧.将您要选择的所有函数放入dll中,然后使用dlsym(或GetProcAddress在Windows上,或系统提供的任何其他API)按名称获取函数指针,并使用它进行调用.

这在某些平台上不起作用,因为它们根本没有dll,或者因为像Symbian一样,dll中的函数在运行时无法通过名称访问,只能通过数字访问.

请注意,如果您的用户欺骗您选择一个功能,该功能没有您想要进行的呼叫的正确参数,那么您的程序将出错.C真的不是为了应对这种事情而设计的.

  • 作为旁注; 如果你想使用`dlsym`访问函数,它们实际上不必在dll中; 您可以以合适的方式链接您的可执行文件,并将可执行映像视为dll用于此目的. (2认同)

Sea*_*ght 13

#include <stdio.h>
#include <string.h>

void function_a(void) { printf("Function A\n"); }
void function_b(void) { printf("Function B\n"); }
void function_c(void) { printf("Function C\n"); }
void function_d(void) { printf("Function D\n"); }
void function_e(void) { printf("Function E\n"); }

const static struct {
  const char *name;
  void (*func)(void);
} function_map [] = {
  { "function_a", function_a },
  { "function_b", function_b },
  { "function_c", function_c },
  { "function_d", function_d },
  { "function_e", function_e },
};

int call_function(const char *name)
{
  int i;

  for (i = 0; i < (sizeof(function_map) / sizeof(function_map[0])); i++) {
    if (!strcmp(function_map[i].name, name) && function_map[i].func) {
      function_map[i].func();
      return 0;
    }
  }

  return -1;
}

int main()
{
  call_function("function_a");
  call_function("function_c");
  call_function("function_e");
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我尝试了这个解决方案:使用 dlopen() 将可执行文件作为动态库打开。

它在我的 Linux Ubuntu 上运行良好,但不幸的是在我的嵌入式 ARM 目标上运行不佳。我不知道为什么,如果它取决于 glibc 或 libdl 的版本可能。

在我的 ARM 目标上,消息很明确:“./test8:无法动态加载可执行文件”。

无论如何,我使用的代码就是这个。

我编译的是:

gcc test8.c -ldl -rdynamic

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int TEST_MyTestFunction( char *pArgPos , int Size , char Param )
{
    printf("TEST_MyTestFunction\n");
    return 0;
}

int main(int argc, char **argv)
{
    int ret;
    void *handle;
    char *error;

    int(*func)(char *,int, char);

    handle = dlopen(argv[0], RTLD_LAZY);
    dlerror();

    func = (int(*)(char *,int, char)) dlsym(handle, "TEST_MyTestFunction");

    error = dlerror();

    char *p1 = "param1";
    int p2 = 5;
    int p3 = 'A';

    ret = func(p1, p2, p3);

    return ret;
}
Run Code Online (Sandbox Code Playgroud)