Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Linux-инструменты для Windows-программистов
Назад Компиляция и сборка приложений Вперед

Конструктор и деструктор

Посмотрим внешние имена (для связывания) созданной в предыдущем примере разделяемой библиотеки:

$ nm libhello.so 

...
00000498 T _fini 
000002ec T _init 
...
00000430 T put_my_msg
...

Мы видим ряд имён (и как раз типа T для внешнего связывания), одно из которых put_my_msg — это имя нашей функции. Два других имени — присутствуют в любой разделяемой библиотеке: функция _init() вызывается при загрузке библиотеки в память (например, для инициализации некоторых структур данных), а _fini() - при выгрузке библиотеки (деструктор).

Примечание: Наличие функций конструктора и деструктора (_init() и _fini()) является общим свойством всех файлов формата ELF, к которому относятся и исполнимые файлы Linux, и файлы динамических разделяемых библиотек.

Эти функции (конструктор и деструктор библиотеки) могут быть переопределены из вашего пользовательского кода создания библиотеки. Для этого перепишем динамическую библиотеку из наших предыдущих примеров (каталог init архива libraries.tgz):

hello_child.c :

#include "../hello_child.h" 
#include <sys/time.h> 

static mark_time( void ) { 
   struct timeval t; 
   gettimeofday( &t, NULL ); 
   printf("%02d:%06d : ", t.tv_sec % 100, t.tv_usec ); 
} 

static mark_func( const char *f ) { 
   mark_time();
   printf( "%s\n", f ); 
} 

void _init( void ) { 
   mark_func( __FUNCTION__ ); 
} 

void _fini( void ) { 
   mark_func( __FUNCTION__ ); 
} 

int put_my_msg( char *messg ) { 
   mark_time();
   printf( "%s\n", messg ); 
   return -1;
} 

Но просто собрать такую библиотеку не получится:

$ gcc -c -fpic -fPIC -shared hello_child.c -o hello_child.o 

$ gcc -shared -o libhello.so hello_child.o 
...
hello_child.c:(.text+0x0): multiple definition of `_init' 
/usr/lib/gcc/i686-redhat-linux/4.4.4/../../../crti.o:(.init+0x0): first defined here 
hello_child.o: In function `_fini': 
hello_child.c:(.text+0x26): multiple definition of `_fini' 
/usr/lib/gcc/i686-redhat-linux/4.4.4/../../../crti.o:(.fini+0x0): first defined here 
...

Совершенно естественно, так как мы пытались повторно переопределить имена, уже определенные в стартовом объектном коде. Желаемого результата мы достигнем сборкой с опциями, как показано в следующем сценарии сборки:

Makefile :
TARGET = hello 
CHILD =  $(TARGET)_child 
LIB = lib$(TARGET) 

TARGET1 = $(TARGET)_d 
TARGET2 = $(TARGET)_a 

all: $(LIB) $(TARGET1) $(TARGET2) 

$(LIB):        $(CHILD).c ../$(CHILD).h 
                gcc -c -fpic -fPIC -shared $(CHILD).c -o $(CHILD).o 
                gcc -shared -nostartfiles -o $(LIB).so $(CHILD).o 
                rm  -f *.o 

$(TARGET1):    $(TARGET1).c $(LIB) 
                gcc $< -Bdynamic -ldl -L./ -l$(TARGET) -o $@ 

$(TARGET2):    $(TARGET2).c $(LIB) 
                gcc $< -Bdynamic -L./ -l$(TARGET) -o $@ 

- здесь как TARGET1 (файл hello_d) собирается приложение, самостоятельно подгружающее динамическую библиотеку libhello.so по требованию, а как TARGET2 (файл hello_a) - приложение, опирающееся на автоматическую загрузку библиотек средствами системы. Сами исходные коды приложений теперь имеют вид:

hello_a.c :
#include "../hello_child.h" 
int main( int argc, char *argv[] ) { 
   int res = put_my_msg( (char*)__FUNCTION__ ); 
   return res; 
}; 
hello_d.c :
#include <dlfcn.h> 
#include "../hello_child.h" 
typedef int (*my_func)( char* ); 

int main( int argc, char *argv[] ) { 
    // Открываем совместно используемую библиотеку 
   void *dl_handle = dlopen( "./libhello.so", RTLD_LAZY ); 
   if( !dl_handle ) { 
      printf( "ERROR: %s\n", dlerror() ); 
      return 3; 
   } 
   // Находим адрес функции в библиотеке 
   my_func func = dlsym( dl_handle, "put_my_msg" ); 
   char *error = dlerror(); 
   if( error != NULL ) { 
      printf( "ERROR: %s\n", dlerror() ); 
      return 4; 
   } 
   // Вызываем функцию по найденному адресу 
   int res = (*func)( (char*)__FUNCTION__ ); 
   // Закрываем библиотеку 
   dlclose( dl_handle ); 
   return res; 
}; 

Теперь собираем всё это вместе и приступаем к опробованию:

$ make 

gcc -c -fpic -fPIC -shared hello_child.c -o hello_child.o 
gcc -shared -nostartfiles -o libhello.so hello_child.o 
rm -f *.o 
gcc hello_d.c -Bdynamic -ldl -L./ -lhello -o hello_d 
gcc hello_a.c -Bdynamic -L./ -lhello -o hello_a 
$ export LD_LIBRARY_PATH=`pwd` 
$ ./hello_a 
65:074290 : _init 
65:074387 : main 
65:074400 : _fini 
$ ./hello_d 
68:034516 : _init 
68:034782 : main 
68:034805 : _fini 

Смысл приложений в том, что каждая из функций, в порядке их вызова, выводи своё имя и метку времени, когда она вызвана (число после двоеточия — микросекунды, перед — секунды).


Предыдущий раздел: Оглавление Следующий раздел:
Как это всё работает?   Подмена имён