Вызов функции, названной в строковой переменной в C - PullRequest
50 голосов
/ 13 июля 2009

Я хочу вызвать функцию, используя переменную. Это возможно в C?

На самом деле, я хочу получить имя функции от пользователя и сохранить его в переменной. Теперь я хочу вызвать функцию, имя которой сохранено. Может кто-нибудь сказать мне, как это можно сделать в C?

Я хочу разработать игровой движок ИИ для двух игроков. В движок игры будут загружены две программы без основной функции, которые реализуют логику для победы в игре. Позвольте мне прояснить, что имена программ будут такими же, как и у простых функций в программе, которые реализуют логику для победы в игре.

Поэтому, когда пользователь вводит имя первого и второго игроков, я могу сохранить их в 2 разных переменных. Теперь, поскольку имена простых функций совпадают с именами имен программ, я намерен вызывать функции с переменными, содержащими имена прог.

Ответы [ 12 ]

41 голосов
/ 13 июля 2009

C не поддерживает этот вид операции (языки, которые имеют отражение). Лучшее, что вы сможете сделать, - это создать справочную таблицу из имен функций для указателей на функции и использовать ее для определения, какую функцию вызывать. Или вы можете использовать оператор switch.

36 голосов
/ 13 июля 2009

Лучшее, что вы можете сделать, это что-то вроде этого:

#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);
}

При этом для идентификации функций используются числа вместо строк, но для строк это просто преобразование строки в правильную функцию.

Что вы делаете, точно? Если только из любопытства, вот и все, но если вы пытаетесь решить проблему с этим, я уверен, что есть способ, более подходящий для вашей задачи.

Редактировать

Что касается вашего редактирования, вам наверняка захочется ответить onebyone .

Вы хотите, чтобы ваш пользователь создавал динамические библиотеки (это общий объект [.so] в Linux и библиотека динамических ссылок [.dll] в Windows).

Как только вы это сделаете, если они предоставят вам имя своей библиотеки, вы можете попросить операционную систему загрузить эту библиотеку для вас и запросить указатель на функцию в этой библиотеке.

27 голосов
/ 13 июля 2009

Хотя это не совсем практичное решение, могу поспорить, что вы наверняка могли бы вызвать функцию по строке, прочитав программу в своем собственном исполняемом файле и проанализировав таблицу символов. Таблица символов должна содержать имя функции, а также ее первый адрес инструкции. Затем вы можете поместить этот адрес в переменную-указатель функции и вызвать ее.

Я думаю, что я могу попытаться это сделать.

РЕДАКТИРОВАТЬ: Пожалуйста, никто никогда не писал реальный код, подобный этому, но вот как вы можете вызвать функцию, используя строку для двоичного файла ELF Linux с таблицей неповрежденных символов (требуется 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;
  }   
15 голосов
/ 13 июля 2009

Это не возможно в чистом C, однако вы можете играть в трюки с DLL. Поместите все функции, из которых вы хотите выбрать, в dll, затем используйте dlsym (или GetProcAddress в Windows, или любой другой API, предлагаемый вашей системой), чтобы получить указатель функции по имени, и вызовите его с помощью.

Это не работает на некоторых платформах, потому что у них вообще нет dll, или потому что, как и в Symbian, функции в dll не могут быть доступны по имени во время выполнения, только по номеру.

Имейте в виду, что если ваш пользователь обманывает вас при выборе функции, которая не имеет правильных параметров для вызова, который вы хотите сделать, то ваша программа пойдет не так. C действительно не рассчитан на такие вещи.

12 голосов
/ 13 июля 2009
#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");
}
2 голосов
/ 13 июля 2009

Это то, что вы пытаетесь:

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

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


int main()
{
    char fun[10] = {'\0'};

    printf("Enter function name (max 9 characters):");
    scanf("%s",fun);

    if(strcmpi(fun, "foo") == 0)
    {
    foo();
    }
    else if(strcmpi(fun, "bar") == 0)
    {
    bar();
    }
    else
    {
    printf("Function not found\n");
    }


    return 0;
}
2 голосов
/ 13 июля 2009

Как говорили другие, это правда, что C не имеет механизма отражения. Но вы можете достичь такого поведения, используя динамически загружаемую библиотеку / общий объект. На самом деле вы можете загрузить динамическую библиотеку, а затем вы можете вызвать функции в dll / so с их именем! Это не зависит от C и ОС, но это так. Он использует dlopen в Linux и LoadLibrary для Windows. Вы можете найти библиотеки, которые выполняют всю работу за вас, например gtk / glib .

1 голос
/ 04 апреля 2019

Я попробовал это решение: открыть исполняемый файл как динамическую библиотеку с помощью 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;
}
1 голос
/ 02 ноября 2016

Я только что попробовал подход Стива Джессопа с использованием статически связанной библиотеки, предложенный Williham Totland в комментариях, и он оказался нетривиальным.

Во-первых, вы найдете множество мест в Интернете (включая Википедию), которые скажут вам, что способ открыть вашу основную программу в виде библиотеки - это вызвать dlopen (3), например dlopen(NULL, 0). Это не будет работать для glibc, потому что должен быть указан флаг привязки, поскольку man-страница четко гласит:

В флаге должно быть указано одно из следующих двух значений:
RTLD_LAZY
Выполните ленивую привязку. Разрешать только символы как код ...
RTLD_NOW
Если указано это значение или переменная окружения ...

Я не думаю, какой из них вы выберете, имеет значение, потому что вы собираетесь связать все символы из статической библиотеки в свой исполняемый файл.

Это подводит нас к следующей проблеме. Компоновщик не будет включать символы из вашей статической библиотеки в ваш исполняемый файл, поскольку на них нет ссылок . Способ заставить компоновщик GNU включать символы в любом случае - -Wl,--whole-archive path/to/static/lib.a -Wl,--no-whole-archive, как этот ответ описывает. Способ заставить компоновщик Mac OS X включить все символы из вашей статической библиотеки - -Wl,-force_load path/to/static/lib.a.

1 голос
/ 13 июля 2009

Если я правильно понимаю ваш вопрос, вы хотите использовать позднюю привязку для вызова функции C. Это не то, что вы обычно можете делать в C. Символические имена (например, имена функций) не хранятся в коде, сгенерированном компилятором C. Вероятно, вы могли бы позволить компилятору испускать символы, а затем использовать их для выполнения позднего связывания, но техника будет отличаться от компилятора к компилятору и, вероятно, не будет стоить затраченного времени.

Такие языки, как C # и Java, поддерживают рефлексию, что упрощает позднюю привязку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...