Есть некий проект, а точнее, CGI-приложение, написанное на C. Такие штуки, по-моему, должны обладать немаловажным свойством - они должны быть не сильно большими. Ведь при каждом обращении клиента создаётся копия всего кода в памяти. Соответственно, поднимать не требующиеся в данный момент громоздкие функции - это моветон.
Поэтому с самого начала было ясно, что часть исполняемого кода рано или поздно придется выносить в отдельные библиотеки. И вот, наконец, этот момент настал. По этому случаю пришлось немного погуглить, и, в силу своего слабого разумения, выстроить следующую картину, как это делается.
1. Пусть, например, у нас есть проект, который состоит из трех файлов:
Файл FirstModule.c (и соответствующий ему заголовочный файл FirstModule.h):
char *FirstModule_Function1 (char *param) {...} char *FirstModule_Function2 (char *param) {...}
Файл SecondModule.c (и заголовочный файл SecondModule.h), который активно пользуется функиями из первого файла:
#include "FirstModule.h" char *SecondModule_Function1 (char *param) {...} char *SecondModule_Function2 (char *param) {...}
И, наконец, файл Main.c, который, конечно, использует функции из обоих вышеуказанных файлов:
#include "FirstModule.h" #include "SecondModule.h" int main() { ... char *val = SecondModule_Function1 ("некий параметр"); ... }
Как всё это богатство компилируется в один исполняемый файл Main? Примерно так:
gcc -c FirstModule.c -o FirstModule.o gcc -c SecondModule.c -o SecondModule.o gcc -c Main.c -o Main.o gcc FirstModule.o SecondModule.o Main.o -o Main rm -f *.o
2. Теперь предположим, что SecondModule.c неприлично растолстел. Превращаем его в Shared library (т.е. делаем динамическую библиотеку).
Во-первых, переписываем Main.c:
#include "FirstModule.h" #include <dlfcn.h> int main() { void *lib_handle; char *lib_error; char *(*fn)(char *param); // пытаемся загрузить библиотеку из той же директории, что и исполняемый файл lib_handle = dlopen("./SecondModule.so", RTLD_LAZY); if (!lib_handle) { fprintf(stderr, "1. %s\n", dlerror()); exit(1); } // получаем ссылку на нужную нам функцию fn = dlsym(lib_handle, "SecondModule_Function1"); if ((lib_error = dlerror()) != NULL) { fprintf(stderr, "2. %s\n", lib_error); exit(1); } // выполняем функцию char *val = (*fn)("некий параметр"); // используем результат выполнения printf("%s\n", val); // выгружаем библиотеку dlclose(lib_handle); ... }Немножко длинно, но довольно понятно.
Во-вторых компилируем всё это так:
gcc -c FirstModule.c -o FirstModule.o gcc -c -fPIC SecondModule.c -o SecondModule.o gcc -shared SecondModule.o -o SecondModule.so gcc -c Main.c -o Main.o gcc -ldl FirstModule.o Main.o -o Main rm -f *.oВ результате получаются исполняемый файл Main и использующаяся им по мере надобности библиотека SecondModule.so.
3. Теперь интересный момент. Выяснилось, что SecondModule.so по-прежнему активно использует функции, описанные в FirstModule.c и вкомпилированные в основной исполняемый файл. Соответственно, использовать этот SecondModule.so где-нибудь в левом проекте, который не знает про FirstModule, не получится.
Это обстоятельство можно обойти так - собрать SecondModule.so, включив в неё функции из FirstModule.c. То есть, проект в этом случае будет собираться так:
gcc -c -fPIC FirstModule.c -o FirstModule.o gcc -c -fPIC SecondModule.c -o SecondModule.o gcc -shared FirstModule.o SecondModule.o -o SecondModule.so gcc -c Main.c -o Main.o gcc -ldl FirstModule.o Main.o -o Main rm -f *.oЗдесь, конечно, и в Main, и в SecondModule.so код из FirstModule будет дублироваться, но зато SecondModule.so станет автономной.
4. Однако, если теперь посмотреть командой nm -D SecondModule.so, то можно увидеть поразительный факт - в этой библиотеке одинаково хорошо видны как FirstModule_Function1, FirstModule_Function2, так и SecondModule_Function1, SecondModule_Function2 ! Если мы не хотим, чтобы были видны ВСЕ функции, придется добавить к проекту ещё один файл, SecondModule.lds, в котором указать, какие именно символы экспортировать:
{ global: SecondModule_Function1; SecondModule_Function2; local: *; }
И тогда уже можно проект собрать в окончательно замороченном виде:
gcc -c -fPIC FirstModule.c -o FirstModule.o gcc -c -fPIC SecondModule.c -o SecondModule.o gcc -shared -Wl,--version-script=SecondModule.lds FirstModule.o SecondModule.o -o SecondModule.so gcc -c Main.c -o Main.o gcc -ldl FirstModule.o Main.o -o Main rm -f *.o(Здесь -Wl,что-то-там - это указание передать линковщику параметр "что-то-там")
В результате получаются:
Программа Main, которая использует в нужный момент SecondModule.so.
Библиотека SecondModule.so, из которой видны две функции, SecondModule_Function1, SecondModule_Function2.
Вроде, неплохо. Если нигде не напутал.
А тут - источники знаний:
http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
http://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
http://stackoverflow.com/questions/8866790/do-shared-libraries-use-the-same-heap-as-the-application
ftp://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_25.html