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. 还有什么要定义以使其工作。
4

2 回答 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 指针等。您需要使用自己的代码手动完成所有这些操作。

于 2013-03-31T20:23:50.153 回答
3

我推荐的方法是查看其他一些使用调度表的 C 代码并了解它的结构。我突然意识到的具体示例(也许不是最好的教学示例,因为它们只是我研究过的随机免费软件)是 MIT Kerberos 和 Heimdal,它们都使用调度表地点和 Apache Web 服务器以及它如何处理动态加载的模块。这些都没有继承,但继承相对容易添加:您查看您尝试调用的方法是否为 NULL,如果是,则检查该方法的父调度表。

如何在 C 中处理调度表的简短版本通常是定义一个类似于 C++ vtable 的数据结构,它包含函数指针和某种确定要使用哪个指针的方法。就像是:

typedef void (*dispatch_func)(void *);
struct dispatch {
    const char *command;
    dispatch_func callback;
};

是超通用版本,它将字符串方法名称映射到将单个匿名指针作为参数的函数。您的实际调度表将是这些的数组,如下所示(取自 INN 源中的 tinyleaf 的修改示例):

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

显然,你对函数了解得越多,你就能做出越具体的原型,你可以在调度表中嵌入其他的选择标准(比如参数的数量)。(实际的 INN 源具有最小和最大参数计数,传入参数结构而不仅仅是 void *,并在调度表中包含命令描述。)

搜索调度表的基本代码非常简单。假设您有一个包含这些调度结构条目的数组以及 invtable中的表的长度length,例如:

for (i = 0; i < length; i++)
    if (strcmp(command, vtable[i].command) == 0) {
        (*vtable[i].callback)(data);
        return;
    }

要实现继承,您需要父指针,如果您离开循环的末尾,则向上移动到父指针并重复逻辑。(显然,这对于递归函数来说可能是一个有用的地方。)

于 2013-03-31T20:21:11.773 回答