web-dev-qa-db-fra.com

Appeler une fonction nommée dans une variable chaîne en C

Je veux appeler une fonction en utilisant une variable. Est-ce possible en C?

En fait, ce que je veux faire, c'est obtenir le nom de la fonction de l'utilisateur et le stocker dans une variable. Maintenant, je veux appeler la fonction qui a son nom stocké. Quelqu'un peut-il me dire comment cela peut être fait en C?

Je souhaite développer un moteur de jeu d'IA pour un jeu à deux joueurs. Deux programmes sans fonction principale qui implémentent la logique pour gagner le jeu seront acheminés vers le moteur de jeu ..___ Soyons clairs: les noms des programmes seront identiques à ceux des fonctions principales du programme qui implémenteront la logique pour gagner le jeu.

Ainsi, lorsque l'utilisateur saisit le nom du premier et du deuxième joueur, je peux les stocker dans deux variables différentes. Maintenant, puisque les noms de primefunction sont les mêmes que ceux des noms de programme, j’ai l’intention d’appeler les fonctions avec des variables contenant les noms de prog.

48
wantobegeek

C ne supporte pas ce genre d'opération (les langues qui ont une réflexion le feraient). Le mieux que vous puissiez faire est de créer une table de recherche à partir des noms de fonction en pointeurs de fonction et de l'utiliser pour déterminer quelle fonction appeler. Ou vous pouvez utiliser une instruction switch.

41
Blair Conrad

Le mieux que vous puissiez faire est quelque chose comme ceci:

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

Cela utilise des chiffres plutôt que des chaînes pour identifier les fonctions, mais le faire avec des chaînes consiste simplement à convertir la chaîne en fonction appropriée.

Que fais-tu exactement? Si vous voulez juste par curiosité, vous y êtes, mais si vous essayez de résoudre un problème avec cela, je suis sûr qu'il existe un moyen plus adapté à votre tâche.

Modifier

En ce qui concerne votre modification, vous voudrez aller avec onebyone's answer, à coup sûr.

Vous voulez que vos utilisateurs construisent des bibliothèques dynamiques (c'est un objet partagé [.so] sous Linux et une bibliothèque de liens dynamiques [.dll] sous Windows).

Une fois cette opération effectuée, s’ils vous communiquent le nom de leur bibliothèque, vous pouvez demander au système d’exploitation de charger cette bibliothèque pour vous et demander un pointeur sur une fonction de cette bibliothèque.

36
GManNickG

Bien que ce ne soit pas vraiment une solution pratique, je parie que vous pourriez certainement appeler une fonction par une chaîne en faisant lire un programme dans son propre exécutable et en analysant la table des symboles. La table des symboles doit contenir le nom de la fonction ainsi que sa première adresse d'instruction. Vous pouvez ensuite placer cette adresse dans une variable de pointeur de fonction et l'appeler.

Je pense que je peux essayer de concocter ça.

EDIT: S'il vous plaît, personne n'écrit jamais de code réel comme celui-ci, mais voici comment appeler une fonction en utilisant une chaîne pour un binaire ELF Linux avec une table de symboles intacts (requiert 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;
  }   
27
Falaina

Ce n'est pas possible en C pur, mais vous pourrez peut-être jouer des tours avec des dll. Mettez toutes les fonctions que vous voulez sélectionner dans une dll, puis utilisez dlsym (ou GetProcAddress sous Windows ou toute autre API proposée par votre système) pour obtenir le pointeur de la fonction par son nom et appelez-le en utilisant.

Cela ne fonctionne pas sur certaines plates-formes, soit parce qu'elles n'ont pas du tout de dll, soit parce que, comme Symbian, les fonctions de la dll ne sont pas accessibles par nom au moment de l'exécution, mais par leur numéro.

Sachez que si votre utilisateur vous incite à choisir une fonction qui ne possède pas les paramètres corrects pour l'appel que vous souhaitez effectuer, votre programme se trompera. C n’est vraiment pas conçu pour faire face à ce genre de chose.

15
Steve Jessop
#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");
}
11
Sean Bright

Comme l'ont dit d'autres personnes, il est vrai que C n'a pas de mécanisme de réflexion. Mais vous pouvez obtenir ce type de comportement en utilisant une bibliothèque chargée/un objet partagé chargé dynamiquement. En fait, vous pouvez charger une bibliothèque dynamique et ensuite appeler les fonctions de la DLL/so avec leur nom! Ce n'est pas spécifique à C et OS, mais c'est la solution. Il utilise dlopen sous Linux et LoadLibrary sous Windows. Vous pouvez trouver des bibliothèques qui font le travail pour vous comme gtk/glib .

2
neuro

Est-ce ce que vous essayez:

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
Naveen

Si je comprends bien votre question, vous souhaitez utiliser une liaison tardive pour appeler une fonction C. Ce n'est pas quelque chose que vous pouvez normalement faire en C. Les noms symboliques (comme les noms de fonctions) ne sont pas stockés dans le code généré par le compilateur C. Vous pouvez probablement laisser le compilateur émettre des symboles, puis les utiliser pour effectuer la liaison tardive, mais la technique varie d'un compilateur à l'autre et ne vaut probablement pas la peine.

Des langages tels que C # et Java prennent en charge la réflexion, ce qui facilite la liaison tardive.

1
Martin Liversage

Je viens d'essayer l'approche de Steve Jessop en utilisant une bibliothèque liée de manière statique comme suggéré par Williham Totland dans les commentaires et elle s'est révélée être non triviale.

Tout d'abord, vous trouverez sur le Web de nombreux sites (y compris Wikipedia) qui vous indiqueront que pour ouvrir votre programme principal en tant que bibliothèque, vous devez appeler dlopen (3) comme ceci dlopen(NULL, 0). Cela ne fonctionnera pas pour la glibc car un indicateur de liaison doit être spécifié, comme l'indique clairement la page de manuel:

L'une des deux valeurs suivantes doit être incluse dans l'indicateur:
RTLD_LAZY
Effectuer une reliure paresseuse. Résoudre uniquement les symboles en tant que code ...
RTLD_NOW
Si cette valeur est spécifiée ou la variable d'environnement ...

Je ne pense pas que celui que vous choisissiez importe ici car vous allez lier tous les symboles de la bibliothèque statique à votre exécutable.

Cela nous amène au problème suivant. L'éditeur de liens n'inclut pas les symboles de votre bibliothèque statique dans votre exécutable car ils ne sont pas référencés . Le moyen de forcer l'éditeur de liens GNU à inclure les symboles de toute façon est -Wl,--whole-archive path/to/static/lib.a -Wl,--no-whole-archive comme le décrit cette réponse. -Wl,-force_load path/to/static/lib.a est le moyen de forcer l'éditeur de liens Mac OS X à inclure tous les symboles de votre bibliothèque statique.

1
Jose Quinteiro

Les tableaux C ne peuvent être indexés qu'avec des types intégraux. Écrivez donc une chaîne de mappage de table de hachage en pointeurs de fonction.

Il pourrait également être intéressant de regarder les installations de Python, Lua et d’autres langages de script pour intégrer leur exécution dans un programme C: des parties du code C peuvent ensuite être manipulées avec le langage de script.

Enfin, certaines personnes codent les extensions de langage de script en C. Elles peuvent ensuite avoir la vitesse et l’accès bas niveau de C dans leurs scripts.

Vous allez découvrir, tôt ou tard, qu’utiliser des idiomes de langage de script à typage dynamique - comme eval (), et la ligne floue entre fonction et nom de fonction et le code qui dépend des tableaux assoc - en C est possible mais finalement pénible.

0
hoohoo

Présentation de la fonction Nginx-c . Il s’agit d’un module NGINX qui vous permet de lier votre application .so (c/c ++) dans un contexte de serveur et d’appeler la fonction de l’application .so dans la directive location. Vous pouvez implémenter le cache de données en mémoire partagée nginx via la fonction nginx c ( https://github.com/Taymindis/nginx-c-function/wiki/Nginx-Cache-Data-via-nginx-c-function ). C’est pour les développeurs qui aiment héberger le serveur c https://github.com/Taymindis/nginx-c-function

 enter image description here

0
Oktaheta

J'ai essayé cette solution: ouvrir l'exécutable en tant que bibliothèque dynamique avec dlopen ().

Cela fonctionne très bien sous Linux Ubuntu, mais malheureusement pas sur ma cible ARM incorporée. Je ne sais pas pourquoi, si cela dépend peut-être de la glibc ou de la version de libdl.

Sur ma cible ARM, le message est clair: "./test8: impossible de charger dynamiquement l'exécutable".

Quoi qu'il en soit, le code que j'ai utilisé est celui-ci.

J'ai compilé avec:

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;
}
0
Erwan