использовать функцию из библиотеки
предположим, все пути указаны верно. можно ли в своем приложении пользоваться функцией 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.8.3130
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Как вызвать функцию из статической библиотеки с
Здарвствуйте! Создал два проекта в 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
Указатель на функцию из библиотеки на Си в С++ класс?
Вопрос такой: как обрабатывать вызовы указателя на функцию в С++ классе?
MainWindow::MainWindow(QWidget *parent). < my = my_new(); my->send_sms = this->test; .
Но компилятор говорит, что функция test должна быть статическим членом класса, а если я её делаю статической, то не могу обрабатывать события из си библиотеки занося их результаты в поля класса окно через this. И всё что мне остаётся, это выводить какойнить MessageBox. Хотел сделать статический сигнал, но компилятор тоже ругается. Мне нужно присваивать результат компонентам окна, как быть?
- Вопрос задан более трёх лет назад
- 549 просмотров
Комментировать
Решения вопроса 1
Да, не можете. Метод может быть вызван только имея 2 указателя, указатель на объект и указатель на функцию, при этом тип метода содержит в себе тип объекта. Соответственно в C либе вы сможете сохранить только в 2 указателя, но вызвать из C метод вы не сможете.
Но можно сделать что-то типа
// C struct my_t < void (*send_sms)(int); void* context; >; void my_test(struct my_t *m) < (m->send_sms)(545, m->context); > // C++ void send_sms_helper(int value, void* context) < MainWindow *wnd = reinterpret_cast(context); wnd->test(value); > MainWindow::MainWindow(QWidget *parent). < my = my_new(); my->send_sms = send_sms_helper; my->context = this;
А send_sms_helper лучше сделать статическим в MainWindow.
Ответ написан более трёх лет назад
Нравится 1 1 комментарий

LittleBuster @LittleBuster Автор вопроса
Спасибо большое!
Ответы на вопрос 1

Станислав Макаров @Nipheris Куратор тега C++
Очень просто: чтобы поменьше влиять на остальной код на C++, который у вас уже написан, сделайте следующее:
1) заведите обычную функцию (НЕ член класса) send_sms, указатель на которую отдадите в либу;
2) сделайте в этой функции все, что необходимо по задаче (отправить смс? :));
3) реализуйте для MainWindow синглтон (проще) или сервис (правильнее), чтобы иметь возможность получить доступ из внешней функции send_sms к экземпляру окна;
4) у MainWindow дергайте метод test или любой другой, чтобы передать в него нужные данные. Если сделаете синглтон, будет что-то вроде этого: MainWindow::instance()->test(. );
Динамические и статические библиотеки.
Начнём с повторения того, что мы уже знаем. Каждый исходный файл транслируется в объектный файл, после чего все объектные файлы линкуются в программу. Но иногда бывает кусок кода, который хочется переиспользовать. Мы могли бы оттранслировать объектные файлы этого куска один раз, после чего сразу с ними компилировать. Но так оказалось, что уже существует механизм сгруппировать объектные файлы вместе, после чего отдать их линковщику. Называется этот механизм.
Статические библиотеки.
// 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 — это единственное, что вам может быть нужно. Но только в перспективе.