在 C 中模拟接口、重写方法、多态性和调用重写的超级方法

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

一个静态库,可以通过 void * 接收任何结构,并且能够访问它的成员变量,无论是嵌入式结构还是指向函数的指针。

保证嵌入式结构的成员变量将存在并可通过 super 访问,并且作为函数指针的成员变量将可用,称为 execute

问题 -(简而言之如何仅使用 void * 访问基础结构成员)

我想这与泛型编程有关,我们能否以某种方式通过内存地址或任何其他替代方式访问以下内容。 转换为特定结构在编译时不可用 struct 肯定会有一个执行指针函数和超级变量。保证。

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

尝试 1Reference

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

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

接受任何形式的解决方案,无论是低级别的内存地址以访问每个成员还是任何其他替代方式。

Project Download


预期输出:

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/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

我终于揭开了它的神秘面纱,但我愿意接受建议,任何人都可以改进这个答案并让它变得更好,或者向我解释这里发生了什么吗?

这是某种神奇的数字 8,它起到了作用。

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

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

编辑

我的struct定义,我想知道为什么先定义的super是16偏移量而我定义的方法execute是8偏移量,内存分配是反的而不是在我定义它们的顺序?

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

恐怕我不能给你一个完美的例子来说明 OOP 应该如何在 C,无论如何在几分钟内都不会出现在我的脑海中。还有其他 Stack Overflow 中的线程更详细地介绍了这一点,就像这样 一:

Can you write object-oriented code in C?

我强烈建议您阅读该主题,那里有很多很好的答案。

我还建议,如果您真的想在代码库中使用 OOP,请不要 自己写,很复杂,会很费时间。在那里面 如果我建议您使用 GObject 这是代码的一部分 glib 图书馆。我告诉你这一切是因为在评论中你说

yes, that's the part I need to know more, I don't know much about that domain, byte layout, casting to char* 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;
}

我有两个结构 K1K2 具有不同的成员,这意味着他们的记忆 布局不同。对于 K1K2 有使用 helper struct TypeInfo 存储结构成员的偏移量和类型 他们自己的class。他们知道此信息,因此他们可以构建 如此 table 的信息。 offsetof returns字段成员的偏移量 从结构类型开始。

函数somefunc对结构一无所知,所以它不能 将 void 指针转换为结构指针。但是它得到了一个列表 来自调用者的类型。此函数更改每个 int 成员的符号, 将每个字符串的第一个字母替换为空字符,并且 将每个 double 乘以 0.35。感谢 types 它可以通过 使用 types.

提供的偏移量

main函数初始化每个结构的一个对象,打印原始 状态,用对象和类型信息调用 somefunc 并打印 再次对象。在输出中,您可以看到值已更改 因此。

输出

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

虽然这可能有效,但我认为这不是一个优雅的解决方案。这是一个 基本示例,但在处理更复杂的问题时可能会变得非常复杂 结构。这向您展示了如何访问您所在的结构的内容 只有一个 void 指针。但是没有类型信息,这不可能 完毕。出于这个原因,我鼓励你使用一些已经 实施了打字系统。 GObject 非常成熟,广泛用于 GTK+ 应用程序。 Glib 和 GTK+ 人员多年来一直在使用它,所以我相信他们 在任何自行实施的解决方案上实施。