Вызвать функцию, названную в строковой переменной в C
Я хочу вызвать функцию, используя переменную. Возможно ли это в C?
Собственно, я хочу, чтобы получить имя функции от пользователя и сохранить его в переменной. Теперь я хочу вызвать функцию, имя которой сохранено. Может ли кто-нибудь сказать мне, как это можно сделать в C?
Я хочу разработать игровой движок AI для двухпользовательской игры. Две программы без основной функции, которые реализуют логику выигрыша в игре, будут переданы в игровой движок.
Позвольте мне пояснить, что имена программ будут такими же, как и в простых функциях в программе, которые реализуют логику выигрыша игры.
Итак, когда пользователь вводит имя первого и второго игроков, я могу хранить их в двух разных переменных. Теперь, поскольку имена премьер-функций такие же, как имена программ, я намереваюсь вызывать функции с переменными, содержащими имена прог.
Ответы
Ответ 1
C не поддерживает такую операцию (языки, на которых есть отражение). Лучшее, что вы сможете сделать, это создать таблицу поиска из имен функций в указатели на функции и использовать ее для определения, какую функцию вызывать. Или вы можете использовать оператор switch.
Ответ 2
Лучшее, что вы можете сделать, это что-то вроде этого:
#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).
Как только вы это сделаете, если они предоставят вам имя своей библиотеки, вы можете попросить операционную систему загрузить эту библиотеку для вас и запросить указатель на функцию в этой библиотеке.
Ответ 3
Хотя это не совсем практическое решение, я готов поспорить, что вы могли бы, конечно, вызвать функцию по строке, если программа прочитала в ней собственный исполняемый файл и проанализировала таблицу символов. Таблица символов должна содержать имя функции, а также адрес первой инструкции. Затем вы можете поместить этот адрес в переменную указателя функции и вызвать его.
Думаю, я могу попытаться взломать это.
РЕДАКТИРОВАТЬ: Пожалуйста, никто никогда не писал настоящий код, как это, но вот как вы можете вызвать функцию, используя строку для 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 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 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;
}
Ответ 4
Это невозможно в чистом C, однако вы можете играть в трюки с dll. Поместите все функции, которые вы хотите выбрать, в dll, затем используйте dlsym
(или GetProcAddress
в Windows или любой другой API, который предлагает ваша система), чтобы получить указатель на функцию по имени и вызвать с помощью этого.
Это не работает на некоторых платформах либо потому, что у них вообще нет библиотек, либо потому, что, как и Symbian, функции в dll не могут быть доступны по имени во время выполнения, только по номеру.
Имейте в виду, что если ваш пользователь обманывает вас в выборе функции, которая не имеет правильных параметров для вызова, который вы хотите сделать, ваша программа пойдет не так. C действительно не предназначен для того, чтобы справляться с такими вещами.
Ответ 5
#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");
}
Ответ 6
Как говорят другие, верно, что C не имеет механизма отражения. Но вы можете добиться такого поведения, используя динамически загружаемую библиотеку/общий объект. Фактически вы можете загрузить динамическую библиотеку, а затем вы можете вызвать функции в dll/so с их именем! Это не C и ОС, а способ. Он использует dlopen для Linux и LoadLibrary для Windows. Вы можете найти библиотеки, которые выполняют для вас работу, например .
Ответ 7
Это то, что вы пытаетесь:
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;
}
Ответ 8
Если я правильно понял ваш вопрос, вы хотите использовать последнее связывание для вызова функции C. Это не то, что вы обычно можете делать в C. Символьные имена (например, имена функций) не сохраняются в коде, сгенерированном компилятором C. Вероятно, вы можете позволить компилятору испускать символы, а затем использовать их для выполнения поздней привязки, но техника будет отличаться от компилятора к компилятору и, вероятно, не стоит того, чтобы газировать.
Языки, подобные С# и Java, поддерживают отражение, облегчая выполнение позднего связывания.
Ответ 9
Я просто попробовал подход Стив Джессопа, используя статически связанную библиотеку, предложенную 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
.
Ответ 10
C массивы могут быть проиндексированы только с интегральными типами. Поэтому напишите строки отображения хеш-таблицы для указателей функций.
Также возможно посмотреть на объект на языках Python, Lua и других script, чтобы внедрить их время выполнения в программу на C: части кода C затем могут обрабатываться с помощью языка сценариев.
Alt'ly, некоторые люди кодируют языковые расширения script в C. Затем они могут иметь доступ к скорости и низкому уровню C в своих сценариях.
Вы рано или поздно обнаружите, что с помощью динамически типизированных языковых слов script, таких как eval(), и размытой линии между именем функции и функции и кода, который зависит от массивов-зависимостей - на C, возможно но в конечном итоге болезненным.
Ответ 11
Представляем Nginx-c-функцию. Это модуль NGINX, который позволяет вам связать ваше приложение .so(c/c++) в контексте сервера и вызвать функцию приложения .so в директиве location. Вы можете внедрить кеш данных общей памяти nginx через функцию nginx c (https://github.com/Taymindis/nginx-c-function/wiki/Nginx-Cache-Data-via-nginx-c-function). Это для разработчиков, которые любят хостинг c сервером. https://github.com/Taymindis/nginx-c-function
![enter image description here]()
Ответ 12
Я попробовал это решение: идея состоит в том, чтобы открыть исполняемый файл как динамическую библиотеку с помощью dlopen()...
К сожалению, это не работает везде. Я не знаю, зависит ли это от glibc или версии libdl?
Например, в Linux Ubuntu это нормально. Затем, после компиляции и попытки нацеливания, я получил приятное сообщение "./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;
unsigned char *function_name = "TEST_MyTestFunction";
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;
}
Ответ 13
Компиляция в качестве динамической библиотеки и использование dlsym()
может помочь. По крайней мере, это единственный способ, которым я знаю, но он не поддерживает cxx.