繁体   English   中英

C共享库问题

[英]C shared library question

背景:

我正在尝试在C中开发一个类似于Zelda(NES)的简单游戏作为学习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

名称:( null)

我希望看到:姓名:1区

我正在努力做甚么可能吗? 如果没有,我还有一个想法是将所有API调用注册到区域模块......但对我来说,这并不理想。

机器信息:gcc(Ubuntu 4.3.3-5ubuntu4)4.3.3。

你有static char *name = 0; 在.h文件中,这会引起混淆。

您的area.c包含app.h,并且只会在其翻译单元中显示一个char *name ,因为它是静态的。

你的game.c还包括app.h,并将获得另一个char *name变量。 因此,area.c看到的name与game.c看到的名称不同,并且在各自的.c文件之外都不可见。 您可以在共享库上运行nm ,并使用主可执行文件来验证这一点。 两者都应该有一个name符号

放置extern char *name; 在app.h而不是static char *name; 并放置char *name; 在game.c中的文件范围的某个地方

(虽然我有点不确定在使用动态加载共享库时是否会正确解析)。

我认为你的第一个问题是你打电话给dlclose 这实际上卸载了你的插件。 尝试将dlclose移到printf后面,这dlclose吗?

另外,那应该只是test(); ,不是(*test)(); 据我所知。

回到我做类似的事情时,我使用了一个带有函数指针的结构。 已经过了几年,这是未经测试的,所以只需将其视为正确方向的指针即可。 您需要在头文件中定义的结构:

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

然后,每个插件都有一个加载了dlsym的方法,就像你使用dlsym(handle, "init"); 但在我的情况下,它返回了一个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;
}

我想出了如何做到这一点。 谢谢大家的建议! 它最终让我到最后的结果。

作为一名前锋,我想提一下我得到的结果。

@DarkDust我实现了你的建议,其中“area”模块给main函数指向了它的方法。 这有效,但结果与第一篇文章相同。 这让我相信两个编译的.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的响应作为明天解决问题的方法。

再次感谢所有人! 如果您对如何使其工作有任何建议,如第一个示例中所述,我仍然有兴趣听取您的回复。 我怀疑这是一个链接问题或我正在声明函数原型错误。 无论如何,这应该适用于我需要它做的事情。

你用

static char *name = 0;

它是静态的,这意味着它在每个翻译实体中都是不同的。 尝试使用,

extern char *name;

而且,我真的不喜欢你这样做。 重新设计它可能是一件好事,将数据结构传递给api调用,api将使用适当的信息初始化这些结构。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM