Динамически загружаемые библиотеки и общие глобальные символы - PullRequest
9 голосов
/ 09 июня 2010

Поскольку я наблюдал странное поведение глобальных переменных в моих динамически загружаемых библиотеках, я написал следующий тест.

Сначала нам нужна статически связанная библиотека: заголовок test.hpp

#ifndef __BASE_HPP
#define __BASE_HPP

#include <iostream>

class test {
private:
  int value;
public:
  test(int value) : value(value) {
    std::cout << "test::test(int) : value = " << value << std::endl;
  }

  ~test() {
    std::cout << "test::~test() : value = " << value << std::endl;
  }

  int get_value() const { return value; }
  void set_value(int new_value) { value = new_value; }
};

extern test global_test;

#endif // __BASE_HPP

и источник test.cpp

#include "base.hpp"

test global_test = test(1);

Затем я написал динамически загруженнуюбиблиотека: library.cpp

#include "base.hpp"

extern "C" {
  test* get_global_test() { return &global_test; }
}

и клиентская программа, загружающая эту библиотеку: client.cpp

#include <iostream>
#include <dlfcn.h>
#include "base.hpp"

typedef test* get_global_test_t();

int main() {
  global_test.set_value(2); // global_test from libbase.a
  std::cout << "client:        " << global_test.get_value() << std::endl;

  void* handle = dlopen("./liblibrary.so", RTLD_LAZY);
  if (handle == NULL) {
    std::cout << dlerror() << std::endl;
    return 1;
  }

  get_global_test_t* get_global_test = NULL;
  void* func = dlsym(handle, "get_global_test");
  if (func == NULL) {
    std::cout << dlerror() << std::endl;
    return 1;
  } else get_global_test = reinterpret_cast<get_global_test_t*>(func);

  test* t = get_global_test(); // global_test from liblibrary.so
  std::cout << "liblibrary.so: " << t->get_value() << std::endl;
  std::cout << "client:        " << global_test.get_value() << std::endl;

  dlclose(handle);
  return 0;
}

Теперь я компилирую статически загруженную библиотеку с помощью

g++ -Wall -g -c base.cpp
ar rcs libbase.a base.o

динамически загружаемая библиотека

g++ -Wall -g -fPIC -shared library.cpp libbase.a -o liblibrary.so

и клиент

g++ -Wall -g -ldl client.cpp libbase.a -o client 

Теперь я наблюдаю: клиент и динамически загружаемая библиотека имеют разные версии переменной global_test.Но в моем проекте я использую cmake.Сценарий сборки выглядит следующим образом:

CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
PROJECT(globaltest)

ADD_LIBRARY(base STATIC base.cpp)

ADD_LIBRARY(library MODULE library.cpp)
TARGET_LINK_LIBRARIES(library base)

ADD_EXECUTABLE(client client.cpp)
TARGET_LINK_LIBRARIES(client base dl)

, анализируя созданные makefile s. Я обнаружил, что cmake собирает клиента с помощью

g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client

. Это приводит к немного другому, но фатальному поведению.: global_test клиента и динамически загружаемой библиотеки одинаковы, но будут уничтожены два раза в конце программы.

Я неправильно использую cmake?Возможно ли, чтобы клиент и динамически загруженная библиотека использовали один и тот же global_test, но без этой проблемы двойного уничтожения?

Ответы [ 6 ]

3 голосов
/ 09 июня 2010
g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client

CMake добавляет параметр -rdynamic, позволяющий загруженной библиотеке разрешать символы в загружаемом исполняемом файле ... Таким образом, вы можете видеть, что это то, что вам не нужно.Без этой опции он просто случайно пропускает этот символ.

Но ... Вы не должны делать ничего подобного.Ваши библиотеки и исполняемый файл не должны совместно использовать символы, если они действительно не должны быть общими.

Всегда думайте о динамическом соединении как о статическом соединении.

2 голосов
/ 09 июня 2010

По умолчанию компоновщик не объединяет глобальную переменную («D») в базовом исполняемом файле с переменной в общей библиотеке. Базовый исполняемый файл является особенным. Возможно, существует неясный способ сделать это с одним из тех неясных управляющих файлов, которые читает ld, но я в этом сомневаюсь.

- при динамическом экспорте символы a.out 'D' будут доступны для общих библиотек.

Однако рассмотрим процесс. Шаг 1: вы создаете DSO из .o с 'U' и .a с 'D'. Итак, компоновщик включает символ в DSO. Шаг 2, вы создаете исполняемый файл с 'U' в одном из .o файлов и 'D' в .a и DSO. Он попытается решить, используя правило слева направо.

Переменные, в отличие от функций, в любом случае создают определенные трудности для компоновщика между модулями. Лучше избегать глобальных ссылок на переменные через границы модулей и использовать вызовы функций. Однако это все равно не получится, если вы поместите одну и ту же функцию в базовый исполняемый файл и в общую библиотеку.

2 голосов
/ 09 июня 2010

При использовании разделяемых библиотек вы должны определить, что вы хотите экспортировать с помощью макроса, например здесь См. Определение макроса DLL_PUBLIC там.

1 голос
/ 09 июня 2010

Мой первый вопрос: есть ли какая-то конкретная причина, по которой вы статически и динамически (через dlopen) связываете один и тот же код?

Для вашей проблемы: -rdynamic будет экспортировать символы из вашей программы и чтоВероятно, происходит из-за того, что динамический компоновщик преобразует все ссылки на вашу глобальную переменную в первый символ, который встречается в таблицах символов.Какой из них я не знаю.

РЕДАКТИРОВАТЬ: учитывая вашу цель, я бы связал вашу программу таким образом:

g++ -Wall -g -ldl client.cpp -llibrary -L. -o client

Возможно, вам придется исправить порядок.

0 голосов
/ 29 января 2014

Я бы предложил скомпилировать любую статическую библиотеку .a, которую вы планируете связать с динамической библиотекой, с параметром -fvisibility = hidden, поэтому:

g ++ -Wall -fvisibility = hidden -g -c base.cpp

0 голосов
/ 31 августа 2010

Я бы посоветовал использовать dlopen(... RTLD_LAZY|RTLD_GLOBAL); для объединения глобальных таблиц символов.

...