Импорт вызовов функций с помощью __declspec(dllimport)
Следующий пример кода демонстрирует способ использования _declspec(dllimport) для импорта вызовов функций из библиотеки DLL в приложение. Предположим, что func1 это функция, которая хранится в библиотеке DLL отдельно от EXE-файла, содержащего функцию main.
Без __declspec(dllimport) с данным кодом:
int main(void)
{
func1();
}
компилятор формирует код, имеющий следующий вид:
call func1
а компоновщик, в свою очередь, преобразует вызов подобным образом:
call 0x4000000 ; The address of 'func1'.
Если func1 хранится в другой библиотеке DLL, компоновщик не может ее распознать, поскольку отсутствует информация об адресе func1. В 16-битных средах компоновщик добавляет адрес этого кода в список в EXE-файле, который во время выполнения будет заполнен загрузчиком верными адресами. В 32-битных и 64-битных средах компоновщик производит преобразователь, адрес которого неизвестен. В 32-битной среде преобразователь выглядит следующим образом:
0x40000000: jmp DWORD PTR __imp_func1
В данном примере imp_func1 это адрес для ячейки func1 в таблице импортируемых адресов EXE-файла. Таким образом все адреса распознаются компоновщиком. Для правильной работы загрузчику необходимо только обновить таблицу импортируемых адресов EXE-файла во время загрузки.
Поэтому рекомендуется использовать __declspec(dllimport), поскольку в этом случае компоновщику не приходится создавать преобразователь без надобности. Преобразователи расширяют код (в системах RISC могут быть дополнительные инструкции) и могут ухудшить эффективность кэша. Если компилятор получает данные о том, что функция размещается в библиотеке DLL, он может создать непрямой вызов.
Таким образом, теперь следующий код:
__declspec(dllimport) void func1(void);
int main(void)
{
func1();
}
создает следующую инструкцию:
call DWORD PTR __imp_func1
В этом случае отсутствует преобразователь и инструкция jmp, что укорачивает код и делает его быстрее.
С другой стороны для вызовов функций внутри DLL нет необходимости использовать непрямой вызов. Адрес функции уже известен. Так как чтобы загрузить и хранить адрес функции перед непрямым вызовом требуется время и место, прямой вызов всегда бывает быстрее и компактнее. Рекомендуется использовать __declspec(dllimport) при вызове функций библиотеки DLL, размещенных не в самой библиотеке. Однако не используйте __declspec(dllimport) для функций, размещенных в библиотеке DLL, при построении данной библиотеки.