【问题标题】:Treat functions by name按名称处理函数
【发布时间】:2012-11-08 00:00:21
【问题描述】:

假设您创建了一个main() 来处理您向学生提出的练习。

每个学生都应该使用相同的 API 编写自己的函数。并且将创建一个文件,其中包含所有函数和 main 调用它们。

假设:int studentname(int a, int b) 是函数模式。

我处理它的一种方法是使用指向函数int (*func[MAX])() 的指针向量。但是你需要将向量一一实现func[0]=studentname;

我想知道,有没有一种方法可以通过它的名字来调用一个函数?

类似:int student1(int a , int b)student2()

main 中,我们可以调用sscanf(funcname,"student%d",i); funcname();

你还有什么想法吗?也许

int studentname(int a, int b, char *fname)
{
    strcpy(fname, "studentname");

任何创意都可以! :)

谢谢! 贝科

PS。我只尝试了一个函数向量,但 C 不允许我这样做! :)

int func[2]()={{;},{;}};

这样我可以给每个学生一个数字,然后瞧……但没办法。不过这很有趣。


已编辑:我正在使用 linux。

编辑2:谢谢!我接受了一个对我有帮助的答案,但我还在下面记录了一个完整的示例作为答案。

【问题讨论】:

  • 生成代码?一些脚本来读取您的姓名列表,转换为正确的格式(例如,所有小写字母、所有空格和特殊字符都已删除),然后转储调用。
  • 一个最小的例子将不胜感激。
  • linux.die.net/man/3/dlopen 余弦示例:` *(void **) (&cosine) = dlsym(handle, "cos");` 我认为这是调用,不是吗?跨度>
  • 我突然想到:学生函数的结果是什么?也许最好将每个学生的代码编译成可执行文件,将结果打印到标准输出,并制作一个脚本(无论哪种语言),对每个二进制文件进行分叉/执行,解析输出并验证/比较它?它有很多优点(分离、可能永远终止进程循环等)。
  • 我已经在很多不同结果的练习中教授和使用这个模式。特别是对于当前的练习,它是一系列选择,每个函数都应该返回一个表示选择的 int。组合的选择导致了一条可能性的道路,必须做出新的选择。 :) 因此,作为一个简单的 int,获取数字比从标准输出解析答案更好。但我同意,对于其他练习,这可能是一个不错的优雅解决方案!

标签: c linux function function-pointers


【解决方案1】:

也许有点过于复杂,但自发的想法:

  • 将所有学生源文件编译到一个共享库中,并导出学生函数。
  • 然后枚举所有公开的函数,调用并测试它们。

作为替代方案:

  • 编写一个小工具,使用预处理器定义编译所有“学生单元”,将预定义的函数名称替换为唯一名称(“func1”、“func2”等)。
  • 然后让工具编写一个小单元,在执行测试等时调用所有这些函数。

还有一个想法:

  • 使用 C++ 编写一个特殊的类模板,它将在对象工厂中注册派生类,并使用 extern "C" 嵌入学生的代码。不过,根据实现的不同,这可能看起来有点混乱和过于复杂。
  • 然后使用工厂创建每个实例并运行代码。

dlopen()dlsym() 的方法示例(每个库只有一个函数还是全部 - 无关紧要):

void *pluginlib = dlopen("student1.so", RTLD_NOW); // RTLD_NOW will load the file right away
if (!pluginlib)
    ; // failed to load
studentproc func = (studentproc)dlsym(pluginlib, "student1"); // this loads the function called "student1"
if (!func)
    ; // failed to resolve
func("hello world!"); // call the lib
dlclose(pluginlib); // unloads the dll (this will make all further calls invalid)

【讨论】:

  • 在发布问题之前,我正在考虑预处理器。但我看不到出路。
  • 它不仅仅是预处理器。只是一个使用预处理器快速重命名/编辑而不实际接触文件的工具。
  • 第 2 项你会怎么做:枚举暴露的函数并调用它们?我认为这是我的主要疑问,如果可能的话,这似乎是一个很好的解决方案。 dlopen 和 dlsym 是否也是其他人建议的?
  • dlopen()/dlsym() 添加了一个简短的示例。我不太确定如何枚举一个文件中的所有函数,抱歉。 :)
  • 谢谢!很有用。我将添加一个最小示例来展示解决方案。但你应该得到公认的答案!
【解决方案2】:

类似于@Jamey-Sharp 的提议:

  • 要求每个学生提供.c文件,带有给定姓名/签名的输入功能
  • 将每个.c 编译成一个共享库,以学生姓名命名,或指定任何唯一名称。使用make 或简单的脚本可以轻松地自动执行此步骤。
  • 制作一个简单的主机应用程序,它枚举给定目录中的所有.so 文件,并使用dlopen()dlsym() 来访问入口点函数。
  • 现在您可以简单地调用每个学生的实现。

顺便说一句,插件通常是这样实现的,不是吗?

编辑:这是一个有效的概念证明(以及一个证明,每个学生都可以使用相同名称的入口点函数)。

这里是student1.c

#include <stdio.h>

void student_task()
{
    printf("Hello, I'm Student #1\n");    
}

这里是student2.c

#include <stdio.h>

void student_task()
{
    printf("Hello, I'm Student #2\n");    
}

这是主程序,tester.c

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

/* NOTE: Error handling intentionally skipped for brevity! 
 * It's not a production code!
 */

/* Type of the entry point function implemented by students */
typedef void (*entry_point_t)(void);

/* For each student we have to store... */
typedef struct student_lib_tag {
    /* .. pointer to the entry point function, */
    entry_point_t entry;
    /* and a library handle, so we can play nice and close it eventually */ 
    void* library_handle;
} student_solution_t;

void load(const char* lib_name, student_solution_t* solution)
{
    /* Again - all error handling skipped, I only want to show the idea! */

    /* Open the library. RTLD_LOCAL is quite important, it keeps the libs separated */
    solution->library_handle = dlopen(lib_name, RTLD_NOW | RTLD_LOCAL);

    /* Now we ask for 'student_task' function. Every student uses the same name.
     * strange void** is needed for C99, see dlsym() manual.
     */
    *(void**) (&solution->entry) = dlsym(solution->library_handle, "student_task");

    /* We have to keep the library open */
}

int main()
{
    /* Two entries hardcoded - you need some code here that would scan
     * the directory for .so files, allocate array dynamically and load 
     * them all.
     */
    student_solution_t solutions[2];

    /* Load both solutions */
    load("./student1.so", &solutions[0]);
    load("./student2.so", &solutions[1]);

    /* Now we can call them both, despite the same name of the entry point function! */
    (solutions[0].entry)();
    (solutions[1].entry)();

    /* Eventually it's safe to close the libs */
    dlclose(solutions[0].library_handle);
    dlclose(solutions[1].library_handle);
    return 0;
}

让我们全部编译:

czajnik@czajnik:~/test$ gcc -shared -fPIC student1.c -o student1.so -Wall
czajnik@czajnik:~/test$ gcc -shared -fPIC student2.c -o student2.so -Wall
czajnik@czajnik:~/test$ gcc tester.c -g -O0 -o tester -ldl  -Wall 

看看效果如何:

czajnik@czajnik:~/test$ ./tester 
Hello, I'm Student #1
Hello, I'm Student #2

【讨论】:

  • 看起来 dlsym 和 dlopen 是要走的路。我只是对如何使用它们有点困惑,因为我以前从未使用过它们。如果我找不到一个完全可行的最小示例,我会阅读更多文档。
  • 这很简单。只在 Windows 上这样做,但在 Unix 上可能非常相似:使用dlopen() 加载将返回句柄的库。然后使用句柄和dlsym() 来获取给定函数的地址。只需将其转换为适当的签名即可。是的,这基本上就是插件通常的完成方式。
  • 我想说这很容易(而且很有趣!) - dlsym() 手册页包含一个简单的示例,请参阅 linux.die.net/man/3/dlsym
  • 但是我需要把名字写到dlsym()?就像funcs[i] = dlsym(handle, funcname); 一样,我使用 sscanf 将 funcname 放在一起,就像问题中一样?
  • 没关系!使用dlsym(),您可以从不同的库中导入多个同名函数。不幸的是,我昨天没有机会展示任何例子(我有时不得不睡觉;) - 但现在让我这样做。我会在几分钟后更新我的答案。
【解决方案3】:

我会采取不同的方法:

  1. 要求每个学生使用相同的函数名,并将每个学生的代码放在单独的源文件中。
  2. 再编写一个带有main 调用标准名称的源文件。
  3. 通过将main.cstudent1.c 链接,然后将main.cstudent2.c 链接生成一个单独的可执行文件,依此类推。您或许可以在 makefile 或 shell 脚本中使用通配符来自动执行此操作。

也就是说,至少在类 Unix 操作系统上,您可以按您的要求做。

  1. 调用dlopen(NULL) 以获取主程序中符号的句柄。
  2. 将该句柄和您想要的函数名传递给dlsym。将生成的指针强制指向正确类型的函数指针,然后调用它。

【讨论】:

  • 那不行,因为主要需要调用学生的组合和反对结果。
  • 嗯,这不在您的规范中。 :-)
  • 很抱歉。但我也没有说我一次只会打一个电话! :P
  • 我正在阅读有关 dlopen 和 dlsym 的信息。听起来很有希望。我从未使用过它,所以我很难弄清楚它们。
【解决方案4】:

这是一个丑陋的预处理器黑客:

#Makefile

FILE_NAME=student

${FILE_NAME}: main.c
        cc -Wall -DFILE_NAME=\"${FILE_NAME}.c\" -o $@ main.c -lm

老师的main.c:

#include <math.h>
#include <stdio.h>

#include FILE_NAME

char *my_name(void);
double my_sin(double val);

int main(void)
{
double dd;
dd = my_sin(3.1415923563);

printf("%s: %f\n", my_name(), dd);
return 0;
}

学生的 .c 文件:

#include <math.h>

char * my_name(void);
double my_sin(double val);

char * my_name(void)
{
return "Wildplasser-1.0";
}

double my_sin(double val)
{
return sin (val);
}

诀窍在于直接包含学生的 .c 文件。

为避免这种情况,您还可以使用不同的 make 行,例如:

 cc -Wall -o $@ ${FILE_NAME}.c main.c -lm

(当然要去掉丑陋的#include FILENAME

【讨论】:

  • 这不起作用,因为我可能需要一次调用多个学生的函数来热比较结果。不过谢谢!不错的收获。
  • 热比较:重定向标准输出,并使用普通的差异(或数据库)由于您提供 main() 例程,您可以随意检测它。
【解决方案5】:

谢谢大家。我已经接受了一个答案,它给了我解决问题的灵感。在这里,只是为了记录它,是我的完整解决方案:

文件 shamain.c

/* Uses shared library shalib.so
 * Compile with:
 *    gcc shamain.c -o shamain -ldl -Wall
 */


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

int main(void)
{
  void *libstud;
  int (*student[2])(int, int);
  char fname[32];
  int i,r;

  libstud = dlopen("./shalib.so", RTLD_NOW);
  if (!libstud)
  {
    fprintf(stderr, "error: %s\n", dlerror());
    exit(EXIT_FAILURE);
  }
  dlerror();    /* Clear any existing error */

  for(i=0; i<2; i++)
  {
    sprintf(fname, "func%d", i);
    *(void **) (&student[i]) = dlsym(libstud, fname); /* c99 crap */
    //student[i] = (int (*)(int, int)) dlsym(libstud, fname); /* c89 format */
  }

  for(i=0; i<2; i++)
  {
    r=student[i](i, i);
    printf("i=%d,r=%d\n", i, r);
  }

  return 0;
}

文件 shalib.c

/* Shared library.
 * Compile with:
 *  gcc -shared -fPIC shalib.c -o shalib.so -Wall
 */

#include <stdio.h>

int func0(int one, int jadv)
{
  printf("%d = Smith\n", one);
  return 0;
}

int func1(int one, int jadv)
{
  printf("%d = John\n", one);
  return 0;
}

【讨论】:

  • 啊,好吧,跳过枚举是另一个想法。 :) 只是要补充一点:不要将文件名传递给 dlopen() 允许您在可执行文件中获取函数 - 所以您甚至不需要库。
  • 我会使用libstud = dlopen(NULL, RTLD_NOW); 吗?我可以从shamain.c 获得函数吗?
  • 是的,这将使它检查可执行文件的符号表中的功能。不仅是 shamain.c,还有任何链接到可执行文件的目标文件/翻译单元。
【解决方案6】:

我已经有一段时间没有使用共享库了,但我感觉你可以从 DLL/shlib 中提取命名函数。您能否创建一个包含所有实现的 DLL/共享库,然后从主目录中按名称访问它们?

【讨论】:

  • 哦,我应该补充一下,我使用的是 linux。但我相信你的评论是平台无关的......关于它,好吧,我不知道具体如何。
【解决方案7】:

根据@william-morris 的建议,您可能有幸使用dlsym() 对函数进行动态查找。 (dlsym() 可能是也可能不是在您的特定平台上使用的库调用。)

【讨论】:

    猜你喜欢
    • 2011-10-12
    • 1970-01-01
    • 2016-08-23
    • 1970-01-01
    • 2012-11-09
    • 1970-01-01
    • 1970-01-01
    • 2019-09-10
    相关资源
    最近更新 更多