@M4gpie

Возможно ли перенести «внешний» интерфейс одной библиотеки в другую через статическую связку?

Добрый день. Возникла следующая проблема: нужно создать программу, способную подключать множество разных функций из динамических библиотек. Внешний интерфейс библиотек унифицирован: из него можно получить кол-во и имена доступных в библиотеке функций и вызвать одну из них через имя. Но дело не в этом, а в том, что сами библиотеки нужно сделать так, чтобы человеку, пишущему новую, не нужно было делать ничего кроме как перегрузить пару методов базового класса. Более того, для удобства пользователя нужно предоставить возможность вручную типизировать контейнер функций обратного вызова (из которых в итоге и вызываются библиотечные функции). Но всё вышеперечисленное не проблема и уже реализовано в более-менее работоспособном виде. Проблема возникла, когда всё это добро нужно было превратить в статическую библиотеку. Дело в том, что динамический интерфейс был реализован внутри этой статической библиотеки, а пользователю с помощью виртуального наследования нужно создать класс-потомок и перегрузить в нём пару методов. И тут началась проблема - почему-то функции базового интерфейса библиотеки не загружаются при подключении в главной программе. Они загружаются, если я не наследую из статической библиотеки, а создаю динамическую библиотеку сразу из основного кода, все функции работают нормально. Я не уверен на 100%, но при линковке статической библиотеки бинарник никак не делит этот код, и имена функций, которые я объявил extern "C" должны быть по прежнему видны "извне".
Выглядит это в общих чертах так:
abstract_part.h
//Я максимально урезал функционал, чтоб не отвлекать от главного вопроса, но описать, для чего каждый класс нужен.

//Общий абстрактный интерфейс (не интерфейс библиотеки)
class Basic_Interface
{
public:
Basic_Interface();
virtual ~basic_interface() = 0;
virtual int function_count() = 0; // возвращает кол-во функций
virtual char** call_function(char * name, int num, char ** variables) = 0; //вызывает функцию по имени
}

//Шаблон-с-натяжкой-контейнер, хранящий информацию по функции
template<typename Ftype>
class function_container
{
private:
std::map<std::string,Ftype> container;
public:
function_container(){}
Ftype getFunction(char * name)
{
std::string fname(name);
auto iter = container.find(fname);
return *iter;
}
int getCount() 
{ 
return container.size();
}
}

//Общий шаблон управления, реализует некоторые промежуточные функции.
//От него пользователь должен наследовать для создания собственной библиотеки.
template<typename Ftype>
class Basic_Manager: public Basic_Interface
{
private:
function_container<Ftype> container;
public:
Basic_Manager():Basic_interface(){}

int function_count()
{
return container.getSize();
}
virtual char** call_function(char * name, int num, char ** variables) = 0; //По прежнему реализуется конечным пользователем
}


global_interface.h
//Здесь уже интерфейс самой библиотеки, с которым и проблема
#include "abstract_part.h"
struct GLB
{
Basic_Interface * target; //  Инициализируют в .cpp
}
//Те самые функции, которые нужно подключить в итоге
extern "C" int __getCount() 
{return target->function_count();}
extern "C" char ** __callFunction(char * name, int count, char ** variables)
{return target->call_function(name,count,variables);}


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

user_library.h
#include "global_interface.h"
typedef char ** (*cb_foo)(int,char**);

class UserManager:public Basic_Manager<cb_foo>
{
public:
UserManager():BasicManager(){}
char ** call_function(char * name, int count, char ** variables);
}


user_library.cpp
#include "user_library.h"
UserManager manager;
GLB.target = &manager;

UserManager():call_function(char * name, int count, char ** variables)
{
cb_foo func = container.getFunction(name);
return (*func)(count,variables);
}


Вот так оно должно работать по идее. И если спаять пользовательскую и часть с интерфейсом, оно работает без проблем. Но если подключать интерфейсную часть как статическую библиотеку с h-ником, в основной программе экстерн-функции не обнаруживаются. Я не совсем понимаю, почему так. Возможно этому есть объяснение в стандарте, но вроде статичная линковка библиотеки не влияет на подобное - просто у функций остаются С-имена. Может быть кто-нибудь сталкивался с подобной проблемой или знает её решение. Я не знаю, как точно назвать эту проблему, возможно "наследование" крайне неподходящее определение для того, что я пытаюсь сделать, но как ещё назвать перенесение внешнего интерфейса из одной библиотеки в другую с помощью статической связки?
Платформы win7, win10, ubuntu. Компилятор mingw 4.9.2 32bit. IDE Qt creator - проект собирается с помощью qmake.
  • Вопрос задан
  • 182 просмотра
Решения вопроса 1
@MarkusD Куратор тега C++
все время мелю чепуху :)
Судя по описанию вопроса, проблема в том, что между кодом основного модуля сборки и кодом статической библиотеки, от которой зависит основной код, нет прямой функциональной связи.

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

Все дело в том, что полная компоновка (linkage) кода производится только для базового модуля сборки, т.е. для самого плагина. Все статические библиотеки, от которых зависит твой базовый модуль, проходят сортировку по зависимостям и компонуются со строгой фильтрацией.
Это значит, что если уже скомпонованный код не ссылается на какую-то функцию из статической библиотеки, эта функция не будет скомпонована. Сортировка библиотек по зависимостям требуется для того, чтобы как раз позволить за один проход по очереди библиотек скомпоновать весь необходимый код.

Есть несколько способов обойти правило компоновки библиотек.
Первый и самый простой: вынудить компоновщик обработать всю библиотеку целиком. Это делается через представление библиотеки как Whole Archive[?].
Однако, в этом случае вся статическая библиотека будет скомпонована в исполняемый код. Это не всегда бывает удобно. Особенно если в библиотеке находится много отладочного кода.

Второй вариант - это использовать __attribute__((used))[?] или [[gnu::used]] на новый лад.
В коде для Visual C++ можно использовать #pragma comment(linker: "/include:")[?].
Данные конструкции помечают функцию как важную для компоновки, в результате чего функция всегда будет компоноваться из статической библиотеки в бинарный код.

Вариант третий - это использовать опцию -u[?] компоновщика.
Для Visual C++ такой опцией будет /INCLUDE[?].
Указание важных функций для принудительной компоновки прямо в сценарии сборки является наиболее прозрачным и удобным методом с точки зрения дальнейшей поддержки кода.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы