我想使用变量调用函数。这可能在C?
实际上,我想要做的是,从用户获取函数名称并将其存储在变量中。现在我想调用存储其名称的函数。谁能告诉我如何在C中完成这项工作?
我想为双人游戏开发AI游戏引擎。没有实现赢得游戏的逻辑的主要功能的两个程序将被馈送到游戏引擎。 让我明确一下,程序名称将与实现赢得游戏逻辑的程序中的主要功能相同。
因此,当用户输入第一个和第二个玩家的名字时,我可以将它们存储在2个不同的变量中。现在,由于函数名称与程序名称相同,我打算用包含编号名称的变量调用函数。
答案 0 :(得分:41)
C不支持这种操作(具有反射的语言)。您能够做的最好的事情是创建一个从函数名到函数指针的查找表,并使用它来确定要调用的函数。或者你可以使用switch语句。
答案 1 :(得分:36)
你能做的最好的事情就是这样:
#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's回答。
您希望您的用户构建动态库(即Linux中的共享对象[.so]和Windows中的动态链接库[.dll])。
一旦这样做,如果他们为您提供了他们的库的名称,您可以要求操作系统为您加载该库,并请求指向该库中的函数的指针。
答案 2 :(得分: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;
}
答案 3 :(得分:15)
在纯C中不可能,但是你可以用dll玩弄技巧。将您要选择的所有函数放入dll中,然后使用dlsym
(或Windows上的GetProcAddress
或系统提供的任何其他API)按名称获取函数指针,并使用该函数调用
这在某些平台上不起作用,因为它们根本没有dll,或者因为像Symbian一样,dll中的函数在运行时不能通过名称访问,只能通过数字来访问。
请注意,如果您的用户欺骗您选择的功能没有您想要拨打的电话的正确参数,那么您的程序就会出错。 C真的不是为了应对这种事情。
答案 4 :(得分:12)
#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");
}
答案 5 :(得分:2)
正如其他人所说,C没有反射机制。但是您可以通过使用动态加载的库/共享对象来实现此类行为。实际上你可以加载一个动态库,然后你可以用他们的名字调用dll / so中的函数!它不是C和OS特定的,但这就是方式。它在Linux上使用dlopen,在Windows上使用LoadLibrary。您可以找到为您工作的库,如gtk/glib。
答案 6 :(得分:2)
这是你在尝试的事情:
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;
}
答案 7 :(得分:1)
如果我正确理解你的问题,你想使用后期绑定来调用C函数。这不是你通常可以在C中做的事情。符号名称(如函数名称)不存储在C编译器生成的代码中。您可以让编译器发出符号,然后使用它们来执行后期绑定,但是这种技术会因编译器而异,并且可能不值得大胆。
C#和Java等语言支持反射,可以轻松执行后期绑定。
答案 8 :(得分:1)
我刚刚在评论中使用了Steve Jessop's建议的静态链接库来尝试Williham Totland方法,结果证明这是非常重要的。
首先,你会在网上找到一堆地方(包括维基百科),它会告诉你打开你的主程序作为一个库的方法就是像这样调用dlopen(3)dlopen(NULL, 0)
。这对glibc不起作用,因为必须指定绑定标志,因为手册页明确指出:
旗帜中必须包含以下两个值之一:
RTLD_LAZY
执行延迟绑定。只解析符号作为代码...
RTLD_NOW
如果指定了此值,或环境变量...
我不认为你选择哪一个在这里很重要,因为你要将静态库中的所有符号链接到你的可执行文件中。
这将我们带到下一个问题。 The linker will not include the symbols from your static library in your executable because they're not referenced。强制GNU链接器包含符号的方法是-Wl,--whole-archive path/to/static/lib.a -Wl,--no-whole-archive
,如答案所述。强制Mac OS X链接器包含静态库中所有符号的方法是-Wl,-force_load path/to/static/lib.a
。
答案 9 :(得分:1)
我尝试了这种解决方案:使用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;
}
答案 10 :(得分:0)
C数组只能用整数类型索引。所以编写一个哈希表,将字符串映射到函数指针。
在Python,Lua和其他脚本语言中查看设施也可能值得将它们的运行时嵌入到C程序中:然后可以使用脚本语言来操作部分C代码。
Alt'ly,有些人用C编写脚本语言扩展。然后他们就可以拥有速度和速度。在脚本中对C的低级访问。
你迟早会发现使用动态类型的脚本语言习语 - 比如eval(),以及函数和函数名之间的模糊界限,以及依赖于assoc数组的代码 - 在C中是可能的,但最终痛苦。
答案 11 :(得分:0)
介绍Nginx-c函数。 它是一个NGINX模块,允许您在服务器上下文中链接.so(c / c ++)应用程序,并在location指令中调用.so application的功能。您可以通过nginx c函数(https://github.com/Taymindis/nginx-c-function/wiki/Nginx-Cache-Data-via-nginx-c-function)实现nginx共享内存数据缓存。 这适用于喜欢托管c服务器的开发人员。 https://github.com/Taymindis/nginx-c-function