C共享库问题
背景:
我正在尝试用 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)
我期望看到: 名称:area 1
我想做的事情可能吗?如果没有,我还有另一种想法,即将所有 API 调用注册到区域模块...但是,对我来说,这不理想。
机器信息:gcc(Ubuntu 4.3.3-5ubuntu4)4.3.3。
Background:
I'm trying to develop a simple game similar to Zelda (NES) in C as a way to learn C. I've come to the conclusion that having all of the game data in a single file is not ideal. So what I'd like to do is break up each "area" into it's own module. This "area" module will describe to the main program its properties, such as a tile map, functions to trigger on certain events, and call the main program's game API to manipulate actors/sounds/etc. on the screen. So, these modules must be loaded/unloaded at run-time. Essentially, the main program is a state machine that works off of whatever data is supplied to it from this "area" module.
I've tried to create these shared modules but it doesn't seem like the functions defined in the main program are visible to the area module (at least not in the same scope). I'm sure this is because I'm linking it incorrectly. So what I've done is try to come up with a proof of concept that would work. Below is my, failing, proof of 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
Build:
$ ./build.sh
The program produces:
$ ./game
Name: (null)
I expected to see: Name: area 1
Is what I'm trying to do even possible? If not, I have one other idea which is to register all the API calls to the area module... but that, to me, is not ideal.
Machine info: gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您的 .h 文件中有
static char *name = 0;
,这会导致混乱。您的area.c包含app.h,并且将获得一个仅在其翻译单元中可见的
char *name
,因为它是静态的。您的 game.c 还包含 app.h 并将获得另一个 char *name 变量。因此,area.c 看到的名称与 game.c 看到的名称不同,并且在各自的 .c 文件之外都不可见。
您可以在共享库和主可执行文件上运行
nm
来验证这一点。两者都应该有一个 provatename
符号在 app.h 中放置
extern char *name;
而不是static char *name;
并放置char *name;
位于 game.c 文件范围内的某个位置(尽管我有点不确定在使用动态加载共享库时是否能正确解析)。
You have
static char *name = 0;
in your .h file, which will cause confusion.Your area.c includes app.h and will get one
char *name
visible only in its translation unit since it's static.Your game.c also includes app.h and will get another
char *name
variable. Thus thename
that area.c sees is not the same as the one game.c sees, and neither are visible outside their respective .c files.You can run
nm
on the shared library, and the main executable to verify this. Both should have a provatename
symbollPlace
extern char *name;
in app.h instead ofstatic char *name;
and placechar *name;
somewhere at file scope in game.c(although I'm a bit unsure if that'll resolve properly when using loading a shared lib dynamically).
我认为你的第一个问题是你调用
dlclose
。这实际上会卸载你的插件。尝试将dlclose
移到printf
后面,可以吗?另外,那应该只是
test();
,而不是(*test)();
AFAIK。当我做类似的事情时,我使用了带有函数指针的结构。已经过去几年了,这还没有经过测试,所以只需将其视为正确方向的指针即可。您需要在头文件中定义一个结构:
然后,每个插件都有一个使用 dlsym 加载的方法,就像使用 dlsym(handle, "init"); 一样。 >。但就我而言,它返回了一个带有正确函数指针的 malloc'd
struct plugin_methods
,如下所示:在插件中,它看起来像这样:
I think your first problem is that you call
dlclose
. That actually unloads your plugin. Try to move thedlclose
behind theprintf
, does that work ?Also, that should be just
test();
, not(*test)();
AFAIK.Back when I did similar things, I used a struct with function pointers. It's been a few years and this is untested, so just treat it as a pointer in the right direction. You need a structure defined in a header file:
Then, each plugin had a method that was loaded with
dlsym
, exactly like you do withdlsym(handle, "init");
. But in my case, it returned a malloc'dstruct plugin_methods
with the proper function pointers, like this:In the plugin, it'd look like this:
我想出了如何做到这一点。谢谢大家的建议!它最终让我得到了最终结果。
作为一名前锋,我想谈谈我通过建议得到的结果。
@暗尘
我实现了您的建议,其中“区域”模块为主函数提供了指向其方法的指针。这有效,但结果与第一篇文章中的结果相同。这让我相信两个编译的 .c 文件都有自己的 play_sfx、move_actor、set_name 等函数的实例。但是,您的建议引导我找到了问题的最终答案。
@nos & @咖啡馆
你说得对。将 game.o 编译到共享库中没有任何意义。所做的只是创建了该程序的两个单独的实例...这不是我想要的。
我还使共享库不与 game.o 链接。当我运行该程序时,它说找不到 set_game 符号。
下图说明了我的预期、实际发生的情况以及解决问题所采取的措施:
这是我的最终概念证明。理想情况下,我仍然希望它像第一个示例一样工作,但我离题了。我让它工作的方式实际上使得如果仅其中一个区域存在错误,重新编译区域模块变得非常容易。我不知道这样做是否会产生巨大的开销。
api.h
area.c
game.c
build.sh
我不在同一台机器上当我第一次创建这个问题时,所以我明天会检查 DarkDust 的响应作为问题的解决方案。
再次感谢大家!如果您对如何使其像第一个示例那样工作有任何建议,我仍然有兴趣听到您的回应。我怀疑这要么是链接问题,要么是我错误地声明了函数原型。无论如何,这应该适合我需要它做的事情。
I figured out how to do this. Thank you all for your suggestions! It eventually got me to the end result.
As a forward I wanted to mention the results I got with the suggestions.
@DarkDust
I implemented your suggestion where the "area" module gave the main function a pointer to its methods. This worked, but the results were the same as in the first post. This lead me to believe that both compiled .c files have their own instances of the play_sfx, move_actor, set_name, etc. functions. However, your suggestion lead me to the eventual answer to my question.
@nos & @caf
You're right. It didn't make any sense to compile in the game.o into the shared library. All that did was create two separate instances of the program... which isn't what I wanted in the first place.
I also made the shared library not link with game.o. When I ran the program it said it couldn't find the set_game symbol.
The diagram below illustrates what I expected, what actually happened, and what was done to resolve the issue:
Here is my final proof of concept. Ideally, I'd still like it to work like the first example, but I digress. The way that I've made it work actually makes it very easy to recompile area modules if there's a bug in just one of the areas. I don't know if I incur any huge overhead by doing it this way though.
api.h
area.c
game.c
build.sh
I'm not on the same machine I was on when I first created the question so I'll check DarkDust's response as the resolution to the problem tomorrow.
Thanks again all! If you have any suggestions on how to make it work as in the first example I'd still be interested in hearing your response. I suspect that it's either a linking problem or I'm declaring the function prototypes incorrectly. In any case this should work for what I need it to do.
你使用
它是静态的,这意味着它在每个翻译实体中都是不同的。尝试使用,
此外,我不太喜欢你这样做的方式。重新设计一下可能会更好,将数据结构传递给 api 调用,api 会使用正确的信息初始化这些结构。
you use
It is static, that means it is different in each entity of translation. Try to use,
Moreover, I don't really like how you do that. It might be a good to redesign it a bit, to pass data structrure to you api calls, api will intialized those structure with proper information.