2010-09-22 11 views
1

Contexte:C partagée question bibliothèque

Je suis en train de développer un jeu simple similaire à Zelda (NES) en C comme un moyen d'apprendre C. Je suis venu à la conclusion que présentant toutes les caractéristiques jeu les données dans un seul fichier ne sont pas idéales. Donc, ce que je voudrais faire est de diviser chaque «zone» dans son propre module. Ce module "zone" décrira au programme principal ses propriétés, telles qu'une carte de tuiles, des fonctions à déclencher sur certains événements, et appellera l'API de jeu du programme principal pour manipuler les acteurs/sons/etc. sur l'écran. Ainsi, ces modules doivent être chargés/déchargés au moment de l'exécution. Essentiellement, le programme principal est une machine d'état qui fonctionne à partir de toutes les données qui lui sont fournies à partir de ce module "zone".

J'ai essayé de créer ces modules partagés mais il ne semble pas que les fonctions définies dans le programme principal soient visibles par le module de zone (du moins pas dans le même cadre). Je suis sûr que c'est parce que je le relie incorrectement. Donc ce que j'ai fait est d'essayer de trouver une preuve de concept qui fonctionnerait. Ci-dessous mon, à défaut, la preuve de concept:

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 

Construire:

./build.sh $

Le programme produit:

./game $

Nom: (null)

Je m'attendais à voir: Nom: zone 1

Est-ce que ce que j'essaie de faire est même possible? Sinon, j'ai une autre idée qui consiste à enregistrer tous les appels d'API au module de zone ... mais pour moi, c'est pas idéal.

Informations sur la machine: gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3.

+0

ressemble à la LUA-Language –

+0

Je suis assez sûr que Zelda sur le NES n'a pas besoin de chargement/déchargement des bibliothèques dynamiques à l'exécution. :) – bk1e

Répondre

1

Je pense que votre premier problème est que vous appelez dlclose. Cela décharge réellement votre plugin. Essayez de déplacer le dlclose derrière le printf, cela fonctionne-t-il? En outre, cela devrait être juste test();, pas (*test)(); AFAIK. Lorsque j'ai fait des choses similaires, j'ai utilisé une structure avec des pointeurs de fonction. Cela fait quelques années et cela n'a pas été testé, alors il suffit de le traiter comme un indicateur dans la bonne direction. Vous avez besoin d'une structure définie dans un fichier d'en-tête:

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

Ensuite, chaque plug-in avait une méthode qui a été chargé avec dlsym, exactement comme vous le faites avec dlsym(handle, "init");. Mais dans mon cas, il est revenu un struct plugin_methods malloc'd avec les pointeurs de bon fonctionnement, comme ceci:

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

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

Dans le plug-in, il avait ressembler à ceci:

// 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

+1, c'est une bonne approche –

+0

Je vais essayer cela dès que j'ai le temps. Merci beaucoup pour la contribution! – echamber

+0

Je ne suis pas tout à fait sûr pourquoi cela a eu le +1. Le simple fait de déplacer le 'dlclose' ne va pas aider. 'name' dans la bibliothèque partagée doit avoir un lien externe (c'est-à-dire non statique) _and_ il doit être accessible via' dlsym' si 'dlopen' et pas de liaison directe est utilisé. (Alternativement 'get_name' pourrait être déplacé dans la bibliothèque partagée.) Aussi' test() 'et' (* test)() 'sont également valides. –

2

Vous avez static char *name = 0; dans votre fichier .h, ce qui causera de la confusion.

Votre area.c comprend app.h et aura un char *name visible uniquement dans son unité de traduction car il est statique.

Votre game.c comprend également app.h et aura une autre variable char *name. Ainsi, le name que voit area.c n'est pas le même que celui que game.c voit, et aucun n'est visible en dehors de leurs fichiers .c respectifs. Vous pouvez exécuter nm sur la bibliothèque partagée et l'exécutable principal pour vérifier cela.Tous les deux devraient avoir un provate name symboll

place extern char *name; dans app.h au lieu de static char *name; et placez char *name; quelque part à périmètre de fichier dans game.c

(bien que je suis un peu incertain si cela va résoudre correctement lorsque en utilisant le chargement dynamique d'une lib partagée).

+0

Changé api.h pour inclure extern char * nom; – echamber

+0

Désolé, en appuyant sur Entrée ne m'a pas mis sur une nouvelle ligne, mais posté le commentaire précédent: Dans game.c j'ai ajouté char * nom; – echamber

+0

Encore une fois, désolé ... ne peut pas sembler mettre mes commentaires sur une nouvelle ligne. Dans tout les cas. Avoir un nom statique dans les deux fichiers .c est logique. Ce changement, cependant, comme vous le soupçonniez, ne semble pas résoudre le problème. Je pense que les adresses des appels de fonction sont également uniques à chaque fichier .c. Donc, même si je compile le fichier game.o dans la bibliothèque partagée, il n'utilise toujours pas la fonction binaire "jeu" compilée. Je suis un pas de plus quand même! Merci pour la réponse rapide! J'apprécierais grandement toute autre contribution que vous pourriez avoir. – echamber

0

vous utilisez

static char *name = 0; 

Il est statique, ce qui signifie qu'il est différent dans chaque entité de traduction. Essayez d'utiliser,

extern char *name; 

En outre, je n'aime pas vraiment comment vous faites cela. Il serait peut-être bon de le redessiner un peu, de transmettre des données structrure à vos appels api, api va intialiser ces structures avec des informations correctes.

1

Je compris comment pour faire ça. Merci à tous pour vos suggestions! Cela m'a finalement amené au résultat final.

En tant qu'avant, je voulais mentionner les résultats que j'ai obtenus avec les suggestions.

@DarkDust J'ai implémenté votre suggestion où le module "area" donnait à la fonction principale un pointeur vers ses méthodes. Cela a fonctionné, mais les résultats étaient les mêmes que dans le premier message. Cela m'amène à croire que les deux fichiers .c compilés ont leurs propres instances des fonctions play_sfx, move_actor, set_name, etc. Cependant, votre suggestion m'a conduit à la réponse éventuelle à ma question.

@nos & @caf Vous avez raison. Cela n'avait aucun sens de compiler dans le jeu. Dans la bibliothèque partagée. Tout ce qui a été fait était de créer deux instances distinctes du programme ... ce qui n'était pas ce que je voulais en premier lieu.

J'ai également fait la bibliothèque partagée pas de lien avec game.o. Quand j'ai lancé le programme, il m'a dit qu'il ne pouvait pas trouver le symbole set_game.

Le schéma ci-dessous illustre ce que je pensais, ce qui est arrivé, et ce qui a été fait pour résoudre le problème:

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()     | 
| }        | |        | 
|        | |        | 
+-------------------------------+ +-----------------------------+ 

Voici ma dernière preuve de concept. Idéalement, j'aimerais toujours que cela fonctionne comme le premier exemple, mais je m'égare. La façon dont je l'ai fait fonctionner rend vraiment très facile de recompiler des modules de zone s'il y a un bug dans un seul domaine. Je ne sais pas si j'engage des coûts énormes en le faisant de cette façon cependant.

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 

Je ne suis pas sur la même machine que j'étais quand je créé la réponse de la question donc je vais vérifier DarkDust que la résolution du problème demain.

Merci encore à tous! Si vous avez des suggestions sur la façon de le faire fonctionner comme dans le premier exemple, je serais toujours intéressé à entendre votre réponse. Je soupçonne que c'est un problème de liaison ou que je déclare incorrectement les prototypes de fonction. En tout cas, cela devrait fonctionner pour ce dont j'ai besoin.

Questions connexes