C共享库问题

发布于 2024-09-24 12:35:26 字数 1907 浏览 0 评论 0原文

背景:

我正在尝试用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

回忆追雨的时光 2024-10-01 12:35:26

您的 .h 文件中有 static char *name = 0;,这会导致混乱。

您的area.c包含app.h,并且将获得一个仅在其翻译单元中可见的char *name,因为它是静态的。

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

在 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 the name 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 provate name symboll

Place extern char *name; in app.h instead of static char *name; and place char *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).

缱绻入梦 2024-10-01 12:35:26

我认为你的第一个问题是你调用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;
}

I think your first problem is that you call dlclose. That actually unloads your plugin. Try to move the dlclose behind the printf, 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:

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

Then, each plugin had a method that was loaded with dlsym, exactly like you do with dlsym(handle, "init");. But in my case, it returned a malloc'd struct plugin_methods with the proper function pointers, like this:

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

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

In the plugin, it'd look like this:

// 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;
}
还不是爱你 2024-10-01 12:35:26

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

作为一名前锋,我想谈谈我通过建议得到的结果。

@暗尘
我实现了您的建议,其中“区域”模块为主函数提供了指向其方法的指针。这有效,但结果与第一篇文章中的结果相同。这让我相信两个编译的 .c 文件都有自己的 play_sfx、move_actor、set_name 等函数的实例。但是,您的建议引导我找到了问题的最终答案。

@nos & @咖啡馆
你说得对。将 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 的响应作为问题的解决方案。

再次感谢大家!如果您对如何使其像第一个示例那样工作有任何建议,我仍然有兴趣听到您的回应。我怀疑这要么是链接问题,要么是我错误地声明了函数原型。无论如何,这应该适合我需要它做的事情。

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:

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

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

#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

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.

稚然 2024-10-01 12:35:26

你使用

static char *name = 0;

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

extern char *name;

此外,我不太喜欢你这样做的方式。重新设计一下可能会更好,将数据结构传递给 api 调用,api 会使用正确的信息初始化这些结构。

you use

static char *name = 0;

It is static, that means it is different in each entity of translation. Try to use,

extern char *name;

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文