feeling_lonely
feeling_lonely

Reputation: 6853

How can I create a function object in C

I would like to create a wrapper for c functions, so that I can convert a function call of the form ret = function(arg1,arg2,arg3); into the form /*void*/ function_wrapper(/*void*/);. That is similar to function objects in C++ and boost bind.

Is this possible? how can I do it?

Update:

To explain in more details what I am looking for:

We start with this function:

int f(int i){
    //do stuff
    return somevalue;
}

Obvioulsy, it is called like this:

// do stuff
int x = 0;
ret = f(0);
// do more stuff.

I would like to do some magic that will wrap the function into void function(void)

struct function_object fo;
fo.function_pointer = &f;
fo.add_arg(x, int);
fo.set_ret_pointer(&ret);
fo.call();

Note: I saw that there was a vote for closing this question and marking it as unclear. Please do not do that. I have a legitimate need to get this question answered. If you need explanation, ask and I will be glad to elaborate.

Upvotes: 2

Views: 2534

Answers (1)

IanC
IanC

Reputation: 2058

I came up with a better code that might allow you to do what you want. First I'll explain how it works, show the code and explain why I still don't think it's a good idea to use it (though the code might open doors for improvements that addresses those issues).

Functionality:

  • Before you start using the "function objects", you have to call an initialization function (FUNCTIONOBJ_initialize();), which will initialize the mutexes on every data structure used in the library.
  • After initializing, every time you want to call one of those "function objects", without using the parameters, you will have to set it up first. This is done by creating a FUNCTIONOBJ_handler_t pointer and calling get_function_handler(). This will search for a free FUNCTIONOBJ_handler data structure that can be used at the moment.
  • If none is found (all FUNCTIONOBJ_handler data structures are busy, being used by some function call) NULL is returned.
  • If get_function_handler() does find a FUNCTIONOBJ_handler data structure it will try to lock the FUNCTIONOBJ_id_holder data structure, that holds the ID of the FUNCTIONOBJ_handler of the function about to be called.
  • If FUNCTIONOBJ_id_holder is locked already, get_function_handler() will hang until it's unlocked by the thread using it.
  • Once FUNCTIONOBJ_id_holder is locked, the ID of the grabbed FUNCTIONOBJ_handler is wrote on it and the FUNCTIONOBJ_handler pointer is returned by get_function_handler.
  • With the pointer in hand, the user can set the pointer to the arguments and the return variable with set_args_pointer and set_return_pointer, which both take a void * as arguments.
  • Finally, you can call the function you want. It has to:

    1 - Grab the FUNCTIONOBJ_handler ID from the FUNCTIONOBJ_id_holder data structure and use it to get a pointer to the FUNCTIONOBJ_handler itself.

    2 - Use the FUNCTIONOBJ_handler to access the arguments.

    3 - Return by using one of the return function (on the example we have ret_int, which will return an integer and unlock the FUNCTIONOBJ_handler)

Below is a simplified mind map describing a bit of what is going on:

Mind map

Finally, the code:

funcobj.h:

#include <stdio.h>
#include <pthread.h>

#define MAX_SIMULTANEOUS_CALLS 1024

typedef struct {
    //Current ID about to be called
    int current_id;

    //Mutex
    pthread_mutex_t id_holder_mutex;
} FUNCTIONOBJ_id_holder_t; 

typedef struct {
    //Attributes
    void *arguments;
    void *return_pointer;

    //Mutex
    pthread_mutex_t handler_mutex;
} FUNCTIONOBJ_handler_t;

FUNCTIONOBJ_handler_t FUNCTIONOBJ_handler[MAX_SIMULTANEOUS_CALLS];
FUNCTIONOBJ_id_holder_t FUNCTIONOBJ_id_holder;

void set_return_pointer(FUNCTIONOBJ_handler_t *this, void *pointer);
void set_args_pointer(FUNCTIONOBJ_handler_t *this, void *pointer);
void ret_int(FUNCTIONOBJ_handler_t *this, int return_value);
void FUNCTIONOBJ_initialize(void);
FUNCTIONOBJ_handler_t *get_function_handler(void);

funcobj.c:

#include "funcobj.h"

void set_return_pointer(FUNCTIONOBJ_handler_t *this, void *pointer){
    this->return_pointer = pointer;
}

void set_args_pointer(FUNCTIONOBJ_handler_t *this, void *pointer){
    this->arguments = pointer;
}

void ret_int(FUNCTIONOBJ_handler_t *this, int return_value){
    if(this->return_pointer){
        *((int *) (this->return_pointer)) = return_value;
    }
    pthread_mutex_unlock(&(this->handler_mutex));
}

void FUNCTIONOBJ_initialize(void){
    for(int i = 0; i < MAX_SIMULTANEOUS_CALLS; ++i){
        pthread_mutex_init(&FUNCTIONOBJ_handler[i].handler_mutex, NULL);
    }

    pthread_mutex_init(&FUNCTIONOBJ_id_holder.id_holder_mutex, NULL);
}

FUNCTIONOBJ_handler_t *get_function_handler(void){
    int i = 0;

    while((0 != pthread_mutex_trylock(&FUNCTIONOBJ_handler[i].handler_mutex)) && (i < MAX_SIMULTANEOUS_CALLS)){
        ++i;
    }
    if(i >= MAX_SIMULTANEOUS_CALLS){
        return NULL;
    }

    //Sets the ID holder to hold this ID until the function is called
    pthread_mutex_lock(&FUNCTIONOBJ_id_holder.id_holder_mutex);
    FUNCTIONOBJ_id_holder.current_id = i;

    return &FUNCTIONOBJ_handler[i];
}

main.c:

#include "funcobj.h"

#include <string.h>

//Function:
void print(void){
    //First the function must grab the handler that contains all its attributes:
    //The FUNCTIONOBJ_id_holder is mutex locked, so we can just access its value and
    //then free the lock:
    FUNCTIONOBJ_handler_t *this = &FUNCTIONOBJ_handler[FUNCTIONOBJ_id_holder.current_id];
    //We dont need the id_holder anymore, free it!
    pthread_mutex_unlock(&FUNCTIONOBJ_id_holder.id_holder_mutex);

    //Do whatever the function has to do
    printf("%s\n", (char *) this->arguments);

    //Return the value to the pointed variable using the function that returns an int
    ret_int(this, 0);
}

void *thread_entry_point(void *data){
    int id = (int) data;

    char string[100];
    snprintf(string, 100, "Thread %u", id);

    int return_val;

    FUNCTIONOBJ_handler_t *this;

    for(int i = 0; i < 200; ++i){
        do {
            this = get_function_handler();
        } while(NULL == this);

        set_args_pointer(this, string);
        set_return_pointer(this, &return_val);
        print();
    }

    return NULL;
}

int main(int argc, char **argv){
    //Initialize global data strucutres (set up mutexes)
    FUNCTIONOBJ_initialize();

    //testing with 20 threads
    pthread_t thread_id[20];

    for(int i = 0; i < 20; ++i){
        pthread_create(&thread_id[i], NULL, &thread_entry_point, (void *) i);
    }

    for(int i = 0; i < 20; ++i){
        pthread_join(thread_id[i], NULL);
    }

    return 0;
}

To compile: gcc -o program main.c funcobj.c -lpthread

Reasons to avoid it:

  • By using this, you are limiting the number of "function objects" that can be running simultaneously. That's because we need to use global data structures to hold the information required by the functions (arguments and return pointer).
  • You will be seriously slowing down the program when using multiple threads if those use "function objects" frequently: Even though many functions can run at the same time, only a single function object can be set up at a time. So at least for that fraction of time it takes for the program to set up the function and actually call it, all other threads trying to run a function will be hanging waiting the the data structure to be unlocked.
  • You still have to write some non-intuitive code at the beginning and end of each function you want to work without arguments (grabbing the FUNCTIONOBJ_handler structure, unlocking the FUNCTIONOBJ_id_holder structure, accessing arguments through the pointer you grabbed and returning values with non-built-in functions). This increases the chances of bugs drastically if care is not taken, specially some nasty ones:
  • Increases the chances of deadlocks. If you forget to unlock one of the data structures in any point of your code, you might end up with a program that works fine at some moments, but randomly freeze completely at others (because all function calls without arguments will be hanging waiting for the lock to be freed). That is a risk that happens on multithreaded programs anyways, but by using this you are increasing the amount of code that requires locks unnecessarily (for style purposes).
  • Complicates the use of recursive functions: Every time you call the function object you'll have to go through the set up phrase (even when inside another function object). Also, if you call the recursive function enough times to fill all FUNCTIONOBJ_handler structures the program will deadlock.
  • Amongst other reasons I might not notice at the moment :p

Upvotes: 3

Related Questions