Импорт вызовов функций с помощью __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, при построении данной библиотеки.

См. также

Основные понятия

Импорт в приложение