[英]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.