Перейти к содержимому

Как вызвать функцию из статической библиотеки с

  • автор:

Использование библиотек и компонентов

В проектах C++ часто требуется вызывать функции или обращаться к данным в двоичных файлах, таких как статические библиотеки (LIB-файлы), библиотеки DLL, компоненты среды выполнения Windows, компоненты COM или сборки .NET. В этих случаях необходимо настроить проект таким образом, чтобы он мог находить нужные двоичные файлы во время сборки. Конкретный способ сделать это зависит от типа вашего проекта, типа двоичного файла, а также от того, был ли этот двоичный файл собран в том же решении, что и ваш проект.

Использование скачанных библиотек с помощью vcpkg

Если вы хотите использовать скачанную библиотеку с помощью диспетчера пакетов vcpkg, то приведенные ниже инструкции можно пропустить. Дополнительные сведения см. в разделе:

  • vcpkg в проектах CMake
  • Установка и использование пакетов с CMake в Visual Studio
  • vcpkg в проектах MSBuild
  • Руководство. Установка и использование пакетов с MSBuild в Visual Studio

Использование статических библиотек

Если проект статической библиотеки был создан в том же решении:

  1. #include файл заголовка для статической библиотеки с помощью кавычки. В типовом решении путь начинается с ../ . При поиске вы можете использовать предложения технологии IntelliSense.
  2. Добавьте ссылку на проект статической библиотеки. Щелкните правой кнопкой мыши элемент Ссылки в узле проекта приложения в обозревателе решений и выберите Добавить ссылку.

Если статическая библиотека не входит в состав решения:

  1. Щелкните правой кнопкой мыши узел проекта приложения в обозревателе решений и выберите Свойства.
  2. На странице свойств Каталоги VC++ добавьте в раздел Пути библиотек путь к каталогу, который содержит LIB-файл. Затем добавьте в раздел Включаемые каталоги путь к файлам заголовков библиотеки.
  3. На странице свойств компоновщика > добавьте имя LIB-файла в дополнительные зависимости.

Библиотеки динамической компоновки

Если библиотека DLL была собрана в рамках того же решения, что и приложение, выполните те же действия, что и для статической библиотеки.

Если библиотека DLL не входит в состав решения приложения, вам потребуются DLL-файл, заголовки с прототипами для экспортируемых функций и классов, а также LIB-файл, содержащий необходимую для компоновки информацию.

  1. Скопируйте DLL-файл в папку выходных данных проекта или другую папку, которая задана в качестве стандартной для поиска библиотек DLL в Windows. Дополнительные сведения см. в разделе «Порядок поиска библиотеки динамических ссылок».
  2. Выполните шаги с 1 по 3 для статических библиотек, чтобы задать пути к заголовкам и LIB-файлу.

COM-объекты

Если в собственном приложении C++ требуется использовать COM-объект и этот объект зарегистрирован, вам достаточно вызвать функцию CoCreateInstance и передать в нее CLSID объекта. Система выполнит поиск объекта в реестре Windows и загрузит его. В проекте C++/CLI COM-объект можно использовать таким же образом. Кроме того, он может использовать его, добавив ссылку на нее из списка добавления ссылок > COM и используя ее через вызываемую оболочку среды выполнения.

Сборки .NET с компонентами среды выполнения Windows

В проектах универсальной платформы Windows (UWP) или C++/CLI для использования сборок .NET или компонентов среды выполнения Windows можно добавить ссылку на сборку или компонент. В узле Ссылки проекта универсальной платформы Windows (UWP) или C++/CLI представлены ссылки на часто используемые компоненты. Щелкните правой кнопкой мыши узел Ссылки в Обозревателе решений, чтобы открыть диспетчер ссылок и просмотреть доступные в системе компоненты. Нажмите кнопку Обзор, чтобы перейти к папке, в которой находится нужный вам пользовательский компонент. Поскольку сборки .NET и компоненты среды выполнения Windows содержат встроенные сведения о типах, для просмотра их методов и классов можно щелкнуть правой кнопкой мыши и выбрать команду Просмотреть в обозревателе объектов.

Свойства ссылки

Каждый тип ссылки имеет свойства. Свойства можно просмотреть, выбрав ссылку в обозревателе решений и нажав клавиши Alt + ВВОД. Также можно щелкнуть ссылку правой кнопкой мыши и выбрать пункт Свойства. Одни свойства доступны только для чтения, другие можно изменять. Тем не менее обычно эти свойства не требуется изменять вручную.

Свойства ссылки ActiveX

Свойства ссылки ActiveX доступны только для компонентов COM. Данные свойства отображаются только тогда, когда в панели Ссылки выбран компонент COM. Эти свойства нельзя изменить.

  • Управление полным путем Отображает путь к каталогу элемента управления, на который указывает ссылка.
  • GUID элемента управления Отображает GUID для элемента управления ActiveX.
  • Версия элемента управления Отображает версию элемента управления ActiveX, на который указывает ссылка.
  • Имя библиотеки типов Отображает имя библиотеки типов, на которую указывает ссылка.
  • Средство программы-оболочки Отображает средство, которое используется для создания сборки взаимодействия из указанной библиотеки COM или элемента управления ActiveX.

Свойства ссылки на сборку (C++/CLI)

Свойства ссылки на сборку доступны только для ссылок на сборки .NET Framework в проектах C++/CLI. Данные свойства отображаются только тогда, когда в панели Ссылки выбрана сборка .NET Framework. Эти свойства нельзя изменить.

  • Относительный путь Отображает относительный путь от каталога проекта к сборке, на которую указывает ссылка.

Свойства сборки

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

  • Копировать локальные Указывает, следует ли автоматически копировать сборку, на которую указывает ссылка, в целевое расположение во время сборки.
  • Копировать локальные вспомогательные сборки (C++/CLI) Указывает, следует ли автоматически копировать вспомогательные сборки ссылочной сборки в целевое расположение во время сборки. Используется, только если для параметра Копировать локальные задано значение true .
  • Выходные данные ссылочной сборки Указывает, что эта сборка используется в процессе сборки. true означает, что эта сборка используется в командной строке компилятора во время выполнения сборки.

Свойства ссылок проектов на проекты

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

  • Компоновать зависимости библиотек Если это свойство имеет значение True, система проектов установит в зависимом проекте связь с LIB-файлами, создаваемыми независимым проектом. Обычно устанавливается значение True.
  • Идентификатор проекта Уникальный идентификатор независимого проекта. Значение свойства — это GUID внутренней системы, который невозможно изменить.
  • Использовать входные данные зависимостей библиотек Если это свойство имеет значение False, система проектов не установит в зависимом проекте связь с OBJ-файлами для библиотеки, созданной независимым проектом. Таким образом, это значение отключает инкрементную компоновку. Обычно указывается значение False, так как при наличии множества независимых проектов сборка приложения может занять длительное время.

Свойства ссылки только для чтения (COM и .NET)

Следующие свойства существуют в ссылках на компоненты COM и сборки .NET, и их нельзя изменить.

  • Имя сборки Отображает имя сборки для сборки, на которую указывает ссылка.
  • Язык и региональные параметры Отображает язык и региональные параметры выбранной ссылки.
  • Description Отображает описание выбранной ссылки.
  • Полный путь Отображает путь к каталогу сборки, на которую указывает ссылка.
  • Identity Для сборок .NET Framework отображает полный путь. Для компонентов COM отображает GUID.
  • Подпись Отображает метку ссылки.
  • Имя Отображает имя ссылки.
  • Токен открытого ключа Отображает токен открытого ключа для идентификации сборки, на которую указывает ссылка.
  • Строгое имяtrue , если сборка, на которую указывает ссылка, имеет строгое имя. Сборка со строгим именем имеет уникальную версию.
  • Версия Отображает версию сборки, на которую указывает ссылка.

Как вызвать функцию из статической библиотеки с

Здарвствуйте! Создал два проекта в VS2008 (x32 в Win7):
— геlib.LIB — там функция, которая должна вызваться из EXE
— CalLIB.EXE — тут вызов функции из LIB
Но EXE-проект не собирается.

В свойства проекта добавил нужные ссылки
— С/С++-Доп.каталоги включения=..\..\utlib\utlib
— Компоновщик-Доп.каталоги библиотек=..\..\utlib\Debug
— Ввод-Доп.зависимости=..\..\utlib\Debug\utlib.lib

Вызов записан так:

#include "utlib.h" #pragma comment(lib, "utlib.lib") void CCallLIBDlg::OnBnClickedButton1()

Re: Не могу вызвать функцию из LIB-библиотеки

От: BlackEric http://black-eric.lj.ru
Дата: 17.04.13 09:33
Оценка:

Здравствуйте, bURov, Вы писали:

UR>Вызов записан так:
UR>

#include "utlib.h" UR>#pragma comment(lib, "utlib.lib") UR>void CCallLIBDlg::OnBnClickedButton1() UR> < UR>utlib::hash(CString &StrVar); UR>>

А в либе utlib::hash(CString &StrVar); вообще есть?

Re: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 17.04.13 09:40
Оценка:

Сорри, ошибся. Так сейчас:

#include "utlib.h" #pragma comment(lib, "utlib.lib") void CCallLIBDlg::OnBnClickedButton1()

Это от старого кода я привел. Теперь уже формальный и фактический аргументы с разными именами.
Re: Не могу вызвать функцию из LIB-библиотеки

От: blonduser
Дата: 17.04.13 13:34
Оценка: -1

Здравствуйте, bURov, Вы писали:

UR>Здарвствуйте! Создал два проекта в VS2008 (x32 в Win7):
UR>- геlib.LIB — там функция, которая должна вызваться из EXE
UR>- CalLIB.EXE — тут вызов функции из LIB
UR>Но EXE-проект не собирается.

UR>В свойства проекта добавил нужные ссылки
UR>- С/С++-Доп.каталоги включения=..\..\utlib\utlib
UR>- Компоновщик-Доп.каталоги библиотек=..\..\utlib\Debug
UR>- Ввод-Доп.зависимости=..\..\utlib\Debug\utlib.lib

UR>Вызов записан так:
UR>

#include "utlib.h" UR>#pragma comment(lib, "utlib.lib") UR>void CCallLIBDlg::OnBnClickedButton1() UR> < UR>utlib::hash(CString &StrVar); UR>>

В библиотеки должно быть описано, что класс экспортируется.
В программе — импортируется.

Re[2]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 18.04.13 06:53
Оценка:

B>В библиотеки должно быть описано, что класс экспортируется.
B>В программе — импортируется.
__declspec(dllexport) int hash();
__declspec(dllimport) int hash();
Так?
А это в cpp-файле или в h-файле?
В каком месте в тексте?

Re[2]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 18.04.13 07:32
Оценка:

B>В библиотеки должно быть описано, что класс экспортируется.
B>В программе — импортируется.
Значит для LIB-варианта недостаточно только создать LIB-файл в LIB-проекте студии.
Может быть нужно ещё дополнительно подготовить утилитой-библиотекарем LIB?

Re[3]: Не могу вызвать функцию из LIB-библиотеки

От: BlackEric http://black-eric.lj.ru
Дата: 18.04.13 08:30
Оценка:

Здравствуйте, bURov, Вы писали:

B>>В библиотеки должно быть описано, что класс экспортируется.
B>>В программе — импортируется.
UR>Значит для LIB-варианта недостаточно только создать LIB-файл в LIB-проекте студии.
UR>Может быть нужно ещё дополнительно подготовить утилитой-библиотекарем LIB?

Либу же тоже сам делал?
Тогда сделай для простоты либу с одной функцией сложения и разбирайся как оно должно быть.
Потом когда поймешь — заводи эту.
Быстрее будет.

Re[4]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 18.04.13 08:34
Оценка:

BE>Либу же тоже сам делал?
BE>Тогда сделай для простоты либу с одной функцией сложения и разбирайся как оно должно быть.
BE>Потом когда поймешь — заводи эту.
BE>Быстрее будет.
Да, сам. И там и есть функция «hash», которая пока ничего не делает (катсти, я проектик для студии прицепил). Эта единственная функция в классе «utlib», разве что ещё конструктос деструктором, что по-умолчанию с клссом создались.
И я вот и разбираюсь, чтобы просто все собралось без ошибок и вызов функции получился.

Re[5]: Не могу вызвать функцию из LIB-библиотеки

От: BlackEric http://black-eric.lj.ru
Дата: 18.04.13 12:13
Оценка:

Здравствуйте, bURov, Вы писали:

Т.е. создаем проект статической либы и добавляем 2 файла. В хедере пишем

// Returns a + b double Add(double a, double b);

В cpp реализуем эту функцию.
Собрали lib файл. Все.

Дальше создали консольное приложение. Ему в ресурсы подключили собранную либу и подкинули хедер из проекта либы
В cpp написали

// TestUseL.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "TestL.h" int _tmain(int argc, _TCHAR* argv[]) < double c = Add(5, 7); printf("c = %f\n", c); getchar(); return 0; >

Все. Если нужно с классами — пример в msdn Creating and Using a Static Library (C++)

Re[6]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 18.04.13 12:22
Оценка:

BE>В интернете же полно примеров.
BE>Вот простейший: How to Create and Implement Static Library in VC++
BE>Дальше создали консольное приложение.
Ну мне же не консольное EXE нужно.
Мне с диалогами, контролами. Я его и пытаюсь собрать.
По интеренту я полазил достаточно.
И там, да, упирают все примеры в консольное EXE.
Сейчас по форумам ищу. Все вроде предлагают то, что я уже и сам выполнил.
А сборка не идет ((

Re[7]: Не могу вызвать функцию из LIB-библиотеки

От: BlackEric http://black-eric.lj.ru
Дата: 18.04.13 12:27
Оценка:

Здравствуйте, bURov, Вы писали:

UR>Мне с диалогами, контролами. Я его и пытаюсь собрать.
UR>По интеренту я полазил достаточно.
UR>И там, да, упирают все примеры в консольное EXE.
UR>Сейчас по форумам ищу. Все вроде предлагают то, что я уже и сам выполнил.
UR>А сборка не идет ((

А вы уверены, что пихать диалоги и контролы в либу это хороший вариант?

Re[2]: Не могу вызвать функцию из LIB-библиотеки

От: pik
Дата: 18.04.13 12:37
Оценка:

Здравствуйте, blonduser, Вы писали:

B>В библиотеки должно быть описано, что класс экспортируется.
B>В программе — импортируется.

не путайте вы его он сам запутается

если скомпилировано как lib то не надо, надо толъко если lib получена из dll

Re: Не могу вызвать функцию из LIB-библиотеки

От: pik
Дата: 18.04.13 12:45
Оценка:

Здравствуйте, bURov, Вы писали:

_cdecl в LIBе убери

Re[8]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 18.04.13 12:56
Оценка:

BE>А вы уверены, что пихать диалоги и контролы в либу это хороший вариант?
В принципе, почему бы и нет, НО.
Я не в либу их пихаю, а в EXE.
А либу строю не как виндовс-приложение или консольное, а как статичесое.

Re[2]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 18.04.13 13:18
Оценка:

UR>>Получаю ошибку
UR>>http://files.rsdn.ru/95990/ERR_LNK2019.png
pik>_cdecl в LIBе убери
Я _cdecl не задаю — это в ошибке студия пишет.

//utlib.h #include #pragma once class utlib < public: utlib(void); ~utlib(void); CString StrVar; static int hash(CString &StrVar); >;
//utlib.cpp #include "utlib.h" utlib::utlib(void) : StrVar(_T("")) < >utlib::~utlib(void) < >int utlib::hash(CString &StrVar) < return 0; >

Re[3]: Не могу вызвать функцию из LIB-библиотеки

От: BlackEric http://black-eric.lj.ru
Дата: 18.04.13 13:21
Оценка:

Здравствуйте, bURov, Вы писали:

UR>>>Получаю ошибку
UR>>>http://files.rsdn.ru/95990/ERR_LNK2019.png
pik>>_cdecl в LIBе убери
UR>Я _cdecl не задаю — это в ошибке студия пишет.

UR>

//utlib.h UR>#include UR>#pragma once UR>class utlib UR>< UR>public: UR> utlib(void); UR> ~utlib(void); UR> CString StrVar; UR> static int hash(CString &StrVar); UR>>; UR>

static попробуй убрать.

Re[4]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 18.04.13 13:26
Оценка:

__cdecl (/Gd) — это один из трех ключей компилятора для EXE-файла (+ fastcall, stdcall)
BE>static попробуй убрать.
Завтра попробую.

Re[5]: Не могу вызвать функцию из LIB-библиотеки

От: BlackEric http://black-eric.lj.ru
Дата: 18.04.13 13:31
Оценка: +1

Здравствуйте, bURov, Вы писали:

UR>__cdecl (/Gd) — это один из трех ключей компилятора для EXE-файла (+ fastcall, stdcall)
BE>>static попробуй убрать.
UR>Завтра попробую.

int hash() < return 0; >

а потом постепенно навешивай навороты. Тогда и поймешь в чем проблема.

Re: Не могу вызвать функцию из LIB-библиотеки

От: carpenter
Дата: 18.04.13 14:37
Оценка:

Здравствуйте, bURov, Вы писали:

а у тебя оба проекта используют unicode character set?
а то по ходу екзешник пользует multibyte a либа unicode

Весь мир — Кремль, а люди в нем — агенты
Re[2]: Не могу вызвать функцию из LIB-библиотеки

От: bURov
Дата: 19.04.13 06:53
Оценка:

C>а у тебя оба проекта используют unicode character set?
C>а то по ходу екзешник пользует multibyte a либа unicode
Точно, недосмотрел! Я зачем-то отключил в EXE unicode.
Сейчас опробовал оба проекта и в unicode и в multibyte.
То же самое (на сборку проекта не влияет, м.б. при выполнении будут грабли).

Но после того, как последовал уроку:
http://www.c-sharpcorner.com/UploadFile/chaq686/how-to-create-and-implement-static-library-in-vcpp/
любезно подсказанному мне BlackEric, проект собрался.
Т.е. я не использовал классы, которые студия автоматом создает

//utlib.h int hash();
//utlib.cpp #include"utlib.h" int hash() < return 0; >

Размер сильно уменьшился.
Ещё нужно было для либы задать ключик «/MTd» (многопоточная),
а был «MDd» (многопоточная DLL).

Всем спасибо за участие.
Теперь, нужно научиться добавлять классы в либу. Если это вообще для либы возможно.

PS. из примера я убрал дефайн

#ifndef __UTLIB__ #define __UTLIB__ int hash(); #endif

использовать функцию из библиотеки

предположим, все пути указаны верно. можно ли в своем приложении пользоваться функцией function без динамической подгрузки функции через dlopen ?

#include int main() < ex a; function(a); //

если кому не сложно, то накидайте этот пример и покажите как вызвать функцию без dlopen. не понимаю
Отслеживать
задан 17 дек 2017 в 13:35
2,622 1 1 золотой знак 17 17 серебряных знаков 30 30 бронзовых знаков
Прилинковать include.so
17 дек 2017 в 13:40
прилинковываю, и что дальше?
17 дек 2017 в 13:46

Всё должно работать. Если не работает, то значит прилинковали не правильно. Укажите, как именно вы линкуете и из каких папок запускаете приложение.

17 дек 2017 в 13:52
@VTT, исправил. замечу что в include.h нету объявления функции foo
17 дек 2017 в 14:18

Как вы пытаетесь обратиться к функции не объявив ее? Откуда компилятору знать, что и где искать? И никто не мешает прямо в main() написать прототип Вашей функции.

17 дек 2017 в 20:03

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

alexander@goblin /tmp/foolib $ cat foo.cpp #include int foo () < std::cout alexander@goblin /tmp/foolib $ cat main-foo.cpp int foo (); int main () < return foo(); >alexander@goblin /tmp/foolib $ g++ -shared -fpic -olibfoo.so foo.cpp alexander@goblin /tmp/foolib $ g++ -fpic -o main-foo -L. -lfoo main-foo.cpp alexander@goblin /tmp/foolib $ LD_LIBRARY_PATH=. ./main-foo Hello world from foo! 

Отслеживать
ответ дан 18 дек 2017 в 9:08
23.1k 1 1 золотой знак 19 19 серебряных знаков 37 37 бронзовых знаков
да, спасибо, Vladimir подсказал что надо было тупо объявить функцию у себя в main.cpp
18 дек 2017 в 9:28
не заметил скрытые комментарии сначала.
18 дек 2017 в 9:39

  • c++
  • linux
  • dll
    Важное на Мете
Похожие

Подписаться на ленту

Лента вопроса

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

Дизайн сайта / логотип © 2024 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2024.1.17.3574

Динамические и статические библиотеки.

Начнём с повторения того, что мы уже знаем. Каждый исходный файл транслируется в объектный файл, после чего все объектные файлы линкуются в программу. Но иногда бывает кусок кода, который хочется переиспользовать. Мы могли бы оттранслировать объектные файлы этого куска один раз, после чего сразу с ними компилировать. Но так оказалось, что уже существует механизм сгруппировать объектные файлы вместе, после чего отдать их линковщику. Называется этот механизм.

Статические библиотеки.

// sum.cpp int sum(int a, int b)
// four.cpp #include int sum(int a, int b); int main()
#include int sum(int a, int b); int main()

И мы зачем-то пытаемся вычленить sum.cpp как библиотеку. Тогда сделать надо вот что: Компилируем:

g++ -c sum.cpp -o sum.o ar rcs libsum.a sum.o 

Что тут происходит?

  • ar — сокращение от «archive».
  • rcs — это некоторая магия (читайте man).
  • libsum.a — название библиотеки.

Чтобы скомпилировать каждый из файлов выше с этой библиотекой, делаем так:

g++ four.cpp -lsum -L. -o four g++ five.cpp -lsum -L. -o five 

А что происходит тут?

  • -L говорит, в каком каталоге искать библиотеку (в нашем случае в каталоге . — в текущем).
  • -lsum говорит, что нам нужна библиотека, которая называется libsum.a (т.е. к тому, что идёт после -l спереди приписывается lib», а сзади — «.a»).

Тут больше совершенно ничего интересного, потому что статическая библиотека — набор объектных файлов, а про объектные файлы мы всё знаем. То ли дело.

Динамические библиотеки.

Пусть у вас есть библиотека, которая используется везде вообще. Например, libc. Если она статическая, то код библиотеки есть в каждой из программ. А значит в каждой программе они занимают место и на диске, и в памяти. Чтобы этого избежать, применяют динамические библиотеки.
Идея динамических библиотек в том, что мы ссылаемся как-то на внешнюю библиотечку, а потом на этапе исполнения грузим по надобности её части. Тогда она на диске лежит всего одна, и в память мы можем загрузить её один раз.

Давайте в примере выше сделаем статическую библиотеку динамической:

g++ -fpic -c sum.cpp -o sum.o g++ -shared sum.o -o libsum.so g++ four.cpp -lsum -L. -o four g++ five.cpp -lsum -L. -o five 

Что значат все консольные опции тут, уже пояснить намного сложнее, и мы поясним их в следующем параграфе.
А пока обратим внимание на то, что когда мы запустим four или five, нам на этапе исполнения скажут, что библиотека libsum.so не найдена. Хотя, казалось бы, вот она рядом лежит. Дело в том, что по умолчанию Linux ищет библиотеки только по системным путям. (Windows ищет и в текущей директории.) Чтобы проверить, от каких библиотек зависит ваша программа, запустите ldd ./four, и вам скажут, что нужна libsum.so, но её нет.

Есть два способа поправить сию оказию:

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

LD_LIBRARY_PATH=. ./four 

Если вам нужно несколько путей, разделяйте их двоеточиями.

Второй — записать в саму программу, где искать библиотеки.
Это можно посмотреть при помощи objdump в секции Dynamic Section , где есть RUNPATH . Чтобы записать туда что надо, делается вот что:

g++ four.cpp -lsum -L. -Wl,-rpath= -o four 

-Wl говорит, что опцию после него (т.е. -rpath) надо передать линковщику. Линковщику эта опция говорит, что в тот самый RUNPATH надо записать тот путь, который вы попросили.
А какой надо просить? Не «.» ведь, потому что это путь, из которого вы запускаете программу, а не то место, где сама программа.
И тут вам на помощи приходит псевдо-путь $ORIGIN , который и ссылается на место программы. Используя его Вы можете свободно написать что-нибудь по типу -rpath='$ORIGIN/../lib/'.

Впрочем, есть ещё и третий путь — использовать CMake, который будет делать всю грязную работу за вас, если написать ему команду add_library .

Кстати, в Windows это работает иначе. В-нулевых, динамическая библиотека там называется не shared object, а dynamic load library. Во-первых, DLL-ки сразу же ищутся в текущем каталоге. Во-вторых, чтобы понять, что вы ссылаетесь на динамическую библиотеку, в Linux вы пишете -L. -lsum, а в Windows компиляция DLL создаёт вам специальный .lib-файл, который называется import-библиотекой, и с которым вы компилируете вашу программу, чтобы она сама поняла, откуда какие функции брать.

Причины нестандартной компиляции динамических библиотек.

g++ -fpic -c sum.cpp -o sum.o g++ -shared sum.o -o libsum.so 

Нас интересуют магические слова -fpic и -shared. Зачем на как-то особенно компилировать динамические библиотеки?

А дело вот в чём — при запуске программы, она первая загружается в адресное пространство, и она сама может выбрать, куда ей хочется. Динамические библиотеки такого же по понятным причинам позволить себе не могут. Возникает вопрос — и что теперь? А то, что при наличии глобальных переменных, мы не можем впаять им фиксированные адреса.

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

Самый простой способ жить с динамическими библиотеками был у Microsoft на 32-битной Windows. У каждой библиотеки был base-address — то куда библиотеке хочется загрузиться. Если там свободно — туда она и загружается, а если нет, то библиотеку загружают туда, где есть место, а в специальной отдельной секции (.reloc) хранится список адресов, которые надо исправить. Разумеется, в случае релокаций умирает переиспользование библиотеки, но Windows вам же полностью предоставляют, там можно расположить системные библиотеки так, как хочется, поэтому в проприетарных системах всё будет хорошо.

В Linux же это реализовано следующим образом. Смотрите как можем:

 call next next: pop ABX lea EAX, [EBX + (myvar - next)] 

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

Вообще мы не очень хорошо написали (процессор не любит непарные call и pop ), поэтому обычно это выглядит так:

get_pc: mov EBX, [ESP] ret get_variable: call get_pc next: lea EAX, [EBX + (myvar - next)] 

Этот код называется position-independent code, и ключ -fpic именно генерацией такого кода и занимается. Вопрос — почему для этого не сделали специальную инструкцию? А вот сделали, но в 64-битном режиме. Всё что с квадратными скобками стало уметь обращаться в память начиная со смещения текущей инструкции. И называется это RIP-relative positioning.

GOT/IAT. PLT.

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

Например, библиотека для работы с JSON хочет делать fopen . То есть нужно подружить библиотеки друг с другом. Самый простой вариант — когда мы делаем call , в файл мы кладём нулевой адрес, а в секцию релокаций записываем, что вместо него нужно положить fopen , после чего при запуске динамический загрузчик всё разложит по местам. То есть то же самое, что с линковщиком. Почему так не делают? Потому что мы от' mmap 'или нашу библиотеку, а в ней дырки. И во все места дырок нужно что-то подставить. И опять вы не можете записать библиотеку в память один раз, что вам очень хочется.

Поэтому вместо этого просто заводят табличку со смещениями, и теперь все call 'ы обращаются туда, и туда же динамический загрузчик подставляет истинные адреса функций. Эта таблица в Linux называется global offset table, а в Windows — import address table.

Но на самом деле есть ещё проблема. Давайте посмотрим, что происходит, когда мы делаем

void foo(); void bar()

Как мы обсуждали, тут будет call и пустой адрес (до линковки пустой). А что будет, если foo — это внешняя функция из какой-то библиотеки? Тогда надо бы вместо простого call 'а сделать call qword [got_foo] . Но есть проблема — мы узнаём, откуда эта функция, только на этапе линковки, а компилировать надо раньше. Поэтому компилятор call foo , а потом, если это было неправильно, просто создаёт свою функцию foo , которая является прослойкой для jmp qword [got_foo] . Такие заглушки, которые просто совершают безусловный переход по глобальной таблице смещений имеют название. В Linux их называют PLT (procedure linkage table), а в Windows как-то по-другому.

Но в Linux PLT используется ещё для одной цели. Рассмотрим, скажем, LibreOffice, в котором сотни динамических библиотек с тысячами функций в каждой. Поэтому заполнение GOT — это долго. И нам не хочется смотреть, где лежит каждая функция, после чего записывать её в таблицу. Поэтому эту операцию сделали ленивой:
GOT заполняется специальными заглушками, которые динамически ищут в хэш-таблице настоящий адрес функции, после чего записывают его в GOT вместо себя, и вызывают эту функцию, чтобы она отработала. В Microsoft по-умолчанию отложенная загрузка не используется, но его можно включить (delayed DLL loading или как-то так называется). Это фича загрузчика, а не самой Windows, и делает эта фича примерно то же самое. Однако есть разница. В Linux отсутствие библиотеки не позволяет запустить программу. В Windows же библиотека подгружается при первом вызове функции оттуда, что, по их словам, сделано чтобы вы могли за' if 'ать ситуацию, когда библиотеки нет.

Офф-топ на тему «Как страшно жить».

Поговорим про изменение so-файлов. Давайте возьмём и во время работы программы поменяем библиотечку на диске, втупую вписав туда другой текст. Результат поразителен — работа программы также изменится. Почему? Мы же, вроде как, исполняем библиотеку из оперативки, а не с диска. А дело в том, как работает copy-on-write в операционных системах. Когда вы пишете в некоторую страницу, вам копируют её. Но когда кто-то извне пишет в страницу, вам не дают копию старых данных. С исполняемым файлом такое не прокатывает, кстати. Это потому, что вашу программу загружает ядро, и оно может запретить изменять бинарники, а библиотеку загружает ваша программа, которая такого механизма не имеет.

Кстати, изменение и перекомпиляция — разные вещи. И если вы во время работы программы перекомпилируете библиотеку, она не обновится. Связано это с тем, что перекомпилированная библиотека — это новый файл, а не старый. По сути вы удалили старую библиотеку и создали новую, вместо того, чтобы библиотеку изменить. А в Linux пока кто-то имеет доступ к файлу, файл не удаляется до конца. И поэтому в вашей программе всё ещё есть та самая библиотека, которую вы загружали (а не новая).

Детали работы с динамическими библиотеками в Windows.

Никто не удивиться, что набор

 call foo@PLT foo@PLT: jmp qword [got_foo] 

не очень эффективен (три обращения в память вместо одного). Поэтому в Windows есть спецификатор __declspec(dllimport) , который сразу вместо call 000000 и замены нулей на foo@plt вставляет call qword [got_foo] .

Ещё в Windows есть такая штука как .def-файл — линковщик экспортирует из вашей DLL-ки только то, что нужно, и в .def-файле указывается, что именно. Это хорошо работает в C, где имена символов и имена функций совпадают, но не очень хорошо в C++, где вам придётся писать сложные декорируемые имена. Поэтому есть второй вариант — написать на самой функции __declspec(dllexport) .

И вроде бы всё хорошо, вы метите функции, которые экспортируете как __declspec(dllexport) , которые импортируете — как __declspec(dllimport) и всё классно работает. Но есть проблема: вы и в библиотеке, и в коде, который её использует, подключаете один заголовочный файл, где объявлена функция. И непонятно, что там писать: __declspec(dllexport) или __declspec(dllimport) . Для этого заводится специальный макрос под каждую библиотеку, которым отличают, саму DLL вы компилируете или кого-то с её использованием.

Есть ещё одна проблема. Непонятно, что делать с глобальными переменными. Там проблема ещё более страшная: вы сначала читаете адрес переменной из GOT (извините, IAT), а потом по полученному адресу обращаетесь. Тут уже никакую функцию-прослойку не написать, увы. Поэтому если вы не пометите глобальную переменную как __declspec(dllimport) , тот тут вы уже точно совсем проиграете, у вас линковка не получится.

А ещё реализация DLL в Windows нарушает правила языка: если вы напишете inline -функцию в заголовочном файле. Она просто откопируется в каждую библиотеку, где вы этот заголовок подключился. С этим вы ничего не сделаете, тут вы просто проиграли.

Детали работы с динамическими библиотеками в Linux.

Если вы думаете, что в Windows проблемы с динамическими библиотеками, потому что Windows — какашка, то сильно заблуждаетесь, потому что в Linux нюансов тоже выше крыши.

Итак, мем первый и основной — interposition. Есть такая переменная окружения, как LD_PRELOAD . Она завставляет динамический загрузчик сначала обшарить в поисках динамических библиотек то, что вы в LD_PRELOAD написали, а уже потом смотреть какие-нибудь RUNPATH 'ы и всё остальное. В частности, так можно подменить аллокатор (и мы так и делали, когда экспериментировали с mmap 'ом и munmap 'ом). Такая подмена и называется interposition. Теперь что же у него, собственно, за нюансы есть.

int sum(int a, int b) < return a + b; >int test(int x, int y)

Тут при обычной компиляции вторая функция просто вернёт свой второй аргумент. А при компиляции с -fpic, вы сможете подменить sum , а значит оптимизации не будет. Чтобы это пофиксить, можно пометить sum как static (тогда эта функция будет у вас только внутри файла, а значит его не поменять извне) или как inline (потому что inline полагается на ODR, а значит функция должна быть везде одинаковой). Но есть ещё способ.
Linux по-умолчанию считает, что все функции торчат наружу (т.е. как __declspec(dllexport) в Windows). А можно их пометить, как не торчащие наружу, а нужные только для текущей компилируемой программы/библиотеки: __attribute__((visibility("hidden"))) .

На самом деле атрибут visibility может принимать несколько различных значений ( "default" , "hidden" , "internal" , "protected" ), где пользоваться сто́ит только вторым, потому что первый и так по-умолчанию, третий заставляет ехать все адреса, а четвёртый добавляет дополнительные аллокации.
При этом также есть различные ключи компиляции (типа -B symbolic), которые тем или иным образом немного меняют поведение, и пояснить разницу между ними всеми вам могут только избранные. И каждый из них может поменять вам поведение так, что вы легко выстрелите себе в ногу. То есть глобально в Linux поведение по умолчанию делаем вам хорошо, но, возможно, немного неоптимизированно, а когда вы начинаете использовать опции, вы погружаетесь в такую бездну, что ускорение заставляет вас очень много думать. Причём замедление от динамических библиотек может быть достаточно сильным: если взять компилятор clang-LLVM и компилировать при помощи его ядро Linux’а, то в зависимости от того, сложен ли clang-LLVM в один большой файл или разбит по библиотечкам, время компиляции отличается на треть. Поэтому ключи использовать придётся.
Один из самых безопасных из них — -fno-semantic-interposition. Это не то же самое, что и -fno-interposition потому, что бинарнику всё равно можно дать LD_PRELOAD , однако в нашем случае функция test будет оптимизирована.
Ещё один полезный ключ — -fno-plt. Он по сути вешает оптимизацию такую же, как __declspec(dllimport) , но на весь файл, поэтому функции, написанные в нём же, замедляются. Чтобы не замедлялись — visibility("hidden") . Вообще всё это детально и подробно рассказано не будет, если вам интересно, гуглите и читайте/смотрите по теме.
Впрочем, всякие -fno-plt и прочие штуки нужны нам тогда и только тогда, когда мы не включили linking-time оптимизации. В GCC все наборы ключей нафиг не нужны, если включить -flto. Так что в перспективе -flto и -fno-semantic-interposition — это единственное, что вам может быть нужно. Но только в перспективе.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *