C语言中使用虚函数表实现动态调度

7

我希望您能提供一个示例(最好是好的示例)来实现C语言中的动态分派。

我正在学习C语言,作为练习,我想使用动态分派虚拟方法表将Java代码翻译成C语言。

例如,我有一段Java代码:

abstract class Foo {
    public abstract int val(); 
    public abstract Boolean error();
}

class Fail extends Foo {
    public int val(){ return 0;}
    public Boolean error(){return true;}
}

class IntFoo extends Foo {
    int v;
    public IntFoo(int value){this.value=v;}
    public int val(){ return v;}
    public Boolean error(){return False;}
}

我可以翻译一些基础内容,例如:

typedef struct Foo{
    void(**vtable);
}Foo;

typedef struct Fail{
    void(**vtable);
    struct Foo inherited;
}Fail;

typedef struct IntFoo{
    void(**vtable);
    struct Foo inherited;
}IntFoo;

我在尝试完成这个项目时遇到了困难,因为我不知道:

  1. 如何在 C 语言中定义这些方法。
  2. 设置这些方法的地址在 vtable 中,以便编译器能够识别要调用的正确方法。
  3. 需要定义什么其他内容才能使其正常工作。

与其试图将一个圆形钉子塞进一个方形洞里——试图在非面向对象语言上强制使用面向对象结构,我会选择使用C++而不是C。 - Hovercraft Full Of Eels
3
@HovercraftFullOfEels 我想只用C语言来完成,因为我的教授要求我这样做。 - Solix
1
这类东西的源头是由Axel Tobias Schreiner所写的ooc.pdf - luser droog
1
@luserdoog,今天稍晚我从Stack Overflow的一个帖子中得到了这个PDF文件,但是我找不到我要找的具体部分。你知道哪一章讨论了这个吗? - Solix
我不知道!我从来没有过第二章。我只是假设了。对不起。 - luser droog
2个回答

6

没有简单的方法来完成这个任务。不过,您可以手动执行C++编译器正在执行的操作。在您的情况下,这将是这样的:

typedef struct vtable_Fail
{
    int  (*val)(struct Fail *this_pointer);
    bool (*error)(struct Fail *this_pointer);
} vtable_Fail;

typedef struct vtable_IntFoo
{
    int  (*val)(struct IntFoo *this_pointer);
    bool (*error)(struct IntFoo *this_pointer);
} vtable_IntFoo;

int Fail_val(struct Fail *this_pointer)
{
    return 0;
}

...

void IntFoo_ctor(struct IntFoo *this_pointer, int value)
{
     this_pointer->v = value;
}

int IntFoo_val(struct IntFoo *this_pointer)
{
    return this_pointer->v;
}

...

struct Fail
{
    vtable_Fail *vtable;
};

struct IntFoo
{
    vtable_IntFoo *vtable;
    int v;
};

每个结构体都应该有一个伴随的vtable结构体,它存储这个结构体实现的所有虚方法的指针。每个vtable结构体只有一个实例。它们应该驻留在静态内存中,并且应该使用函数指针进行初始化。这些指针应该以每个特定结构所需的方式实现虚方法。结构体本身可以有许多实例。实例中的vtable数据字段应该指向其类的静态vtable结构体。
参数“this_pointer”传递实例的地址。它应该手动传递。这毕竟是C语言。
请注意,您需要分配vtable结构、初始化vtable指针等。您需要使用自己的代码手动完成所有这些操作。

3
我建议的方法是查看一些使用调度表的C代码,并了解其结构。我能想到的具体示例(可能不是最好的教学示例,因为它们只是我曾经使用过的随机免费软件)是MIT Kerberos和Heimdal,这两个软件都在很多地方使用调度表,以及Apache Web服务器和它如何处理动态加载模块。这些示例都没有使用继承,但是添加继承相对简单:您可以查看要调用的方法是否为NULL,如果是,则检查该方法的父调度表。
一般来说,处理C中的调度表的简短版本是定义类似于C++虚函数表的数据结构,其中包含函数指针和某种找出要使用哪个指针的方法。例如:
typedef void (*dispatch_func)(void *);
struct dispatch {
    const char *command;
    dispatch_func callback;
};

这是一种超级通用的版本,它将字符串方法名称映射到以单个匿名指针作为参数的函数。您实际的调度表将是一个由这些数组组成的数组,就像这样(从INN源代码中修改的示例):

const struct dispatch commands[] = {
    { "help",  command_help },
    { "ihave", command_ihave },
    { "quit",  command_quit }
};

显然,您对函数的了解越多,就可以使原型更具体,并将其他选择标准(如参数数量)嵌入调度表中。(实际INN源代码具有最小和最大参数计数,传递一个结构化参数而不仅仅是void *,并在调度表中包括命令描述。)
搜索调度表的基本代码非常简单。假设您有一个vtable中带有调度结构条目的数组和表的长度length,类似于:
for (i = 0; i < length; i++)
    if (strcmp(command, vtable[i].command) == 0) {
        (*vtable[i].callback)(data);
        return;
    }

为了实现继承,您需要父指针,如果您在循环结尾掉出来,就需要移动到父级并重复逻辑。 (显然,这可能是递归函数的有用位置。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接