App2015
App2015

Reputation: 993

Simulating Interfaces, Overriding Methods, Polymorphism and calling the overridden super method in C

A static library that can receive any struct via void * and is able to access it's member variables whether an embedded struct or a pointer to a function.

It's guaranteed that a member variable which is an embedded struct will be there and is accessible via super and a member variable which is a pointer to a function will be available called execute.

Question - (in short how to access underlying struct members using void * only)

I guess it's related to generic programming, can we somewhow access the following via memory addresses or any other alternate way. casting to specific structure is not available at compile time struct will sure have an execute pointer function and super variable. Guaranteed.

// from src/library.c (someStruct a void *, casting to specific struct is not available)
someStruct->execute();
someStruct->super->execute();

Attempt 1 Reference

struct Super *pointer = someStruct + 8; // works
pointer->execute(); // prints overridden method!

// how to access second member which is a pointer to function

Open to any form of solutions whether it's going low level to memory addresses to access each member or any other alternate way.

Project Download


Expected Output:

overridden method!

super method


src/library.h

#ifndef POLY_LIBRARY_H
#define POLY_LIBRARY_H

struct Library {
    void (*passSomeStruct)(void *someStruct);
};

struct Library *newLibrary();

#endif

src/library.c

#include "library.h"
#include <stdlib.h>

void passSomeStruct(void *someStruct) {

    // can we somewhow access the following via memory addresses or any other alternate way.
    // casting to specific structure is not available at compile time
    // struct will sure have an execute pointer function and super variable
      
      someStruct->execute();
      someStruct->super->execute();
}

struct Library *newLibrary() {
    struct Library *library = malloc(sizeof(struct Library));
    library->passSomeStruct = passSomeStruct;
    return library;
}

src/super.h

#ifndef POLY_SUPER_H
#define POLY_SUPER_H

struct Super {
    void (*execute)();
};

struct Super *newSuper();

#endif //POLY_SUPER_H

src/super.c

#include "super.h"
#include <stdio.h>
#include <stdlib.h>

static void execute() {
    printf("super method\n");
}

struct Super *newSuper() {
    struct Super *super = malloc(sizeof(struct Super));
    super->execute = execute;
    return super;
}

Test

test/library_test.h

#ifndef POLY_LIBRARY_TEST_H
#define POLY_LIBRARY_TEST_H

#endif //POLY_LIBRARY_TEST_H

test/library_test.c

#include "../src/library.h"
#include "temp.h"

int main() {
    struct Library *library = newLibrary();
    struct Temp *temp = newTemp();
    library->passSomeStruct(temp);
    return 0;
}

test/temp.h

#ifndef TEST_SUPER_H
#define TEST_SUPER_H

#include <stdlib.h>

struct Temp {
    struct Super *super;
    void (*execute)();
};

struct Temp *newTemp();

#endif //TEST_SUPER_H

test/temp.c

#include "temp.h"
#include <stdio.h>
#include "../src/super.h"

static void execute() {
    printf("overridden method!\n");
}

struct Temp *newTemp() {
    struct Temp *temp = malloc(sizeof(struct Temp));
    temp->super = newSuper();
    temp->execute = execute;
    return temp;
}

MakeFile

VPATH := src test

all: libTest

libTest: super.o library.o
    mkdir -p bin
    ar rcs bin/libTest $^

test: library_test
    ./library_test

library_test: library_test.o library.o super.o temp.o
    $(CC) -o $@ $^

%.o: %.c %.h

Upvotes: 1

Views: 42

Answers (2)

App2015
App2015

Reputation: 993

Finally I got it demystified, but I'm open to suggestions, can anyone improve this answer and make it better or explain to me what's going on in here?

It's some sort of magic number 8, that did the trick.

void passSomeStruct(void *someStruct) {
    struct Super *pointer = someStruct + 8; 
    pointer->execute(); // outputs "overridden method!"

    pointer = someStruct + 16; 
    pointer->execute(); // outputs "super method"
}

Edit

my struct definition, I'm wondering why super which is defined first is 16 offset and the method execute I defined is 8 offset, is memory allocated is in reverse and not in the order I defined them?

struct Temp {
    struct Super *super;
    void (*execute)();
};

Upvotes: 0

Pablo
Pablo

Reputation: 13580

I'm afraid that I cannot give you a perfect example of how OOP should be done in C, not of the top of my head in a couple of minutes anyway. There are other threads here in Stack Overflow that cover this is in far more detail, like this one:

Can you write object-oriented code in C?

I highly recommend that you read that thread, there are many good answers there.

I also recommend, that if you really want to use OOP in your code base, don't write it yourself, it is very complex and would be very time consuming. In that case I would recommend that you use GObject that is part of the code of the glib library. I tell you all this because in the comments you say

yes, that's the part I need to know more, I don't know much about that domain, byte layout, casting to char* etc.

and when it come to stuff like this, knowing these things is fundamental.

I'm going to give you a very brief example of what I meant in the comments about the memory layout, etc.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

#define TEND 0
#define TINT 1
#define TSTRING 2
#define TDOUBLE 3

typedef struct typeinfo {
    int type;
    size_t offset;
} TypeInfo;




typedef struct K1 {
    int a;
    int b;
    char c[32];
} K1;

void print_K1(K1 *obj)
{
    printf("K1: %d %s %d\n", obj->a, obj->c, obj->b);
}

TypeInfo *init_k1(K1 *obj, int a, int b, const char *c)
{
    TypeInfo *types = calloc(4, sizeof *types);

    obj->a = a;
    obj->b = b;
    strcpy(obj->c, c);

    // filling type info
    // we know the structure, we can provide this information
    types[0].type = TINT;
    types[0].offset = offsetof(K1, a);

    types[1].type = TINT;
    types[1].offset = offsetof(K1, b);

    types[2].type = TSTRING;
    types[2].offset = offsetof(K1, c);

    types[3].type = TEND;

    return types;
}

typedef struct K2 {
    int k;
    double o;
} K2;

void print_K2(K2 *obj)
{
    printf("K2: %d %lf\n", obj->k, obj->o);
}

TypeInfo *init_k2(K2 *obj, int k, double o)
{
    TypeInfo *types = calloc(3, sizeof *types);

    obj->k = k;
    obj->o = o;

    // filling type info
    // we know the structure, we can provide this information
    types[0].type = TINT;
    types[0].offset = offsetof(K2, k);

    types[1].type = TDOUBLE;
    types[1].offset = offsetof(K2, o);

    types[2].type = TEND;

    return types;
}



void somefunc(void *ptr, TypeInfo *types)
{
    char *base = ptr, *offset;

    while(1)
    {
        offset = base + types->offset;
        switch(types->type)
        {
            case TINT:
            {
                int *p = (int*) offset;
                *p *= -1;
                break;
            }

            case TSTRING:
            {
                char *p = offset;
                p[0] = ' ';
                break;
            }

            case TDOUBLE:
            {
                double *p = (double*) offset;
                *p *= 0.35;
            }

            case TEND:
                return;
        }

        types++;
    }
}

int main(void)
{
    K1 k1;
    K2 k2;

    TypeInfo *t1, *t2;

    t1 = init_k1(&k1, 33, -23, "Hello Peter");
    print_K1(&k1);
    somefunc(&k1, t1);
    print_K1(&k1);

    t2 = init_k2(&k2, 112, 122.12);
    print_K2(&k2);
    somefunc(&k2, t2);
    print_K2(&k2);

    free(t1);
    free(t2);

    return 0;
}

I have two struct K1 and K2 with different members, that means their memory layout is different. For K1 and K2 there are init functions that using the helper struct TypeInfo store the offsets and types of the struct members of their own class. This information is known to them, so they can construct such a table of information. offsetof returns the offset of the field member from the start of the structure type.

The function somefunc doesn't know anything about the structs, so it cannot cast the void pointer to a pointer of the structs. However it gets a list of types from the caller. This functions changes the sign for every int member, replaces the first letter with an empty character for every string and multiplies every double by 0.35. Thanks to types it can get the members by using the offset supplied by types.

The main function initializes one object of each struct, prints the original state, calls somefunc with the object and the type information and prints the objects again. In the output you can see that the values were change accordingly.

Output

K1: 33 Hello Peter -23
K1: -33  ello Peter 23
K2: 112 122.120000
K2: -112 42.742000

While this might work, I don't think that this is an elegant solution. This is a basic example but it could get very complicated when dealing with more complex structs. This shows you how you can access the contents of a struct where you only have a void pointer. But without the type information, this could not be done. For that reason I encourge you to use some library that has already implemented a typing system. GObject is very mature and widely use in GTK+ applications. The Glib and GTK+ folks have been using that for years, so I would trust their implementation over any self implemented solution.

Upvotes: 1

Related Questions