Как DLL функции экспортировались в 32-битной Windows?


Перевод с блога The Old New Thing. Оригинал здесь.

Разработчикам 32-битной Windows не надо было заботиться о том, как втиснуть все необходимое в 256 килобайт памяти. Так как модули в Win32 основаны на загрузке страниц в память при доступе к ним, то все, что необходимо сделать — это отобразить весь образ модуля в память, а затем обращаться к требуемым частям. Здесь нет различия между резидентными и нерезидентными именами; имена экспортируемых функций хранятся в образе модуля вместе с указателем (точнее, с относительным виртуальным адресом) на имя, хранящееся в таблице экспорта.

В отличие от 16-битной таблицы порядковых номеров, 32-битная таблица номеров не является разреженной. Если ваша DLL экспортирует две функции, одну с номером 10, а вторую — с номером 1000, то вы получите таблицу с 991 элементом, которая содержит два реальных указателя на функции и множество нулей. Таким образом, следует избегать больших промежутков в нумерации иначе память, занимаемая таблицей экспорта будет потрачена впустую.

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

Как и в 16-битной Windows, каждой именованной функции ставится в соответствие порядковый номер. Если программист не назначил номер в DEF-файле, редактор связей сделает это за него, и так же как и в 16-битной версии, значения могут отличаться от сборки к сборке. Однако, существует существенное отличие между этими двумя моделями. Вспоминаем, экспорт по имени в 16-битной Windows не приветствовался (по соображениям производительности) и, как результат, каждой экспортируемой функции явно назначался свой номер, что являлось оптимальным способом для динамического связывания данной функции. С другой стороны, экспорт по имени в 32-битной Windows является нормой, безо всякого явного указания номеров. Это означает, что номера для функций, экспортируемых по имени, не фиксированы. Например, посмотрите какие номера, назначались функции LocalAlloc из kernel32.dll в разных версиях:

Windows NT 3.1 314

Windows NT 3.5 372

Windows 95        501

Windows NT 4.0 407

Еще момент, у некоторых людей есть привычка копаться в библиотеках импорта, вероятно, потому что они не могут позаботится о том, чтобы скачать Platform SDK и получить настоящие библиотеки для импорта. Проблема с созданием библиотеки для импорта вручную заключается в том, что вы не можете утверждать наверняка, был ли номер, скажем, для функции LoadLibrary присвоен ей при помощи DEF файла (а значит он не будет меняться от сборки к сборке) или он был сгенерирован автоматически редактором связей (в этом случае он может меняться). Программы для генерации библиотек импорта могли бы выполнять эту работу так как следует, используя экспорт по имени, поскольку он работает в обоих случаях, но, по некоторым причинам, они вместо этого используют экспорт по номеру. (Возможно, это пережиток со времен 16-битной Windows, когда номера были более предпочтительны, чем имена.)

Этот неудачный подход в части инструментов для генерации библиотек импорта создал проблемы с совместимостью у команды DirectX. (Я не знаю, почему именно на них это повлияло в большей степени, чем на другие команды. Возможно потому что у разработчиков игрушек нет времени толком изучить особенности Win32, им хочется только писать свои игры.) Так как они использовали один из таких инструментов, они в итоге импортировали DirectX функции типа DIrectDrawCreate по номеру, а не по имени, и когда вышла новая версия DirectX, где компоновщик назначил этим именам другие номера, их программы стали прелестно падать. Команда DirectX вынуждена была обратиться к старым версиям DLL библиотек и выяснить, какие номера назначил компоновщик в прежней версии, а затем явно указать эти номера в DEF файле, чтобы в будущем они уже не менялись.

Есть также другие причины, почему вы не можете сгенерировать библиотеку импорта на основе DLL. Я затрону эти вопросы позже, когда расскажу о библиотеках импорта более детально.

Реклама
Запись опубликована в рубрике DLL. Добавьте в закладки постоянную ссылку.

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

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s