0

The solution consists of two parts, one is a static library that receives instances of struct from the user of the library. Library doesn't know what will be the type of structs, all it knows there will be two function pointers to it with a specific name.

Library Code

pre-compiled library has no way of knowing types of user structs, hence receiving via void*

void save(void *data) {
    // library will save/cache user's object
    data->registered(); // if register successful
}

void remove(void *data) {
    // library will remove the object from memory
    data->remove(); // if removed successful
}

User of the Library Code

struct Temp { // random order of fields
   void (*custom1)();
   void (*registered)();
   void (*custom2)();
   void (*remove)();
   void (*custom3)();
}

void reg() {
    printf("registered");
}

void rem() {
    printf("removed");
}

void custom1() {}
void custom2() {}
void custom3() {}

var temp = malloc(struct Temp, sizeof(struct Temp));
temp->registered = reg;
temp->remove = rem;
temp->custom1 = custom1; // some custom functions
temp->custom2 = custom2; 
temp->custom3 = custom3;


// calling library code
save(temp);
remove(temp);

Q. Is there a way for the Library to know how to iterate and go through member fields and see if there's a pointer to such function and call it available.

App2015
  • 973
  • 1
  • 7
  • 19
  • Not sure you have added enough information to this question or I'm just missing your question. Can you be more specific please? What member fields? What function should there be a pointer to? – smac89 Feb 25 '18 at 23:05
  • library is expecting passed custom structs to have a member pointer to a function called `register` and `remove` and it will call them. – App2015 Feb 25 '18 at 23:07
  • Oh I think I get it. The precompiled library does not know what the types of the objects are, so it has no way of knowing what the members are, so you're asking if there is a way to make it aware? – smac89 Feb 25 '18 at 23:07
  • the precompiled library doesn't know the types but is there a way to call those pointer functions `register` and `remove` within the library. It's via `void *` – App2015 Feb 25 '18 at 23:08
  • 1
    Given `void *data`, any attempt to use the pointer such as `data->...` will fail. `void *` pointers can not be dereferenced. You need to better explain exactly what you want to do. – Andrew Henle Feb 25 '18 at 23:11
  • `register` is a C keyword. Don't make function names with keywords. – chux - Reinstate Monica Feb 25 '18 at 23:53
  • I've updated the code – App2015 Feb 26 '18 at 00:12

4 Answers4

2

Is there a way for the Library to know how to iterate and go through member fields and see if there's a pointer to such function and call it available.

No there is not.

Your best bet is to create a structure in the library that has these members, and pass that structure instead of void*.

user253751
  • 57,427
  • 7
  • 48
  • 90
  • If I create a structure in library, then I'm limited to these two functions, I want the structure to have some additional custom function to it as you can see. and each structure may vary. – App2015 Feb 25 '18 at 23:14
  • @App2015 then the application can put the library structure inside its own structure. – user253751 Feb 25 '18 at 23:21
  • that mind numbing for me, I didn't get the putting library structure inside it's own structure, library is linked to the application code for use. – App2015 Feb 25 '18 at 23:25
1

As @immibis said, there is no way for this to work (i.e. no way for the compiler to justify compiling such code) if the compiler does not know what the types of the data being passed to the function are.

Since you wanted to pass the objects along to the library without storing information about the type of each object in the library, you can fake polymorphism in C, by doing the following:

callback.h

#ifndef _CALLBACK_H_
#define _CALLBACK_H_

typedef struct {
    void (*registered)();
    void (*removed)();
} ICallback;

#endif _CALLBACK_H_

pre_comp.h

#ifndef _PRE_COMP_H_
#define _PRE_COMP_H_

#include "callback.h"

void save(ICallback* data);
void remove(ICallback* data);

#endif /* _PRE_COMP_H_ */

precomp.c

#include <stdlib.h> /* NULL */

#include "callback.h"
#include "pre_comp.h"

void save(ICallback *data) {
    if (NULL != data && NULL != data->registered) {
        data->registered(); // if register successful
    }
}

void remove(ICallback *data) {
    if (NULL != data && NULL != data->removed) {
        data->removed(); // if removed successful
    }
}

main.c

#include <stdio.h>

#include "pre_comp.h"
#include "callback.h"

struct Temp {
    ICallback base; // has to be defined first for this to work
    void (*custom1)();
    void (*custom2)();
    void (*custom3)();
};


// calling library code

void reg() {
    puts("registered");
}

void rem() {
    puts("removed");
}


int main() {
    struct Temp data = {{reg, rem}};
    save((ICallback*)&data);
    remove((ICallback*)&data);
}

compiling

gcc pre_comp.c main.c

output

registered
removed
smac89
  • 39,374
  • 15
  • 132
  • 179
  • In order for this to work register and remove must have the same offsets into both structs. – afic Feb 25 '18 at 23:15
  • 1
    I'm trying to understand use of `NULL` in the calling library code, not familiar with the syntax, please explain this part. Please also explain same offset part. Thanks. – App2015 Feb 25 '18 at 23:18
  • @App2015, I'm assuming `remove` function does not need to use `register` and vice versa – smac89 Feb 25 '18 at 23:19
  • That's right, they don't call or use each other, it's the library that calls them – App2015 Feb 25 '18 at 23:20
  • @afic, They are not different structs. It's the same one – smac89 Feb 25 '18 at 23:20
  • @App2015, right, so I use NULL to indicate that the `NULL` field is not being used. You can ignore what I did and instead create a concret object and pass that to both functions – smac89 Feb 25 '18 at 23:22
  • @smac89 as in my question, you notice that I'm passing a temp struct that has three other custom methods, so the custom temp instance can't be limited to have these two methods (`register` and `remove`) only. – App2015 Feb 25 '18 at 23:23
  • please also add 1 or 2 custom methods to the user's struct so we don't miss the point, user is free to add any custom methods to the struct it passes to the library – App2015 Feb 25 '18 at 23:26
  • @smac89 I think I'm getting your approach here, you have created a custom object with subset fields (register and remove) and the original struct is not passed, the solution is going on in another direction, the library has to receive the original custom object via void* to cache it for later use. I've added some comments in the library part of the code. – App2015 Feb 25 '18 at 23:36
  • @App2015, that's where the problem lies. `void*` cannot be dereferenced - the library needs to know the type of the object in order to make use of the fields – smac89 Feb 25 '18 at 23:50
  • @smac89 I understand the situation, but here's the objective in other words, "Library receives and registers the user's custom object and acknowledges that it has received by calling it's method", Maybe there's a lateral approach??? – App2015 Feb 25 '18 at 23:52
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/165797/discussion-between-smac89-and-app2015). – smac89 Feb 25 '18 at 23:53
  • I'm slightly changing the library code to avoid reserved words, please review. – App2015 Feb 26 '18 at 00:13
  • @App2015 see the revised answer – smac89 Feb 26 '18 at 00:23
  • @smac89 Thanks, I wish I can offer you a drink or coffee :). This specific help around my code helped me more than the links. a small question around `ICallback base;` within Temp struct. When someone instantiates `Temp`, the `base` gets memory allocated automatically? – App2015 Feb 26 '18 at 01:05
  • @App2015 cheers mate. To answer your question, yes, the memory is allocated automatically – smac89 Feb 26 '18 at 02:01
  • Can you please point to an example where you can call the base struct method from derived struct or shall I post a question? – App2015 Feb 26 '18 at 03:53
  • @App2015, yes a new question would be good, this comment train is getting too long – smac89 Feb 26 '18 at 04:19
  • ok, can you please upvote my question, it causes some warning in me asking another question. – App2015 Feb 26 '18 at 04:26
  • thanks. Here's the question. https://stackoverflow.com/questions/48981982/calling-base-function-from-derived – App2015 Feb 26 '18 at 05:32
1

If the library has 0 information about the possible struct types, then you cannot do it. The library has to get somehow the information or the offsets.

The only way I can think of is:

  1. All register member have the same prototype
  2. Pass the offset to the function.

I created an example of this

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

// function that does not know anything about any struct
void reg(void *data, size_t offset)
{
    uintptr_t *p = (uintptr_t*) (((char*) data) + offset);

    void (*reg)() = (void(*)()) *p;

    reg();
}


struct A {
    int c;
    void (*reg)();
};

struct B {
    int b;
    int c;
    void (*reg)();
};

void reg_a()
{
    printf("reg of A\n");
}

void reg_b()
{
    printf("reg of B\n");
}

int main(void)
{
    struct A a;
    struct B b;

    a.reg = reg_a;
    b.reg = reg_b;


    reg(&a, offsetof(struct A, reg));
    reg(&b, offsetof(struct B, reg));
    return 0;
}

This prints:

$ ./c 
reg of A
reg of B

I run it with valgrind and I did not get any errors nor warnings. I'm not sure if this violates somehow strict aliasing rules or yields undefined behaviour because of the uintptr_t* conversions, but at least it seems to work.

I think however, the more cleaner solution is to rewrite the register (btw. register is a keyword in C, you cannot use that for a function name) function to accept a function pointer and possible parameters, something like this:

#include <stdio.h>
#include <stdarg.h>

void reg(void (*func)(va_list), int dummy, ...)
{
    if(func == NULL)
        return;

    va_list ap;
    va_start(ap, dummy);
    func(ap);
    va_end(ap);
}


void reg1(int a, int b)
{
    printf("reg1, a=%d, b=%d\n", a, b);
}

void vreg1(va_list ap)
{
    int a = va_arg(ap, int);
    int b = va_arg(ap, int);
    reg1(a, b);
}

void reg2(const char *text)
{
    printf("reg2, %s\n", text);
}

void vreg2(va_list ap)
{
    const char *text = va_arg(ap, const char*);
    reg2(text);
}

int main(void)
{
    reg(vreg1, 0, 3, 4);
    reg(vreg2, 0, "Hello world");
    return 0;
}

This has the output:

reg1, a=3, b=4
reg2, Hello world

Note that reg has a dummy parameter. I do that because the man page of stdarg says:

man stdarg

va_start():

[...]

Because the address of this argument may be used in the va_start() macro, it should not be declared as a register variable, or as a function or an array type.

Pablo
  • 13,271
  • 4
  • 39
  • 59
0

You can take an approach similar to qsort and pass function pointers in addition to a void pointer to the structure.

Here is the function prototype for qsort, which is a function that can be used to sort arrays of any type:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

It takes a function pointer that performs the comparison because without it qsort wouldn't know how to compare two objects.

This can be applied to your task with a function prototype like this:

int DoFoo(void *thing, void (*register)(void *), void (*remove)(void *))

This function takes a void pointer to your struct and then two functions that it can call when it needs to register or remove that struct. Having the functions be members of the struct is not required and I generally do not recommend it. I recommend reading up on qsort because it is does something similar to what you are trying to do.

afic
  • 500
  • 4
  • 13