1

バックグラウンド:

C を学習する方法として、ゼルダ (NES) に似た単純なゲームを C で開発しようとしています。すべてのゲーム データを 1 つのファイルに格納するのは理想的ではないという結論に達しました。そこで私がやりたいのは、各「領域」を独自のモジュールに分割することです。この「エリア」モジュールは、タイル マップ、特定のイベントでトリガーする関数などのプロパティをメイン プログラムに記述し、メイン プログラムのゲーム API を呼び出してアクター/サウンド/その他を操作します。画面上。したがって、これらのモジュールは実行時にロード/アンロードする必要があります。本質的に、メイン プログラムは、この「エリア」モジュールから供給されるデータに基づいて動作するステート マシンです。

これらの共有モジュールを作成しようとしましたが、メイン プログラムで定義された関数が area モジュールから見えるようには見えません (少なくとも同じスコープではありません)。これは、リンクを間違っているためだと確信しています。だから私がやったことは、うまくいく概念の証明を考え出すことです. 以下は、失敗した私の概念実証です。

api.h

#ifndef _API_H
#define _API_H

static char *name = 0;

extern int play_sfx(int, int, int);
extern int move_actor(int, int, int);
extern int set_name(char*);
extern char* get_name(void);

#endif

エリア.c

#include "api.h"

extern int init(void)
{
    int ret = set_name("area 1");
    return ret;
}

game.c

#include <stdio.h>
#include <dlfcn.h>

#include "api.h"

int main()
{
    void *handle = dlopen("/home/eric/tmp/build_shared5/libarea.so", RTLD_LAZY);
    int (*test)(void) = dlsym(handle, "init");
    (*test)();
    dlclose(handle);
    printf("Name: %s\n", get_name());
    return 0;
}

extern int play_sfx(int id, int times, int volume)
{
    // @todo Execute API call to play sfx
    return 1;
}

extern int move_actor(int id, int x, int y)
{
    // @todo Execute API call to move actor
    return 1;
}

extern int set_name(char *p)
{
    name = p;

    return 1;
}

extern char* get_name(void)
{
    return name;
}

build.sh

#!/bin/bash
gcc -o game.o -c game.c
gcc -fPIC -o area.o -c area.c
#,--no-undefined
gcc -shared -Wl,-soname,libarea.so.1 -o libarea.so game.o area.o
gcc -o game game.o -ldl

建てる:

$ ./build.sh

プログラムは以下を生成します。

$ ./ゲーム

名前: (ヌル)

名前: エリア 1

私がやろうとしていることは可能ですか?そうでない場合は、すべての API 呼び出しを area モジュールに登録するという別のアイデアがあります... しかし、それは私にとって理想的ではありません。

マシン情報: gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3.

4

4 に答える 4

2

static char *name = 0;.hファイルにあるため、混乱が生じます。

area.cにはapp.hが含まれており、char *name静的であるため、翻訳単位でのみ表示されます。

game.cにはapp.hも含まれており、別のchar *name変数を取得します。したがって、nameそのarea.cが表示するものはgame.cが表示するものと同じではなく、それぞれの.cファイルの外部にも表示されません。nm共有ライブラリとメインの実行可能ファイルで実行して、これを確認できます。両方ともprovateシンボルを持っている必要がありnameます

game.cのファイルスコープextern char *name;の代わりにapp.hに配置し、どこかにstatic char *name;配置しますchar *name;

(共有ライブラリの動的なロードを使用するときに、それが適切に解決されるかどうかは少しわかりませんが)。

于 2010-09-22T20:17:28.300 に答える
1

あなたの最初の問題は、あなたが電話することだと思いますdlclose。これにより、実際にプラグインがアンロードされます。dlcloseを の後ろに移動してみてくださいprintf。うまくいきますか?

また、それはAFAIKtest();ではなく、ただのはずです。(*test)();

私が似たようなことをしたときは、関数ポインタを持つ構造体を使用していました。数年が経ち、これはテストされていないため、正しい方向へのポインタとして扱ってください. ヘッダー ファイルで定義された構造体が必要です。

struct plugin_methods {
   void (*foo) ();
   void (*bar) (int baz);
};

次に、各プラグインには、 でdlsym行うのとまったく同じように、 でロードされるメソッドがありましたdlsym(handle, "init");。しかし、私の場合、次のようにstruct plugin_methods、適切な関数ポインターで malloc されたものを返しました。

struct plugin_methods* plug;
struct plugin_methods* (*init) () = dlsym(handle, "init");

plug = init();
plug->foo ();
plug->bar (123);

プラグインでは、次のようになります。

// Defined static so that another plugin could also define a
// function with the same name without problems.
static void my_current_plugin_foo() {
  // do something
}

static void my_current_plugin_bar(int baz) {
  // do something else
}

// Do not define the next function as "static", its symbol needs to
// be accessible from the outside.
struct plugin_methods* init() {
  struct plugin_methods* res;
  res = malloc(struct plugin_methods);
  res->foo = my_current_plugin_foo;
  res->bar = my_current_plugin_bar;
  return res;
}
于 2010-09-22T20:30:42.960 に答える
1

これを行う方法を理解しました。ご提案いただきありがとうございます。それは最終的に私を最終結果に導きました。

フォワードとして、私は提案で得た結果について言及したかった.

@DarkDust「エリア」モジュールがメイン関数にそのメソッドへのポインターを与えるという提案を実装しました。これは機能しましたが、結果は最初の投稿と同じでした。これにより、コンパイルされた両方の .c ファイルに、play_sfx、move_actor、set_name などの関数の独自のインスタンスがあると考えるようになりました。ただし、あなたの提案により、私の質問に対する最終的な答えが得られました。

@nos & @caf そうです。game.o を共有ライブラリにコンパイルしても意味がありません。行ったことは、プログラムの 2 つの個別のインスタンスを作成することだけでした...これは、そもそも私が望んでいたことではありません。

また、共有ライブラリを game.o とリンクしないようにしました。プログラムを実行すると、set_game シンボルが見つからないと言われました。

以下の図は、私が予想したこと、実際に起こったこと、および問題を解決するために行われたことを示しています。

How I expected it to work:
+--------------------+   +----------------+
| area.h             |   | game.h         |   
|                    |   |                |   
| call set_name() --------->set_name()    |   
|                    |   |                |   
+--------------------+   +----------------+

How it actually happened:
+-------------------------------+   +-----------------------------+
| area.h                        |   | game.h                      |   
|                               |   |                             |   
| call set_name() -->set_name() |   | set_name() <-- never called |
|                               |   |                             |   
+-------------------------------+   +-----------------------------+

How I resolved the issue:
+-------------------------------+   +-----------------------------+
| area.h                        |   | game.h                      |   
|                               |   |                             |   
| init(*p) {                    |   | init(*game_api_ref)         |   
|    *api = p;                  |   |                             |   
|    api->set_name() ----------------->set_name()                 |   
| }                             |   |                             |   
|                               |   |                             |   
+-------------------------------+   +-----------------------------+

これが私の最終的な概念実証です。理想的には、最初の例のように動作することを望んでいますが、脱線します。私がこれを機能させた方法により、領域の 1 つだけにバグがあった場合でも、実際に領域モジュールを再コンパイルするのが非常に簡単になります。ただし、この方法で大きなオーバーヘッドが発生するかどうかはわかりません。

api.h

#ifndef _API_H
#define _API_H

typedef struct
{
    int (*play_sfx)(int, int, int);
    int (*move_actor)(int, int, int);
    int (*set_name)(char*);
    char* (*get_name)(void);
} api_methods;

#endif

エリア.c

#include <stdlib.h>

#include "api.h"

static api_methods *api = NULL;

int init(api_methods *p) 
{
    api = p;

    api->set_name("area 1");

    return 1;
}

game.c

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

#include "api.h"

/**
 * Exposed API methods
 */
int play_sfx(int, int, int);
int move_actor(int, int, int);
int set_name(char*);
char* get_name(void);

/***** State machine variables *****/

// Name of area
static char *name = 0;

int main()
{

    // Setup API methods
    api_methods *api = (api_methods*) malloc(sizeof(api_methods));
    api->play_sfx = &play_sfx;
    api->move_actor = &move_actor;
    api->set_name = &set_name;
    api->get_name = &get_name;

    // Load & initialize area file
    void *handle = dlopen("./libarea.so", RTLD_LAZY);
    int (*init)() = dlsym(handle, "init");

    init(api);
    printf("Name: %s\n", get_name());

    free(api);
    dlclose(handle);

    return 0;
}

int play_sfx(int id, int times, int volume)
{
    // @todo Execute API call to play sfx
    return 1;

}

int move_actor(int id, int x, int y)
{
    // @todo Execute API call to move actor
    return 1;
}

int set_name(char *p)
{
    name = p;

    return 1;
}

char* get_name(void)
{
    return name;
}

build.sh

#!/bin/bash
gcc -fPIC -o area.o -c area.c
gcc -shared -o libarea.so area.o
gcc game.c -o game -ldl

私は最初に質問を作成したときと同じマシンを使用していないため、明日、問題の解決策として DarkDust の応答を確認します。

ありがとうございました!最初の例のように機能させる方法について何か提案があれば、引き続き回答をお待ちしています。リンクの問題か、関数プロトタイプの宣言が間違っていると思われます。いずれにせよ、これは私がする必要があることでうまくいくはずです。

于 2010-09-23T04:51:49.807 に答える
0

あなたが使う

static char *name = 0;

それは静的です。つまり、翻訳のエンティティごとに異なります。使ってみて、

extern char *name;

さらに、私はあなたのやり方があまり好きではありません。データ構造をAPI呼び出しに渡すために、少し再設計するのが良いかもしれません。APIは適切な情報でそれらの構造を初期化します。

于 2010-09-22T20:20:27.303 に答える