понедельник, 29 августа 2011 г.

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

Это перевод How were DLL functions exported in 16-bit Windows? Автор: Реймонд Чен.

Весь смысл динамических библиотек (DLL) заключается в динамическом связывании. В то время как статические библиотеки встраиваются в конечный продукт, модуль, который использует динамическую библиотеку, просто говорит: "мне, пожалуйста, функцию X из модуля Y, спасибо". Эта техника имеет преимущества и недостатки. Одно из преимуществ - эффективное использование места на диске и в памяти, поскольку существует только одна копия модуля, вместо отдельных копий для каждого модуля. Второе преимущество - обновление DLL может быть выполнено без перекомпиляции всех программ, её использующих. С другой стороны, возможность менять функциональность также является и одним из недостатков DLL, поскольку одна программа может изменить DLL и вызвать этим каскадные эффекты в других клиентах DLL.

В любом случае, давайте начнём с того, как 16-битные Windows управляли импортом и экспортом. После этого мы посмотрим что было изменено при миграции на 32-битные Windows, а затем мы посмотрим на импорт с точки зрения компилятора.

16-битная DLL имела не одну, а целых три таблицы экспорта (на самом деле всё было немного сложнее, чем я описываю здесь, но я собираюсь опустить некоторые занудные детали, чтобы ваши головы не взорвались от переизбытка информации). Самая важная таблица представляла собой разрежённый массив функций, проиндексированных с 1("ordinal"). Это и есть таблица функций, которая является мастер-списком всех экспортируемых функций. Если вы запрашивали функцию по номеру (ordinal), этот номер искался именно в этой таблице. Физически таблица устроена не так просто - из-за разрежённости, но логически она выглядит так:

Номер Адреспрочее
102:0014...
204:0000...
502:02C8...

Первая колонка в таблице представляет собой номер функции, а вторая описывает размещение функции (заметьте, что в этой DLL нет функций 3 и 4).

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

...
ClipCursor16
GetCursorPos17
SetCapture18
...

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

Подождите-ка, я сказал "таблица экспортируемых имён"? Ох, извините, это было сильное упрощение. На самом деле было две таблицы экспортируемых имён: резидентная таблица имён и не резидентная таблица имён. Как следует из их названий, имена в резидентной таблице оставались в памяти всё время, пока DLL была загружена, а имена из не резидентной таблицы загружались в память только когда кто-то вызывал GetProcAddress (или её эквивалент). Это различие появилось из-за очень жёстких ограничений на память, с которыми Windows приходилось работать в те дни. К примеру, оконный менеджер имел около шестисот экспортируемых функций; если бы все эти функции были резидентными, то они занимали бы более 10 Кб памяти. Это означает, что вы тратите 4 процента вашей памяти (машины с 256 Кб), чтобы хранить вещи, которые вам не нужны 99% процентов времени.

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

Поскольку получение функции по имени было таким дорогим (загрузка не резидентной таблицы с диска), то все функции ОС экспортировались по именам и номеру. Импорт функции по номеру был предпочтителен - это позволяло полностью избегать таблицы имён.

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

Это был краткий обзор экспорта функций в 16-битных Windows. В следующий раз мы посмотрим на то, как они импортировались.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.