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.
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;
}