C общая библиотека вопрос - PullRequest
       8

C общая библиотека вопрос

1 голос
/ 23 сентября 2010

Справочная информация:

Я пытаюсь разработать простую игру, похожую на Zelda (NES) в C, как способ изучения C. Я пришел к выводу, что хранение всех данных игры в одном файле не является идеальным. Поэтому я хотел бы разбить каждую «область» на отдельный модуль. Этот модуль "области" будет описывать основной программе ее свойства, такие как карта тайлов, функции для запуска определенных событий и вызывать API игры основной программы для управления актерами / звуками / и т. Д. на экране. Таким образом, эти модули должны быть загружены / выгружены во время выполнения. По сути, основная программа - это конечный автомат, который работает от любых данных, которые ему поступают из этого модуля "области".

Я пытался создать эти общие модули, но не похоже, чтобы функции, определенные в основной программе, были видны модулю области (по крайней мере, не в той же области видимости). Я уверен, что это потому, что я связываю это неправильно. Поэтому я попытался найти подтверждение концепции, которая бы работала. Ниже мое, ошибочное, доказательство концепции:

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

area.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

Программа выдает:

$ ./game

Имя: (ноль)

Я ожидал увидеть: Имя: область 1

Возможно ли то, что я пытаюсь сделать? Если нет, у меня есть еще одна идея - зарегистрировать все вызовы API для модуля области ... но для меня это не идеально.

Информация о машине: gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3.

Ответы [ 4 ]

2 голосов
/ 23 сентября 2010

У вас есть static char *name = 0; в вашем файле .h, что приведет к путанице.

Ваш area.c включает app.h и получит один char *name видимый только в своей единице перевода, поскольку он статический.

Ваш game.c также включает app.h и получит другую переменную char *name.Таким образом, name, который видит area.c, не совпадает с тем, который видит game.c, и ни один из них не виден за пределами своих соответствующих файлов .c.Вы можете запустить nm в общей библиотеке и в основном исполняемом файле, чтобы убедиться в этом.Оба должны иметь name symboll

Поместить extern char *name; в app.h вместо static char *name; и поместить char *name; где-нибудь в области видимости файла в game.c

(хотя яЯ немного не уверен, что это решится правильно при динамической загрузке разделяемой библиотеки.)

1 голос
/ 23 сентября 2010

Я понял, как это сделать. Спасибо всем за ваши предложения! Это в конечном итоге привело меня к конечному результату.

В качестве нападающего я хотел бы упомянуть результаты, которые я получил с предложениями.

@ DarkDust Я реализовал ваше предложение, в котором модуль "area" дал главной функции указатель на ее методы. Это сработало, но результаты были такими же, как в первом посте. Это привело меня к мысли, что оба скомпилированных файла .c имеют свои собственные экземпляры функций play_sfx, move_actor, set_name и т. Д. Однако ваше предложение привело меня к возможному ответу на мой вопрос.

@ nos & @caf Ты прав. Не имело никакого смысла компилировать в game.o в общую библиотеку. Все, что было сделано, - это создать два отдельных экземпляра программы ... что, во-первых, не то, что я хотел.

Я также сделал общую библиотеку не связанной с 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()                 |   
| }                             |   |                             |   
|                               |   |                             |   
+-------------------------------+   +-----------------------------+

Вот мое последнее доказательство концепции. В идеале мне бы хотелось, чтобы это работало как первый пример, но я отвлекся. То, как я это сделал, на самом деле позволяет очень легко перекомпилировать модули областей, если есть ошибка только в одной из областей. Я не знаю, понесу ли я какие-либо огромные накладные расходы, делая это таким образом.

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

area.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 в качестве решения проблемы завтра.

Еще раз спасибо всем! Если у вас есть какие-либо предложения о том, как заставить это работать, как в первом примере, мне все равно было бы интересно услышать ваш ответ. Я подозреваю, что это либо проблема со связыванием, либо я неправильно объявляю прототипы функций. В любом случае это должно работать для того, что мне нужно.

1 голос
/ 23 сентября 2010

Думаю, ваша первая проблема в том, что вы звоните dlclose. Это на самом деле выгружает ваш плагин. Попробуйте переместить dlclose за printf, это работает?

Кроме того, это должно быть просто test();, а не (*test)(); AFAIK.

Когда я делал подобные вещи, я использовал структуру с указателями на функции. Прошло несколько лет, и это не проверено, поэтому просто относитесь к нему как к указателю в правильном направлении. Вам нужна структура, определенная в заголовочном файле:

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

Затем у каждого плагина был метод, который был загружен с dlsym, точно так же, как вы делаете с dlsym(handle, "init");. Но в моем случае он возвратил malloc'd struct plugin_methods с правильными указателями на функции, например:

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;
}
0 голосов
/ 23 сентября 2010

вы используете

static char *name = 0;

Это статично, это означает, что оно отличается в каждой сущности перевода.Попробуйте использовать,

extern char *name;

Более того, мне не очень нравится, как вы это делаете.Было бы неплохо немного переработать его, передать структуру данных вашим вызовам API, API будет инициализировать эту структуру с правильной информацией.

...