6

まず第一に、関数ポインターと文字列またはその他のルックアップを使用してディスパッチ テーブルを実装する方法を理解しています。それは難しいことではありません。

私が探しているのは、コンパイル時にこのテーブルにエントリを動的に追加する方法です。

私が望むコード構造のタイプは次のようなものです。

Strategy.h - ディスパッチャの関数定義とディスパッチ テーブル定義が含まれています Strategy.c - ディスパッチャのコードが含まれています

MyFirstStrategy.c - Strategy.h を含み、戦略の 1 つの実装を提供します MyOtherStrategy.c - Strategy.h を含み、戦略の 2 番目の実装を提供します

関数ポインターとストラテジー名をディスパッチ テーブルに挿入するコードは、Strategy.c ではなく、個々のストラテジー実装ファイルに配置する必要があり、ルックアップ テーブルはコンパイル時に何らかの方法で動的に構築する必要があります。

固定サイズのディスパッチ テーブルの場合、これは以下のように管理できますが、動的なサイズのテーブルが必要です。Strategy.c 実装に実装のすべてのヘッダー ファイルを含める必要はなく、ディスパッチが必要ですテーブルは、実行時ではなくコンパイル時に構築されます。

固定サイズの例

Strategy.h

typedef void strategy_fn_t(int);
typedef struct {
    char           *strategyName;
    strategy_fn_t  *implementation;
} dispatchTableEntry_t;

MyFirstStrategy.h

#include "Strategy.h"

void firstStrategy( int param );

MyOtherStrategy.h

#include "Strategy.h"

void otherStrategy( int param );

Strategy.c

#include "Strategy.h"
#include "MyFirstStrategy.h"
#include "MyOtherStrategy.h"

dispatchTableEntry_t dispatchTable[] = {
    { "First Strategy", firstStrategy },
    { "Other Strategy", otherStrategy }
};
int numStrategies = sizeof( dispatchTable ) / sizeof(dispatchTable[0] );

私が本当に欲しいのは、これを自動的に処理するために戦略実装ファイルに挿入できるプリプロセッサ マジックです。

MyFirstStrategy.c

#include "Strategy.h"

void firstStrategy( int param );

ADD_TO_DISPATCH_TABLE( "First Strategy", firstStrategy );

何かご意見は ?

4

3 に答える 3

4

gnuリンカーとコンパイラーまたは互換性のあるものを備えたシステムでは、特定のオブジェクトを異なるセクションに配置することが可能です。リンカは、セクションの開始と終了のシンボルを生成します。これを使用すると、すべての構造体をさまざまなオブジェクトのそのセクションに配置できます。リンカは、リンク時にそれらのセクションを統合し、配列としてそれらにアクセスできます。共有ライブラリでこれを行う場合、これにはさらに多くの手間がかかり、ELF Linux /*BSDの外部では間違いなく移植性がありません。

私はMacOSとa.outBSDで同様のことをしましたが(この例は機能しませんが)、そのコードを失いました。Linuxで機能する方法の例を次に示します。

$ cat link_set.c
#include <stdio.h>

struct dispatch_table {
    const char *name;
    void (*fun)(int);
};

#define ADD_TO_DISPATCH_TABLE(name, fun) \
    static const struct dispatch_table entry_##fun \
        __attribute__((__section__("link_set_dispatch_table"))) = \
        { name, fun }

int
main(int argc, char **argv)
{
    extern struct dispatch_table __start_link_set_dispatch_table;
    extern struct dispatch_table __stop_link_set_dispatch_table;
    struct dispatch_table *dt;

    for (dt = &__start_link_set_dispatch_table; dt != &__stop_link_set_dispatch_table; dt++) {
        printf("name: %s\n", dt->name);
        (*dt->fun)(0);
    }
    return 0;
}

void
fun1(int x)
{
    printf("fun1 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 1", fun1);

void
fun2(int x)
{
    printf("fun2 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 2", fun2);
$ cc -o link_set link_set.c
$ ./link_set
name: fun 1
fun1 called
name: fun 2
fun2 called
$

マクロの機能を説明します。1つのオブジェクトで複数回使用したい場合があるため(同じ関数を複数回使用する場合は、構造体に名前を付ける別の方法を見つけてください)、一意であることが望ましい名前で構造体ディスパッチテーブルを作成し、オブジェクトがどのセクションで終わるかを指定するgnu拡張。オブジェクトを「some_section_name」に入れると、リンカは自動的にシンボル__start_some_section_nameと__end_some_section_nameを追加します。次に、それらのシンボル間を歩き、そのセクションに配置したすべての構造体を取得できます。

MacOSの実用的な例で更新:

#include <stdio.h>
#include <mach-o/ldsyms.h>
#include <mach-o/getsect.h>
#include <mach-o/loader.h>

struct dispatch_table {
        const char *name;
        void (*fun)(int);
};

#define ADD_TO_DISPATCH_TABLE(name, fun) \
    static const struct dispatch_table entry_##fun \
    __attribute__((__section__("__DATA,set_dt"))) = \
    { name, fun }

int
main(int argc, char **argv)
{
        struct dispatch_table *start;
        unsigned long sz;
        intptr_t s;
        int i;

        s = (intptr_t)getsectdata("__DATA", "set_dt", &sz);
        if (s == 0)
                return 1;
        s += _dyld_get_image_vmaddr_slide(0);
        start = (struct dispatch_table *)s;
        sz /= sizeof(*start);

        for (i = 0; i < sz; i++) {
                struct dispatch_table *dt = &start[i];
                printf("name: %s\n", dt->name);
                (*dt->fun)(0);
        }
        return 0;
}

void
fun1(int x)
{
        printf("fun1 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 1", fun1);

void
fun2(int x)
{
        printf("fun2 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 2", fun2);

この実行可能形式ではセクション名の長さが制限されているため、ここではセクションを「set_dt」と呼ぶ必要があります。

もちろん、これが必要なところまで来たら、これはすべて非常に危険で、移植性がなく、これまで動作することが保証されていないことを確実に理解します(3年前のコードは現在のバージョンのmacosでは動作しませんでした) 、タイプやその他の安全性はなく、他の何かが同じ名前のセクションに物を入れることにした場合、物は非常にきれいな花火になります。しかし、それは非常に巧妙なトリックです。私はこの方法を2つの大きなプロジェクトで使用しており、多くの作業を節約できます。中央のディスパッチテーブルを、全員に競合を与えるために使用されていた共有リポジトリで編集する必要がありません。

于 2012-08-07T10:55:59.703 に答える
1

Strategy.c は名前 ("#include "XYstrategy.h") で戦略インスタンスについて明らかに既に知っているので、実装ファイルの代わりにヘッダー ファイルを使用して、中央のディスパッチャに戦略を伝えることができます。

MyFirstStrategy.h

#include "Strategy.h"
void firstStrategy( int param );
#define MY_FIRST_STRATEGY {"First Strategy", firstStrategy}

MyOtherStrategy.h

#include "Strategy.h"
void otherStrategy( int param );
#define MY_OTHER_STRATEGY {"Other Strategy", otherStrategy }

Strategy.c

#include "Strategy.h"
#include "MyFirstStrategy.h"
#include "MyOtherStrategy.h"

dispatchTableEntry_t dispatchTable[] = {
    MY_FIRST_STRATEGY,
    MY_OTHER_STRATEGY
};
int numStrategies = sizeof( dispatchTable ) / sizeof(dispatchTable[0] );

これは、リンク時のトリックなしで、どの C コンパイラおよびプラットフォームでも機能するはずです。

于 2012-08-09T11:48:35.313 に答える
1

function-pointer-having-structs のリンクされたリストでそれを行うことができるはずです:

struct dispatch_entry {
    const char *name;
    void (*func)(int);
    struct dispatch_entry *next;
};

struct dispatch_entry *dispatch_head = NULL;

#define ADD_TO_DISPATCH_TABLE(entry) do { \
    (entry)->next = dispatch_head; \
    dispatch_head = entry; \
} while (0)

次に、リストをたどって必要なエントリを見つけるか、後で並べ替えたり、実行時に最適化されたルックアップ ルーチンなどに配置したりできます。これには、dispatch_entry 構造体をマクロの外でインスタンス化する必要があることに注意してください。ただし、それは大きな問題ではないと思います。

いつものように、私はこのコードをコンパイル/実行しておらず、技術を説明するためだけに打ち抜いたので、emptor に注意してください (さまざまな作業プロジェクトで数回使用しました)。

于 2012-08-07T07:07:27.827 に答える