DrBeco
DrBeco

Reputation: 11785

Treat functions by name

Suppose you created a main() to deal with an exercise you asked your students.

Every student is supposed to write their own function, with the same API. And a single file will be created, with all functions and the main calling them.

Lets say: int studentname(int a, int b) is the function pattern.

One way I deal with it was using a vector of pointer to functions int (*func[MAX])(). But you need to fulfill the vector one by one func[0]=studentname;.

I wonder, is there a way a function can be called by its name somehow?

Something like: int student1(int a , int b), student2(), etc.

And in main somehow we could just call sscanf(funcname,"student%d",i); funcname();.

Do you have any other idea? Maybe

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

Anything creative will do! :)

Thanks! Beco

PS. I tried just a vector of functions, but C won't allow me! :)

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

This way I could just give to each student a number, and voilá... But no way. It was funny though.


Edited: I'm using linux.

Edited 2: Thanks! I've accepted an answer that helped me, but I've also documented a complete example as an answer bellow.

Upvotes: 0

Views: 644

Answers (7)

Code Painters
Code Painters

Reputation: 7275

Similar to what @Jamey-Sharp proposed:

  • ask each student to provide .c file with entry function of a given name/signature
  • compile each .c into a shared library, named by the student name, or given whatever unique name. This step can be easily automated with make or simple script.
  • make a simple host application which enumerates all .so files in a given directory, and uses dlopen() and dlsym() to get to the entry point function.
  • now you can simply call each student's implementation.

BTW, that's how plug-ins are implemented usually, isn't it?

Edit: Here's a working proof of concept (and a proof, that each student can use the same name of the entry point function).

Here's student1.c:

#include <stdio.h>

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

Here's student2.c:

#include <stdio.h>

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

And here's the main program, 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;
}

Let's compile it all:

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 

And see it works:

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

Upvotes: 2

DrBeco
DrBeco

Reputation: 11785

Thanks you all. I've accepted an answer that gave me the inspiration to solve the question. Here, just to document it, is my complete solution:

File 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;
}

File 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;
}

Upvotes: 1

wildplasser
wildplasser

Reputation: 44250

Here is an ugly preprocessor hack:

#Makefile

FILE_NAME=student

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

Teacher's 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;
}

Student's .c File:

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

The trick lies i the literal inclusion of the student's .c file.

To avoid this, you could also use a different make line, like:

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

(and remove the ugly #include FILENAME, of course)

Upvotes: 1

Mario
Mario

Reputation: 36487

Maybe a bit overcomplicating it, but spontaneous idea:

  • Compile all student source files into one shared library with the students' functions being exports.
  • Then enumerate all exposed functions, call and test them.

As an alternative:

  • Write a small tool that will compile all "student units" using a preprocessor define to replace a predefined function name with an unique name ("func1", "func2", etc.).
  • Then let the tool write a small unit calling all these functions while performing tests, etc.

And yet another idea:

  • Use C++ to write a special class template that's going to register derived classes in a object factory and just embed student's code using extern "C". Depending on the implementation this might look a bit confusing and overcomplicated though.
  • Then use the factory to create one instance of each and run the code.

Example for the approach with dlopen() and dlsym() (whether only one function per library or all - doesn't matter):

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)

Upvotes: 2

Jamey Sharp
Jamey Sharp

Reputation: 8491

I'd take a different approach:

  1. Require every student to use the same function name, and place each student's code in a separate source file.
  2. Write one more source file with a main that calls the standard name.
  3. Produce a separate executable from linking main.c with student1.c, then main.c with student2.c, and so on. You might be able to use wildcards in a makefile or shell script to automate this.

That said, at least on Unix-like OSes, you can do what you asked for.

  1. Call dlopen(NULL) to get a handle on the symbols in the main program.
  2. Pass that handle and the function name you want to dlsym. Coerce the resulting pointer to a function pointer of the right type, and call it.

Upvotes: 1

danfuzz
danfuzz

Reputation: 4353

Per @william-morris's suggestion, you might have luck using dlsym() to do a dynamic lookup of the functions. (dlsym() may or may not be the library call to use on your particular platform.)

Upvotes: 0

William Morris
William Morris

Reputation: 3684

It is a while since I have used shared libraries, but I have a feeling you can extract named functions from a DLL/shlib. Could you create a DLL/shared library containing all of the implementations and then access them by name from the main?

Upvotes: 0

Related Questions