быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
1

Создание *.dll: для чего нужен компилятору параметр -DBUILD_DLL? (использую MinGW)

07.02.2014, 17:22. Показов 8329. Ответов 46
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Не, реально, зачем он?

...Друзья! Много где в инете вы найдёте как кропать dll-ки, например тут. обратите внимание на командную строку:

Bash
1
gcc -c -DBUILD_DLL dllfct.c
Да, так вот. Много где приводят примеры с использованием этго параметра, но зачем он никто толком не знает.

А вот я не поленился и скомандовал --help для каждого из экзешников лежащих в папке mingw\bin- ни для одного из экзешников такого параметра нет. То есть он просто-напросто ни одним из экзешников не используется. Вот результаты, кому интересно, в архиве.

Так зачем же нужен параметр -DBUILD_DLL? Спасибо, кто откликнется.
Вложения
Тип файла: rar rez.rar (46.3 Кб, 14 просмотров)
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
07.02.2014, 17:22
Ответы с готовыми решениями:

AddItem и TObject. Для чего нужен второй параметр в приведенном коде
Непонял для чего нужен второй параметр и как его использовать?...

Подскажите для чего нужен WdfCoInstallerXXXX.dll
Коллеги, приветствую! Пишу мини-фильтр клавиатуры. При создание Empty WDF проекта драйвера, в...

Создание и использование dll- Для чего dllimport?
Всем доброго времени суток Вот пытаюсь разобраться в создании и использовании dll. По данной...

Нужно найти путь к компилятору MinGW в QT Creator
Всем привет,установил недавно Qt creator,но сразу же появилась проблема - прога не видит мой...

46
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
18.02.2014, 14:12 21
Author24 — интернет-сервис помощи студентам
Цитата Сообщение от kravam Посмотреть сообщение
DrOffset, опять вы думаете обо мне плохо. Там именно то и написано, зачем нужен модификатор
C++
1
__declspec(dllimport)
Но зачем нужно вот это вот:
C++
1
2
3
4
5
6
7
#ifdef BUILD_DLL
   // the dll exports
   #define EXPORT __declspec(dllexport)
#else
   // the exe imports
   #define EXPORT __declspec(dllimport)
#endif
там об этом ни слова.
Ну раз зачем нужен __declspec(dllimport) понятно, то ответ зачем нужна конструкция выше, уже не раз сюда постили. Первый ответ в теме ну и мой ответ, например.

Ну, могу еще раз.
Ситуация:
Я разработчик библиотеки (dll). Я собираю свою библиотеку из исходников и указываю макрос -DBUILD_DLL в мэйкфайле.
Конструкция выше срабатывает и проставляет всем функциям __declspec(dllexport), перед которыми стоял EXPORT (на самом деле EXPORT некорректный пример и только запутывает. Лучше использовать нотацию из моего примера. Или посмотреть на WIN32 API (например в файле winbase.h функции помечены как WINBASEAPI). Простановка __declspec(dllexport) помечает функции как экспортируемые. Смотрим MSDN:
Declaring functions as dllexport eliminates the need for a module-definition (.def) file, at least with respect to the specification of exported functions.
То есть это нужно, чтобы была возможность вызывать функции через границу модуля.

Ситуация 2:
Теперь я пользователь библиотеки. Я подключаю заголовочный файл вышеозначенной библиотеки, естественно никакой -DBUILD_DLL я не указываю в своем мэйкфайле. Автоматически начинает применяться вариант__declspec(dllimport). Зачем он нужен, ты уже понял.

ЗЫ. В линуксе, например, можно обходиться без всего этого. Там правила игры немного другие. Но есть похожие по форме приемы (в моем примере как раз один из вариантов). Подробнее тут.
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
18.02.2014, 17:24  [ТС] 22
Цитата Сообщение от DrOffset Посмотреть сообщение
Первый ответ в теме
нет, первый ответ был не про то. Первый ответ был про то, почему хорошо определять BUILD_DLL командой компилятору, а явно
C++
1
#define BUILD_DLL 1
-тоже хорошо, но уже не совсем. Вот про что был первый ответ.

+++++++++++++++++++++++++++++++++++++++==

Цитата Сообщение от DrOffset Посмотреть сообщение
Я разработчик библиотеки (dll). Я собираю свою библиотеку из исходников и указываю макрос -DBUILD_DLL в мэйкфайле.
Конструкция выше срабатывает и проставляет всем функциям __declspec(dllexport), перед которыми стоял EXPORT
Я же выше писал- ну зачем эти сложности? Ну написать перед каждой экспортируемой функцией EXPORT и ВСЁ, и не надо никаких ключей. А перед каждой импортируемой IMPORT и опять всё. Я же по условию сам пишу dll и сам определяю, какая пойдёт на экспорт, а какую я буду импортировать!
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
18.02.2014, 17:34 23
Цитата Сообщение от kravam Посмотреть сообщение
Я же выше писал- ну зачем эти сложности? Ну написать перед каждой экспортируемой функцией EXPORT и ВСЁ, и не надо никаких ключей. А перед каждой импортируемой IMPORT и опять всё.
Именно это и делается, только препроцессором и на автомате. Все соответствующие функции для пользователя библиотеки будут помечены как import. Все функции, которые разработчик пометил будут export. Заголовочный файл один, а импортировать или экспортировать через него будут функции переключается одни флажком.

Цитата Сообщение от kravam Посмотреть сообщение
Я же по условию сам пишу dll и сам определяю, какая пойдёт на экспорт, а какую я буду импортировать!
Ну да. И что? Нужно смотреть на библиотеку с двух сторон, с точки зрения пользователя и с точки зрения ее разработчика. Иначе при твоем подходе нужно будет иметь две версии заголовочного файла, один для экспорта, нужный при сборке либы, а другой для импорта, нужный при использовании либы. Это - сложнее.
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
18.02.2014, 18:36  [ТС] 24
Ничё не понял. Может, перейдём к конкретике? Я вот тут подумал- где может быть реальна полезна штука из первого поста. И написал такой код (хотя мне потом скажут, что примеры приводили, никто примеров полезности не приводил. Первый пример действительной полезности ниже)

C++
1
2
3
4
5
6
7
8
9
10
11
//A.cpp
#ifdef BUILD_DLL
   // the dll exports
   #define EXPORT __declspec(dllexport)
#else
   // the exe imports
   #define EXPORT __declspec(dllimport)
#endif
 
extern "C" EXPORT void functsia_0 () {}
extern "C" EXPORT void functsia_1 () {}
Bash
1
2
del A.dll libAdll.a
gcc -shared -o A.dll -Wl,--out-implib,libAdll.a A.cpp -D BUILD_DLL=1
Так, ну а потом я передумал и переписал всё это дело так:

C++
1
2
3
4
//A.cpp
#define EXPORT __declspec(dllexport)
extern "C" EXPORT void functsia_0 () {}
extern "C" EXPORT void functsia_1 () {}
Bash
1
2
del A.dll libAdll.a
gcc -shared -o A.dll -Wl,--out-implib,libAdll.a A.cpp
Даже я не знаю, что и добавить. Два кода. Второй проще причём лишь тем. что убрано лишнее, то есть ничё не добавилось, то есть ничё запоминать не надо, память головную (в смысле помнить, что чем заменить) загружать не надо. Файлы на выходе абсолютно одинаковые.

Так с каким пор стало правильным усложнять себе жизнь?
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
18.02.2014, 19:14 25
Лучший ответ Сообщение было отмечено kravam как решение

Решение

Цитата Сообщение от kravam Посмотреть сообщение
Так с каким пор стало правильным усложнять себе жизнь?
Ох... Неверно полагать, что все вокруг идиоты.

А теперь к конкретике.
Заголовочный файл где? Как использовать твою dll? Она же не сферическая в вакууме?
Не надо эту конструкцию писать в cpp, ей там действительно не место.
Эту конструкцию пишут в заголовочном файле, вместе с прототипом:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//A.h
#ifdef BUILD_DLL
   // the dll exports
   #define DLL_API __declspec(dllexport)
#else
   // the exe imports
   #define DLL_API __declspec(dllimport)
#endif
 
// вызывающего для С++ кода 
#ifdef __cplusplus 
extern "C" {
#endif
 
DLL_API void function_0 ();
DLL_API void function_1 ();
 
#ifdef __cplusplus 
}
#endif
Теперь код самой dll:
C++
1
2
3
4
5
6
7
8
9
10
11
12
//A.c
#include <A.h>
 
void function_0 ()
{
   // реализация
}
 
void function_1 ()
{
   // реализация 
}
Собираем библиотеку:
gcc -shared -o A.dll -Wl,--out-implib,libAdll.a A.с -DBUILD_DLL
Все. Я как разработчик библиотеки свою миссию выполнил.
Теперь я пользователь библиотеки.
Мой код:
C++
1
2
3
4
5
6
7
8
9
//main.cpp
#include <A.h> // тут они уже будут __declspec(dllimport)
 
int main()
{
    function_0();
    // ....
    return 0;
}
Собираем (заметь, никакого -DBUILD_DLL):
gcc main.cpp -lAdll -o myprog
1
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
18.02.2014, 20:11  [ТС] 26
А, ну вот, теперь вроде всё понятно. Оказывается, хидер работает на два фронта. Разработчик dll написал её и дал пользователю вместе с хидером и *.a. Пользователь, не думая о прототипах функций, подключает хидер и, не определяя BUILD_DLL вызывает функции (f()) и те оказываются import. Всё круто. Очень удобно для пользователя.

Небольшая тык скыть, догадка- получается, если мы имеем скомпилированные таким образом экзешники (к примеру, я пользуюсь билиотекой X.dll, невесть где взятой, и с её помощью что-то пишу), то если вдруг я заимею другую X.dll, то экзешники надо будет перекомпилировать, ибо, вызыванные в них функции вызываются по жёстко прописанным адресам, т. к.

C++
1
#define DLL_API __declspec(dllimport)
(ну или программно находить адрес и вызывать через адрес)

Ну что ж теперь всё ясно. Большое спасибо, огромный пласт тык скыть знаний раскрыт. Если что, спрошу здесь.
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
18.02.2014, 20:42 27
Цитата Сообщение от kravam Посмотреть сообщение
А, ну вот, теперь вроде всё понятно. Оказывается, хидер работает на два фронта. Разработчик dll написал её и дал пользователю вместе с хидером и *.a. Пользователь, не думая о прототипах функций, подключает хидер и, не определяя BUILD_DLL вызывает функции (f()) и те оказываются import. Всё круто. Очень удобно для пользователя.
Наконец-то Все-таки все тоже самое, что в последнем сообщении было написано в первом.
Вот смотри, я выделю все ключевые слова:
Цитата Сообщение от Nick Alte Посмотреть сообщение
Потому что ленивые программисты используют один и тот же заголовок и для построения самой DLL, где содержащиеся в ней функции описаны как экспортируемые (со спецификатором __declspec(dllexport) в случае Visual Studio), и для использующих ту DLL программ, где их описывают уже как импортируемые, с модификатором __declspec(dllimport). Переключение как раз осуществляется проверкой макроса BUILD_DLL. Менять исходный код, добавляя-убирая этот макрос в зависимости от того, что мы собираем, неудобно. А удобно привязывать этот макрос к проекту, добавляя его в параметры компилятора, а лучше в свойства проекта в IDE, откуда он всё равно попадёт в командную строку.
Разве нет?

Цитата Сообщение от kravam Посмотреть сообщение
Небольшая тык скыть, догадка- получается, если мы имеем скомпилированные таким образом экзешники (к примеру, я пользуюсь билиотекой X.dll, невесть где взятой, и с её помощью что-то пишу), то если вдруг я заимею другую X.dll, то экзешники надо будет перекомпилировать, ибо, вызыванные в них функции вызываются по жёстко прописанным адресам, т. к.
C++
1
#define DLL_API __declspec(dllimport)
Не так. Таблица импорта заполняется фактическими адресами при старте программы. При компиляции она только создается и заполняется "ссылками" на импортируемые функции. А вот таблица экспорта фиксирована.
Вот тут все очень подробно.
Рекомендую еще раз прочитать ссылку на мсдн. Для полноты картины.
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
18.02.2014, 21:57  [ТС] 28
Цитата Сообщение от DrOffset Посмотреть сообщение
Наконец-то Все-таки все тоже самое, что в последнем сообщении было написано в первом.
Соглашусь, но разве не для того форум и существует, чтобы чисто и честно, по-товарищески объяснить? Это вообще философский вопрос. Всё написано в книгах/учебниках. И всё написано правильно. А вот поди ж ты- откуда берутся двоечники (лентяев не берём в расчёт)? Есть такой феномен как непонимание, и никуда от него не деться

Цитата Сообщение от DrOffset Посмотреть сообщение
Не так. Таблица импорта заполняется фактическими адресами при старте программы.
Действительно, заполняется. И этот адрес используется при вызове функции, то есть у меня в отладчике так:

Assembler
1
2
mov eax, [адрес в таблице импорта, по которому лежит адрес функции]
call eax
Вот моё заблуждение. Я думал, что будет так:
Assembler
1
2
mov eax, [чётко прописанный адрес]
call eax

Не по теме:

...Откуда взялся "чётко прописанный адрес"? Ну, когда мы компильнули экзешник с модификатором __declspec(dllimport), вот компилятор его и прописал тогда чётко по месту, что называется. Но теперь, я вижу, это не так.

Соответсвующее поле сначала занято RVA имени функции, а при загрузке в память загрузчик прописывает туда её адрес, прямо как по цитатному:
заполняется загрузчиком перед запуском программы и до заполнения адресами функций содержит RVA имен этих функций в таблице просмотра импорта.
К слову, я это примерно знал, поизучав "От зелёного к красному."

Не по теме:


Кстати, я только щас понял, почему не должно быть чётко прописанных адресов в экзешнике- *dll-ки могут грузиться по разным адресам и адреса вызываемых функций будут разными.



+++++++++++++++++++++++++++++++++++++++++++++

Тут вопросов нет. Вопрос возникает совершено не там, откуда ждали. Смотрите. Если мы компилим функцию с модификатором __declspec(dllimport), загрузчик во время загрузки ищет адрес этой функции и его использует. А если компилим БЕЗ __declspec(dllimport), загрузчик... делает то же самое, только использует адрес по-другому. Но по большому счёту, вся-то и сложность и разница в поведении по-разному скомпиленных экзешников должна заключаться в том, КОГДА В ЭКЗЕШНИКЕ СТАНЕТ ИЗВЕСТЕН АДРЕС ФУНКЦИИ

И если адрес функции в ОБОИХ СЛУЧАЯХ становится известным только при загрузке программы в память, тогда мы В ОБОИХ случаях ("с" и "без" __declspec(dllimport)) можем использовать более эффективный код, а именно:

Assembler
1
2
mov eax, [адрес в таблице импорта, по которому лежит адрес функции]
call eax
А на самом деле компилятор без __declspec(dllimport) генерит такой код: добавляет лишний прыжок

Assembler
1
2
call 0x4000000
4000000: jmp DWORD PTR __imp_func1 //вот этот лишний прыжок
Когда мог превосходно сгенерить код, который я написал выше. Я так понимаю, он эффективнее прыжка, и это же написано в msdn.
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
18.02.2014, 22:19 29
Цитата Сообщение от kravam Посмотреть сообщение
А на самом деле компилятор без __declspec(dllimport) генерит такой код: добавляет лишний прыжок

Assembler
1
2
call 0x4000000
4000000: jmp DWORD PTR __imp_func1 //вот этот лишний прыжок
Когда мог превосходно сгенерить код, который я написал выше. Я так понимаю, он эффективнее прыжка, и это же написано в msdn.
Функция без __declspec(dllimport) может находиться как внутри твоей программы, так и в dll. Понять это без подсказки компилятор не способен. Поэтому чтобы разрешить ситуацию он генерит наиболее общий код, который возможно, он подходит и для этого случая и для того. Помечая функцию __declspec(dllimport) мы как бы намекаем ему, что она точно не в нашем модуле, позволяя исключить лишний шаг.
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
19.02.2014, 18:13  [ТС] 30
Честное слово, я не понял мысли. Во-первых, почему компилятор не способен это понять? Откуда такой вывод? Я в отладчике наблюдаю, что очень даже способен. Но это не главное. На этот вопрос, как и на возникающие далее вообще можно не отвечать. Мне кажется, они не имеют отношения к делу.

А вот это имеет. Псведокод:

Вызов функции из dll

Это назовём good_kod
//Используем __declspec(dllimport)
mov eax, адрес_в таблице_импорта
call [eax]
А это назовём bad_kod
//Не используем __declspec(dllimport)
call <прыгаем_на_некоторый_адрес_в_коде>
jmp [адрес_в таблице_импорта]
Так, а теперь что мешает компилятору заменить bad_kod на good_kod? Ничего! Пусть заменяет и раз и навсегда избавимся от ненужного дополнительного промежуточного прыжка. Не заменяет. Почему?
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
19.02.2014, 18:37 31
Цитата Сообщение от kravam Посмотреть сообщение
Так, а теперь что мешает компилятору заменить bad_kod на good_kod? Ничего! Пусть заменяет и раз и навсегда избавимся от ненужного дополнительного промежуточного прыжка. Не заменяет. Почему?
Сначала много всего написал, но по ссылке, которую ты почему-то упорно не хочешь изучить полностью, содержится ответ на твой вопрос.

call 0x4000000 ; The address of 'func1'.
Если func1 хранится в другой библиотеке DLL, компоновщик не может ее распознать, поскольку отсутствует информация об адресе func1. В 16-битных средах компоновщик добавляет адрес этого кода в список в EXE-файле, который во время выполнения будет заполнен загрузчиком верными адресами. В 32-битных и 64-битных средах компоновщик производит преобразователь, адрес которого неизвестен.
Однако если func1 - это функция не из dll, а определенная в нашей программе, то код останется неизменным.
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
19.02.2014, 18:51  [ТС] 32
Видите ли, в чём дело, некоторые вещи там непонятны. Но я просто не хотел на них акцентировать внимание, предполагая, что можно понять и без них. Ну, давайте всё подряд разбирать. Вот первая непонятная вещь:
"Если компилятор получает данные о том, что функция размещается в библиотеке DLL, он может создать непрямой вызов."
Что значит "может создать"? То есть может и не создать что ли? Это важно для ответа на мой вопрос?

Добавлено через 6 минут
И вообще, это пом-моему ерунда. Если непрямой вызов это bad_kod, то следует, что мы пишем
_declspec(dllimport)

А компилятор создаёт непрямой вызов вместо прямого, и это имея указание _declspec(dllimport)! Мда. Вот и договорились. Извините, я просто честно пытаюсь понять ссыль.
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
19.02.2014, 18:59 33
Цитата Сообщение от kravam Посмотреть сообщение
Видите ли, в чём дело, некоторые вещи там непонятны. Но я просто не хотел на них акцентировать внимание, предполагая, что можно понять и без них. Ну, давайте всё подряд разбирать. Вот первая непонятная вещь:

Что значит "может создать"? То есть может и не создать что ли? Это важно для ответа на мой вопрос?
Это как раз то, что называется implementation-defined.

Цитата Сообщение от kravam Посмотреть сообщение
А компилятор создаёт непрямой вызов вместо прямого, и это имея указание _declspec(dllimport)! Мда. Вот и договорились. Извините, я просто честно пытаюсь понять ссыль.
Компилятор не может создать прямой вызов из dll, т.к. адреса становятся известны только при старте приложения. Об этом же уже шла речь

Не по теме:


Совет: Учи английский. Быть нормальным программистом без знания английского невозможно. Очень многие ответы на все твои (даже будущие) вопросы находятся в англоязычных источниках. Намного более подробно, чем могут вместить формат форума и конкретно мои ответы. Т.к. расписывать все от запятой к запятой довольно утомительно и это, как ты видишь, никто кроме меня не решался в этой теме делать.

0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
19.02.2014, 19:09  [ТС] 34
Цитата Сообщение от DrOffset Посмотреть сообщение
Компилятор не может создать прямой вызов из dll, т.к. адреса становятся известны только при старте приложения. Об этом же уже шла речь
Э, нет, так не пойдёт. Почему тогда выше в этой же ссылке написано, что компилятор может создать непрямой вызов? Не "должен создать" или "создаёт", но "может создать".

Ну, хорошо implementation-defined. И вот, согласно implementation-defined компилятор не создаёт непрямой вызов... А какой же тогда он создаёт? А у нас их только два, прямой и непрямой. Непрямой мы откинули, остался прямой. А вы пишите что прямой он не может создать ни при каких условиях...

...Я сразу сказал, в этой статье нет ответа на мой вопрос. Там просто сказано, когда создаётся прямой, а когда непрямой. И чем они различаются (И термины до кучи небрежно использованы). А почему один нельзя другим заменить- не сказано, хотя и в том и другом случае адрес функции становится известно только при загрузке dll
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
19.02.2014, 19:38 35
Цитата Сообщение от kravam Посмотреть сообщение
А вы пишите что прямой он не может создать ни при каких условиях...
прямой он может создать только если известен адрес! Адрес известен если функция в том же приложении. Т.е. прямой вызов будет только если функция НЕ dll. Однако как я писал, компилятор не может определить сам где находится функция, поэтому он генерит прямой вызов, который потом делает прыжок на таблицу импорта (или не делает, если функция таки в нашем приложении). Если мы указываем import, то прямого вызова на jmp не происходит, а сразу идет непрямой из таблицы импорта.
Т.е., еще раз. Если функция (func1) в dll и не помечена import, то:
1) Генерится прямой вызов на адрес находящийся внутри приложения.
2) По этому адресу размещен прыжок на таблицу импорта.
3) Происходит непрямой вызов через отрезовленный адрес через таблицу импорта.

Если функция помечена import:
1) Сразу происходит непрямой вызов через таблицу импорта.

Теперь внимание! Это очень важно!
Я отключаю dll от проекта и создаю еще один cpp с реализацией функции funс1:
1) Генерится прямой вызов на адрес находящийся внутри приложения (func1).

Видишь, первые этапы одинаковы в случае когда функция не помечена как import.

Добавлено через 12 минут
Вот нашел качественную ссылку:
However, the matching double compile is not performed when consuming
libraries. It is therefore not possible to reliably distinguish if the
consumer is importing from a DLL or if it is going to use a static
library.
И действительно, дизайн языков С и С++ построен так, чтобы исключить лишние этапы компиляции из процесса сборки. Более того (сейчас это не всегда так) раньше и С и С++ использовали совершенно одинаковый линкер.
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
19.02.2014, 20:24  [ТС] 36
Цитата Сообщение от DrOffset Посмотреть сообщение
Однако как я писал, компилятор не может определить сам где находится функция, поэтому он генерит прямой вызов, который потом делает прыжок на таблицу импорта (или не делает, если функция таки в нашем приложении).
(Дальше не читал). Это НЕ ТАК. Я, кстати, и предложил с этого начать наш разговор: компилятор ОТЛИЧНО МОЖЕТ ОПРЕДЕЛИТЬ, где находится функция, в dll или в текущем модуле, и это, заметьте, безо всякого _declspec(dllimport).

Поехали:

C++
1
2
3
4
5
6
7
8
//DLL-ка
/*A.cpp*/
#include <stdio.h>
 
extern "C" __declspec(dllexport) void my_func_dll () {
 printf ("Я нахожусь в теле dll\n");
 getchar ();
}
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//экзешник
//B.cpp
#include <windows.h>
#include <stdio.h>
 
void my_func_exe () {
 printf ("Я нахожусь в теле экзешника\n");
 getchar ();
}
 
////////////////////////////////////////
 
extern "C" void my_func_dll ();
 
int main () {
 SetConsoleCP(1251);
 SetConsoleOutputCP(1251);
 
 printf ("%x", my_func_exe);
 printf ("%x", my_func_dll);
 
 getchar ();
 
 my_func_exe ();
 my_func_dll ();
 
 
 return 0;
}
компилим:
Bash
1
2
3
4
5
6
7
8
9
10
11
rem удаляем A.dll и B.exe для чистоты эксперимента
del A.dll B.exe
 
rem Кропаем libAdll.a и A.dll
gcc -shared -o A.dll -Wl,--out-implib,libAdll.a A.cpp
 
rem Кропаем B.exe
gcc -o B.exe B.cpp -L./ -lA
 
rem удаляем libAdll.a
del libAdll.a
Смотрим в отладчике:

Так вот. Первый вызов этот вызов my_func_exe (); и адрес его ИЗВЕСТЕН, он- 40138C и по этому адресу находится тело функции, и сейчас туда будет прыжок. А следующий вызов- это вызов my_func_dll (); через таблицу импорта и её адрес найден при загрузке программы.

Да, вы написали:
Цитата Сообщение от DrOffset Посмотреть сообщение
прямой он может создать только если известен адрес! Адрес известен если функция в том же приложении.
, то есть вроде как вы писали, так в отладчике и есть. Пока нормально всё. Но далее ваши же слова:
Цитата Сообщение от DrOffset Посмотреть сообщение
компилятор не может определить сам где находится функция,
Может. Он это сделал САМ, безо всяких моих указаний __declspec(dllexport). Он отлично определил, что одна из функций находится ТУТ, а другая ТАМ. Он это сделал сам. Я не говорил ему, вы не говорили, никто не говорил.

Не по теме:

Так оставь вызов my_func_exe () как есть, а вызов my_func_dll () замени на good_kod, всё равно загрузчик напишет адрес функции в таблице импорта.



Вот хотя бы даже с этим разобраться, а потом двигаться дальше.
Миниатюры
Создание *.dll: для чего нужен компилятору параметр -DBUILD_DLL? (использую MinGW)  
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
19.02.2014, 20:39  [ТС] 37
Исправление:
Цитата Сообщение от kravam Посмотреть сообщение
Он это сделал САМ, безо всяких моих указаний __declspec(dllimport).
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
19.02.2014, 20:47 38
Цитата Сообщение от kravam Посмотреть сообщение
(Дальше не читал)
....
Может. Он это сделал САМ, безо всяких моих указаний __declspec(dllexport). Он отлично определил, что одна из функций находится ТУТ, а другая ТАМ.
Вот зря ты не читал..
Это сделал не компилятор, а линкер! И сделал он то, что писано в МСДН, добавил jmp на таблицу импорта. Компилятор генерит объектники, а линкер все остальное. Но на этапе работы линкера уже поздно менять объектники. Он может только скомпоновать получившийся код, а никак не перестраивать внутри инструкции! Замена меток на адреса, вот простейшая его работа + генерация системозависимого кода. А вот понимание компилятором что нужно, т.е. вместо одной инструкции сгенерировать другую было бы возможно только в случае повторного шага компиляции с использованием уже собранной информации о других модулях. Вот тогда и только тогда именно компилятор бы сделать описанную тобой работу, заменив инструкцию вызова прямого адреса, а переход на таблицу импорта.

Читай, пожалуйста, до конца материал, прежде чем возражать...
0
быдлокодер
1724 / 911 / 106
Регистрация: 04.06.2008
Сообщений: 5,679
19.02.2014, 22:01  [ТС] 39
Цитата Сообщение от DrOffset Посмотреть сообщение
Вот зря ты не читал..
Я сейчас прочитал, объяснений нет. Написано, как обстоят дела, а почему они так обстоят- непонятно.

...Это делает линковщик:
Цитата Сообщение от DrOffset Посмотреть сообщение
Т.е., еще раз. Если функция (func1) в dll и не помечена import, то:
1) Генерится прямой вызов на адрес находящийся внутри приложения.
2) По этому адресу размещен прыжок на таблицу импорта.
3) Происходит непрямой вызов через отрезовленный адрес через таблицу импорта.
А это компилятор
Цитата Сообщение от DrOffset Посмотреть сообщение
Если функция помечена import:
1) Сразу происходит непрямой вызов через таблицу импорта.
Так?
0
18828 / 9831 / 2403
Регистрация: 30.01.2014
Сообщений: 17,267
19.02.2014, 22:26 40
Цитата Сообщение от kravam Посмотреть сообщение
...Это делает линковщик:
А это компилятор
Так?
Нет... Как-то ты странно читаешь. Я устал если честно.
Вопрос плевый на самом деле, разобраться в нем дело 30 минут... Тем более такое огромное количество инфы было сюда выложено.

Вот скажи, ты знаешь, что компилятор собирает каждый модуль отдельно, независимо? Именно поэтому у нас получаются объектные файлы на выходе cpp->obj (или o).
Понимаешь что такое компиляция?
Понимаешь что такое связывание (линковка)?

Еще раз, вся соль в том, что компилятор собирает модули независимо (это называется единицей трансляции (translation unit)).
И вся соль в том, что линкер не меняет сгенерированные объектники (т.к. там уже машинный код). Что на самом деле вызывается, функция из dll или функция из другого obj становится известно только на этапе линковки, а линкер не может менять машинный код, он может только, грубо говоря, поменять метки на фактические адреса. Поэтому единственное что может линкер - вставить в результирующий exe редирект (jmp) на таблицу импорта в случае, если функция не найдена в доступных obj.
И именно поэтому я говорю, что компилятор не МОЖЕТ узнать природу вызова, он может сгенерировать наиболее оптимальный код из доступной инфы. Когда мы говорим в коде import для функции, он получает больше инфы и генерит более оптимальный код.
0
19.02.2014, 22:26
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
19.02.2014, 22:26
Помогаю со студенческими работами здесь

Нужен glaux для mingw
glaux for mingw в гугле ничё не даёт ++++++++++++++++++++++++++++++++++ Теперь вот ещё, у...

Подлючение библиотеки sfml 2.0 sjlj к дефолтному компилятору mingw sjlj code::blocks 13.12 x32
Напишите, что неверно. 1. Новый пустой проект, потому что в code::blocks 13.12 не функционирует...

Для чего нужен Seed() и для чего его override?
Привет, ребята. Прочитал много разных статей про Seed(). И так и не могу понять зачем он нужен на...

MinGW - скомпилированная программа требует наличия libstdc++-6.dll и libgcc_s_seh-1.dll
Добрый день. При компиляции с помощью Visual Studio программа не требует никаких dll, можно ли в...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru