Reputation: 993
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.
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
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
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