Компьютерный форум
Правила
Вернуться   Компьютерный форум > Форум программистов > Языки программирования > FAQ > Библиотека описаний > DLL
Перезагрузить страницу Как написать DLL с нормальными экспортами
Ответ
 
Опции темы Опции просмотра
  (#2 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:17

3.1.4. Visual Basic

Любителей этого языка нам придется огорчить – средствами чистого Visual Basic создание DLL общего назначения (с экспортируемыми функциями как у системных DLL Windows) невозможно.


Дэн Эпплман в своей монографии "The Visual Basic Programmer's Guide to the Win32 API" пишет: "Visual Basic не позволяет экспортировать функции, которые могут быть напрямую вызваны из других приложений. DLL, созданные VB, используют OLE-интерфейс. В тех случаях, когда вам действительно необходимо создать DLL, экспортирующую функции, вам нужно либо использовать дополнительные программные средства, дополняющие стандартные возможности VB (такие как, например, Desaware’s SpyWorks), либо более традиционные средства разработки и языки, предназначенные для этого.”

Visual Basic поддерживает создание только ActiveX DLL, т.е. DLL-библиотек, содержащих COM-объекты. Если просмотреть таблицу экспорта такой DLL, то окажется, что любая ActiveX DLL экспортирует только четыре функции:
  • DllRegisterServer
  • DllUnregisterServer
  • DllCanUnloadNow
  • DllGetClassObject

Поскольку сущность и применение технологии COM выходят за рамки данной статьи, мы отсылаем читателя к соответствующей литературе, например, к книге Дэйла Роджерсона "Основы COM".

Тем не менее, с помощью дополнительных утилит сторонних фирм все-таки оказывается возможным создать DLL библиотеку общего назначения. В качестве примера отправим читателя на www.vbadvance.com. Тем не менее, вам необходимо иметь в виду, что для использования такой DLL общего назначения, написанной на языке Visual Basic, обязательно потребуется исполняющая система VB – библиотеки msvbvm50.dll, msvbvm60.dll и т.д. – объемом ни
много ни мало 1,3 Мбайт!

Поскольку, по мнению авторов этой статьи, упомянутые утилиты сторонних фирм не являются в чистом виде средой Visual Basic, на этом мы и закончим обсуждение создания DLL общего назначения средствами Visual Basic.

Как видно из таблиц экспорта, декорирование имен функций (манглинг) средой Visual Basic не применяется.
Ответить с цитированием
  (#3 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:18

3.2. Как получить таблицу экспортируемых имен?

Все экспортируемые имена DLL компоновщик (линкер) помещает в специальную таблицу в секции экспорта PE-файла. Каждый элемент в этой таблице содержит имя экспортируемой функции или переменной, а также относительный адрес этой функции или переменной (RVArelative virtual address) внутри DLL-файла. Все списки имен сортируются по алфавиту.

Воспользовавшись утилитой dumpbin.exe с ключом -exports из состава Microsoft Visual Studio, вы можете увидеть содержимое секции экспорта DLL. Вот лишь небольшой фрагмент такого раздела для системной библиотеки kernel32.dll:

Код:
C:\WINDOWS\SYSTEM32>DUMPBIN -exports kernel32.dll

Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file C:\WINDOWS\SYSTEM32\kernel32.dll

File Type: DLL
Section contains the following exports for KERNEL32.dll

0 characteristics
3B7DDFD8 time date stamp Sat Aug 18 07:24:08 2001
0.00 version
1 ordinal base
928 number of functions
928 number of names

ordinal hint RVA      name

1    0 00012ADA ActivateActCtx
2    1 000082C2 AddAtomA
3    2 0000D39F AddAtomW
4    3 00065B2D AddConsoleAliasA
5    4 00065AF6 AddConsoleAliasW
6    5 00052A10 AddLocalAlternateComputerNameA
7    6 000528FB AddLocalAlternateComputerNameW
8    7 00060FFA AddRefActCtx
Остальной вывод для экономии места опущен.

Содержимое секции импорта выводится в четырех колонках:
  • ordinal - это номер (ординал) функции – см. раздел "Вызов по имени и по ординалу",
  • hint - так называемая "подсказка", ускоряющая поиск имени в таблице экспорта,
  • RVA - относительный адрес функции в файле (relative virtual address),

  • name - имя экспортируемой функции.

Из приведенной информации видно, что библиотека kernel32.dll экспортирует 928 имен функций – немало, не так ли?

<blockquote>Замечание:
В случае предпочтения продуктов компании Borland вы можете использовать утилиту tdump аналогичного назначения.</blockquote>
Ответить с цитированием
  (#4 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:19

3.3. Декорирование имен</span>

По ходу чтения этой статьи вы уже не раз сталкивались с понятием "декорирование имен". Настало время узнать об этом немного больше. Кроме того, здесь же вы узнаете, какие способы существует для того, чтобы избавиться от этого замечательного процесса (потому что использование декорированных имен не всегда бывает удобным и необходимым).

<blockquote>Замечание:
Определенную часть материала вы уже видели по ходу чтения предыдущих разделов. Получается это вследствие того, что способы избавления от декорирования напрямую связаны со способами экспорта из DLL (см. раздел <a href=\'index.php?act=findpost&pid=185343\'>"Различ ные способы экспорта"</a>). Но так ведь даже лучше – повторение, как говорится, - мать учения!</blockquote>

Итак, что такое декорирование имен?

Декорирование (decorate – украшать, награждать знаками отличия) – процесс преобразования имен с целью сохранения информации об этом имени.

Microsoft сообщает, что это способ сохранения дополнительной информации о типе – например, в случае определения функции или члена класса, можно некоторым образом "восстановить" (при помощи обратного процесса) по этому "странному" виду ее текстовое описание.

Но сразу же поспешим вас разочаровать – ни одна версия декорирования имен не имеет стандарта в своей основе (как, например, стандарт ISO/IEC 14482 определяет Стандарт языка С++). Поэтому декорированные имена от Visual Studio 2.0 могут быть вполне оправданно не быть понятными Visual Studio 4.0. Алгоритм декорирования может изменяться от версии к версии (о чем вас любезно предупредят в документации конкретной среды разработки).

Кстати, декорирование имен - это также и один из способов обеспечить дополнительную уникальность экспортируемых имен.

Допустим, мы захотим экспортировать перегруженную функцию getSum с двумя и тремя параметрами! Посмотрим, что у нас из этого получится:

Код:
ordinal hint RVA      name

1    0 00001253 ?getSum@@YAHHH@Z
2    1 00001334 ?getSum@@YAHHHH@Z
Как видим, две перегруженные функции различаются по именам. Первая из них – это наша "старая" getSum с двумя параметрами, а вторая – это та же getSum, но уже с тремя параметрами. Таким образом, мы всегда легко сможем использовать ОБЕ экспортированные функции из DLL. Другое дело, что делать это не всегда правильно! Об этом мы обязательно поговорим чуть позже.

Вам наверняка когда-нибудь захочется посмотреть – как расшифровать то или иное декорированное имя? И здесь вам поможет еще одна утилитка из состава Visual Studio под названием undname.exe. Она также является приложением консольного типа, поэтому не зверствует с точки зрения обучения управления ей. На вход поставляются декорированные имена, а она пытается их привести в нормальный вид. Параметр –f (используется только для версии из набора VC++ 6.0) заставляет производить полное де-декорирование.

Рассмотрим результаты ее работы на основе полученной выше информации.

Код:
C:\...osoft Visual Studio\Common\Tools>undname.exe ?getSum@@YAHHH@Z
Microsoft® Windows NT® Operating System
UNDNAME Version 5.00.1768.1Copyright © Microsoft Corp. 1981-1998

>> ?getSum@@YAHHH@Z == getSum
А теперь запустим ее с ключом –f сразу для двух идентификаторов:

Код:
C:\...osoft Visual Studio\Common\Tools>undname.exe -f ?getSum@@YAHHH@Z ?getSum@@YAHHHH@Z
Microsoft(R) Windows NT(R) Operating System
UNDNAME Version 5.00.1768.1Copyright (C) Microsoft Corp. 1981-1998

>> ?getSum@@YAHHH@Z == int __cdecl getSum(int,int)
>> ?getSum@@YAHHHH@Z == int __cdecl getSum(int,int,int)
Неправда, не так плохо? Таким образом, на основе декорированного имени можно получить:
  • точный тип возвращаемого значения;
  • точные типы и количество входных параметров;
  • правила вызова функции.

Разумеется, не всегда бывает удобным (особенно, в случае использования явной загрузки DLL) использовать имена, аналогичные приведенным выше. Поэтому процессом декорирования можно управлять (об этом мы поговорим немного позже). Но при этом вся сохраненная информация будет утеряна. Перегрузка функций также будет недоступна (что, в принципе, не так плохо, потому что другие языки могут не поддерживать такую перегрузку!).

Разумно предположить, что если используемые версии и типы компиляторов при сборке EXE- и DLL-файлов совпадают, то они (по умолчанию) будут понимать друг друга. И это действительно так (если бы это было иначе, то сложно даже представить какие ругательства постоянно обрушивались бы на создателя такого компилятора! (Что-нибудь вроде: "Будь проклят тот день, когда я сел за клавиатуру этого пылесоса!"). Таким образом, вы можете быть уверены, подготовив DLL в Visual C++, а затем, используя DLL в проекте этой же среды разработки, что все пройдет гладко.

Поэтому в этом случае о декорировании имен можно даже не вспоминать... если, конечно, вы не пишете системную DLL!

<blockquote>Замечание:
По сообщениям из MSDN алгоритм декорирования, который применялся в VC++ 2.0, был изменен в версии VC++ 4.2. Таким образом, lib-файлы в этом случае не будут понятны компилятором этих двух сред разработки.</blockquote>

Посмотрим таблицу экспорта одной из таких системных DLL (KERNEL.DLL):

Код:
...Microsoft Visual Studio .NET\Vc7\bin\dumpbin.exe" /exports kernel32.dll 
Microsoft (R) COFF/PE Dumper Version 7.00.9466
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file kernel32.dll

File Type: DLL

Section contains the following exports for KERNEL32.dll

00000000 characteristics
3D6DE616 time date stamp Thu Aug 29 13:15:02 2002
0.00 version
1 ordinal base
942 number of functions
942 number of names

ordinal hint RVA      name

1    0 000137E8 ActivateActCtx
2    1 000093FE AddAtomA
3    2 0000D496 AddAtomW
4    3 000607C5 AddConsoleAliasA
5    4 0006078E AddConsoleAliasW
6    5 0004E0A1 AddLocalAlternateComputerNameA
7    6 0004DF8C AddLocalAlternateComputerNameW
8    7 00035098 AddRefActCtx
9    8            AddVectoredExceptionHandler 
(forwarded to NTDLL.RtlAddVectoredExceptionHandler)
10    9 00036909 AllocConsole
11    A 000520CE AllocateUserPhysicalPages
12    B 0000DF51 AreFileApisANSI
13    C 0000261A AssignProcessToJobObject
14    D 00060CCE AttachConsole
...
Это лишь небольшой список из находящихся в этой DLL девятисот сорока двух (!) экспортируемых функций. Как видите, имена построены "человеческим" способом, чтобы такая DLL могла быть использована любыми средствами разработки без особых трудностей, не заботясь об их "происхождении".

<blockquote>Замечание:
Заботиться о происхождении все же приходится. Дело в том, что такие функции должны использовать стандартные правила вызова функции. Существует несколько различных стратегий:
  • стратегия управления стеком:
    • стек очищает вызываемая функция;
    • стек очищает вызывающая функция;
  • стратегия передачи параметров:
    • параметры передаются слева-направо;
    • параметры передаются справа-налево.

Стратегия вызова может задаваться явно при помощи нестандартных ключевых слов. Например, ключевое слово __fastcall заставляет передавать параметры через регистры процессора, а остаток помещать в стек справа-налево. Вызываемая функция обязана очистить стек перед возвратом управления. В свою очередь, __stdcall подразумевает размещение всех параметров в стеке справа-налево; вызываемая процедура также обязана очистить стек перед возвратом управления.

Несоответствие в правилах вызова функции немедленно приведет к краху приложения.</blockquote>

Как вы понимаете, для того чтобы ваша DLL могла бы быть использована другими приложениями, необходимо позаботиться:
  1. об отмене декорирования имен;
  2. о соответствии правил вызова (calling conventions).

Функции WinAPI используют соглашение __stdcall.

По умолчанию среды VC++ и Borland C++ Builder использует соглашения вызова __cdecl. Это позволяет, в частности, экспортировать функции с переменным числом параметров (подобно семейству функций xprintf).

Среда Borland Delphi по умолчанию использует соглашение register. В этом случае параметры передаются слева-направо, вызываемая функция очищает стековую область; по возможности, параметры передаются через регистры процессора.

Изменить параметры соглашения:
  1. Используя ключевые слова явного указания параметров соглашения (__stdcall, __cdecl и пр.):

    VC++:
    <div class=\'codetop\'>Код C++</div><div class=\'codemain\'>int __stdcall getSum(const int n1, const int n2);[/code]

    Delphi:
    <div class=\'codetop\'>Код Pascal</div><div class=\'codemain\'>function getSum(const n1, n2: integer): integer; cdecl; external 'XDll6.dll';[/code]
  2. Используя опции компилятора /Gd, /Gr, /Gz:
    • /Gd – опция по умолчанию; используется __cdecl для всех функций, за исключением членов-классов C++ и функций, помеченных __stdcall или __fastcall.
    • /Gr – используется __fastcall для всех функций, за исключением членов-функций классов C++ и функций, помеченных __cdecl или __stdcall. Для всех __fastcall-функций должны быть объявлены прототипы.
    • /Gz – используется __stdcall для всех С-функций, за исключением функций с переменным числом параметров и функций, помеченных __cdecl или __fastcall. Для всех __stdcall-функций должны быть объявлены прототипы.

    <blockquote>Замечание:
    Для автоматической настройки ключей используйте "Project Options->C/C++->Code Generation->Calling Conventions".</blockquote>

    И всего сказанного выше следует, что явные спецификаторы вызова функции отменяют действие опции компилятора.

    VC++ 6.0:
    Добавьте в "Project Settings->C/C++->Project Options" нужное вам значение (например, /Gz).

    VC++ 7.0:
    Установите значение поля "С/C++->Advanced->Calling Conventions".


Пример взаимодействия DLL, написанной на одном языке программирования, с клиентским приложением, написанным на другом, мы обязательно рассмотрим в разделе
<a href=\'index.php?act=findpost&pid=185635\'>"Исполь зование DLL, созданных в различных средах программирования"</a>.

Отмена процесса "украшения" имен может быть произведена одним из описанных ниже способов:
  1. Использование extern "C" __declspec(dllexport).
  2. Использование DEF-файла.
  3. Использование директивы #pragma.
  4. Использование настроек проекта.

<blockquote>Замечание:
Как вы поняли, на основе "украшенного" имени (ничего не скажешь – хороши
украшения!) можно определить всю информацию относительно любого экспортируемого
идентификатора. Почему, спросите вы, нельзя использовать эту информацию без
дополнительного использования h-файла? Причины, на наш взгляд, просты:
  1. нет стандартизации процесса декорирования – каждый декорирует, как хочет;
  2. не все компиляторы поддерживают декорирование в силу пункта а).
</blockquote>

Давайте вновь рассмотрим применение этих способов. По мере изложения материала мы будем приводить ссылки на соответствующие разделы, где работа с тем или иным вариантом изложена более подробно.

  1. Использование extern "C" __declspec(dllexport) .

    По ходу чтения статьи вы уже неоднократно сталкивались с использованием директивы __declspec(dllexport) для экспортирования имен. Оказывается, можно сделать немного больше, чтобы защитить наши имена от искажения. Для этого используется ключевое слово extern "C" совместно с использованием __declspec. При этом предотвращается искажение имен – так, как это делается в случае написания программы на языке C (не C++!). Но, соответственно, использовать эту директиву можно только в
    программах на C++. Поэтому обобщение для конструкции __declspec(dllexport) можно представить в таком виде:

    <div class=\'codetop\'>Код C++</div><div class=\'codemain\'>#ifdef XDLL6_EXPORTS
    #ifdef __cplusplus
    #define XDLL_API extern "C" __declspec(dllexport)
    #<span style=\'color:blue\'>else</span>
    #define XDLL_API __declspec(dllexport)
    #endif // __cplusplus
    #<span style=\'color:blue\'>else</span>
    #define XDLL_API __declspec(dllimport)
    #endif // XDLL6_EXPORTS[/code]

    Это позволяет использовать одну и ту же конструкцию не только в файлах проекта DLL и клиентских приложениях, но и в программах, написанных как на C++, так и на C. Ведь символ препроцессора __cplusplus определен только в проектах, написанных на языке C++, но не на языке C. Ну, а дальше уже дело техники! Подробно использование такого подхода для экспорта изложено в разделе <a href=\'index.php?act=findpost&pid=185343\'>"Различ ные способы экспорта"</a>. Разумеется, вам придется сделать соответствующую поправку исходного текста, приведенную выше.

    <blockquote>Замечание:
    Эта техника не работает, если вы используете спецификаторы вызова функции (calling conventions) в явном виде!

    Замечание:
    В случае использования языка C искажения имен не происходит в любом случае. Так что это можно также считать еще одним способом избавления от декорирования имен!

    Замечание:
    Даже несмотря на это, компилятор Borland (bcc32.exe) все равно искажает имена – добавляет ‘_’ (подчеркивание). Для того чтобы все-таки получить "правильное" имя, необходимо явным образом – в настройках среды или в опциях командной строки – указать компилятору, что добавлять символ подчеркивания не следует!</blockquote>
  2. Использование DEF-файла.

    Файл определений (DEF-файл) используется для дополнительного описания характеристик приложения. Он может состоять из различного набора секций. В случае работы с DLL нас особенно сильно заинтересует секция EXPORTS. Именно в ней описываются экспортируемые объекты DLL (в качестве таких объектов могут выступать функции и переменные). Кроме того, вместе с указанием имени идентификатора могут быть использованы необязательные поля (@ordinal, PRIVATE, DATA) для указания дополнительных характеристик объекта. В случае обнаружения файла определений (процесс использования подробно описан в разделе <a href=\'index.php?act=findpost&pid=185343\'>"Различ ные способы экспорта"</a>)
    линкер пытается определить относительный виртуальный адрес функции по ее идентификатору для последующей записи требуемой информации в LIB-файл. В случае обнаружения функции в раздел экспорта DLL помещается то имя, которое указано в DEF-файле. А именно это нам и нужно, чтобы окончательно избавиться от декорирования имен.

    <blockquote>Замечание:
    В общем случае, DEF-файл позволяет даже "переименовать" конкретную функцию. В этом случае в разделе EXPORTS должна появиться запись примерно следующего содержания:

    Код:
    EXPORTS
    NewFunc = getSum
    </blockquote>
  3. Использование директивы #pragma.

    Директива #pragma предполагает явное управление настройками проекта. Такой подход также рассматривался в разделе <a href=\'index.php?act=findpost&pid=185343\'>"Различ ные способы экспорта"</a>. Как там было сказано, он обладает определенным недостатком, наличие которого сводит практически на нет его применение. Дело в том, что для избавления от декорирования необходимо знать точное декорированное имя объекта:

    <div class=\'codetop\'>Код C++</div><div class=\'codemain\'>#pragma comment(linker, <span style=\'color:brown\'>"/export:getSum=?getSum@@YAHHH@Z"
  1. )[/code]

    В этом случае предполагается, что функции ?getSum@@YAHHH@Z должна быть экспортирована также и под именем getSum. В связи с тем, что имена функций слева и справа в достаточной степени "похожи", то линкер будет экспортировать только одно имя (то, что указано в левой части).
  2. Использование настроек проекта.

    Настройка линкера /export позволяет явно указать (в качестве параметра) те функции, которые вы хотите экспортировать. При этом указанное имя добавляется в раздел экспорта без каких-либо искажений. Использование такой техники изложено в разделе "Различные способы экспорта".

<blockquote>Замечание:
Как следует из всего написанного выше, проблемы декорирования - это проблемы совместимости с точки зрения использования. С точки зрения исполнения кода возникают уже совершенно другие проблемы (связанные, например, с несоответствием соглашений вызова, отличиями в моделях управления памятью и пр.).</blockquote>
Ответить с цитированием
  (#5 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:19

3.4. Экспорт/импорт по имени и по порядковому номеру</span>

Еще раз взглянем на прототип функции GetProcAddr (см. раздел <a href=\'index.php?act=findpost&pid=185519\'>"Как приложение загружает DLL. Явная загрузка."</a>)

<div class=\'codetop\'>Код C++</div><div class=\'codemain\'>FARPROC GetProcAddress(
HMODULE hModule, // HMODULE спроецированной DLL
LPCSTR lpProcName // название функции в формате ANSI или наименование переменной.
// Также вместо названия функции может быть указан
// ее порядковый номер
);[/code]

Как вы видели в описании функции GetProcAddr, в случае использования метода явной загрузки указатель на функцию можно получить, задав в качестве второго параметра этой функции не ее имя, а порядковый номер.

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

<div align=\'center\'>
Рисунок 3.2. Алгоритм получения виртуального адреса функции</div>

При этом в программе используется код, подобный показанному ниже:

<div class=\'codetop\'>Код C++</div><div class=\'codemain\'>...
// определяем при помощи typedef новый тип -
// указатель на вызываемую функцию.
// Очень важно знать типы и количество аргументов,
// а также тип возвращаемого результата
typedef int (*PGetSum)(const int, const int);
// пытаемся получить адрес функции getSum
PGetSum pGetSum = (PGetSum)GetProcAddress(hModule, "getSum");
// проверяем успешность получения адреса
_ASSERT(pGetSum != NULL);

// используем функцию так, словно мы сами ее написали
const int res = pGetSum(10, 20);
...[/code]

В этом случае по заданному значению hModule происходит обращение к таблице экспорта DLL, которая предварительно была спроецирована на адресное пространство вызывающего процесса. В этой таблице находятся символьные идентификаторы всех экспортируемых объектов, каждому из которых соответствует определенное значение RVA (relative virtual address).

<blockquote>Замечание:
Одна и та же функция может иметь несколько синонимов для ее вызова. В этом
случае в поле RVA будут одинаковые значения.</blockquote>

Методом построчного сравнения находится необходимый идентификатор, по нему получается соответствующее значение RVA. Это значение суммируется с базовым адресом, по которому DLL была спроецирована ранее – виртуальный адрес требуемой функции получен, теперь его можно использовать в программе для обращения к запрошенной функции.

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

<blockquote>Замечание:
Система оптимизирует процесс поиска необходимого имени, используя специальное
поле "hint", сохраняемое в таблице экспорта.</blockquote>

Чтобы сократить время поиска, можно воспользоваться так называемым порядковым номером функции в таблице экспорта. Этот порядковый номер можно наблюдать в выводе утилиты dumpbin в столбце "ordinal":

Код:
ordinal hint RVA      name

1    0 00008064 ?g_N@@3HA
2    1 000010C0 ?getSum@@YGHHH@Z
3    2 00008064 g_N
4    3 000010C0 getSum
Этот номер служит для прямого обращения к необходимому адресу RVA – следовательно, никакой перебор и построчное сравнение не требуются. В этом случае мы используем примерно такой код:

<div class=\'codetop\'>Код C++</div><div class=\'codemain\'>...
// определяем при помощи typedef новый тип -
// указатель на вызываемую функцию.
// Очень важно знать типы и количество аргументов,
// а также тип возвращаемого результата
typedef int (*PGetSum)(const int, const int);
// пытаемся получить адрес функции getSum
PGetSum pGetSum = (PGetSum)GetProcAddress(hModule, MAKEINTRESOURCE(4));
// проверяем успешность получения адреса
_ASSERT(pGetSum != NULL);

// используем функцию так, словно мы сами ее написали
const int res = pGetSum(10, 20);
...[/code]

Макрос MAKEINTRESOURCE используется для преобразования порядкового номера в строковый формат и передачи его в функцию GetProcAddress.

Вроде бы все замечательно – получили указатель на функцию с минимумом затрат и без особых проблем.

Но, как известно, в действительности не все так, как на самом деле. Опасность использования этого метода заключается в том, что этот номер может меняться от версии к версии. Скажем, мы решили расширить функциональность нашей библиотеки, добавив новую функцию getDiff.

Файл XDll.h.
<div class=\'codetop\'>Код C++</div><div class=\'codemain\'>...
XDLL_API int getDiff(const int n1, const int n2);
...[/code]

Файл XDll.cpp.
<div class=\'codetop\'>Код C++</div><div class=\'codemain\'>...
//////////////////////////////////////////////////////////////////////////
// getDiff function
int getDiff(const int n1, const int n2)
{
const int n = n1 - n2;
g_N = n;

<span style=\'color:blue\'>return
n;
}
...[/code]

<blockquote>Замечание:
В отличие от идентификатора экспортируемой переменной g_N и экспортируемой функции getSum, для функции getDiff мы определяем только одно (в данном случае - декорированное) имя. Как добавить альтернативные имена для экспортируемых идентификаторов (без декорирования) подробно рассказано в разделе "Декорирование имен".</blockquote>

И что мы видим?

Код:
ordinal hint RVA      name

1    0 000A9020 ?g_N@@3HA
2    1 00033D42 ?getDiff@@YAHHH@Z
3    2 0003350E ?getSum@@YAHHH@Z
4    3 000A9020 g_N
5    4 0003350E getSum
А видим мы следующее. В таблице экспорта появился новый идентификатор, который занял второй порядковый номер. В этом случае обращение по порядковому номеру 4 вновь выдаст нам некоторый адрес, но это будет уже адрес другого экспортируемого идентификатора (в данном случае – g_N). И об этом мы никак не узнаем, пока наша программа замечательно не рухнет.

<blockquote>Замечание:
Обрушение программы произойдет в случае несовпадения интерфейсов вызываемых
функций. Если же функции имеют совпадающие прототипы, значит, программа всего
лишь будет выдавать неправильные результаты!</blockquote>

Таким образом, при использовании записи вида MAKEINTRESOURCE(4) вместо указателя на функцию getSum мы получим указатель на экспортированную переменную g_N. Что случится с программой при попытке вызова функции по этому указателю – догадаться не сложно.

Если же использовать имя функции для получения ее виртуального адреса, то никаких проблем с расширением функциональности библиотеки не возникнет – мы всегда точно получим адрес именно той функции, которой мы ждем получить! Если же запрошенной функции не окажется в таблице экспорта библиотеки, функция GetProcAddress вернет нам значение NULL, что будет сигналом об отсутствии этой функции в списке экспортируемых идентификаторов.

Подытоживая сказанное выше, еще раз отметим, чем эти способы различаются, в чем их преимущества и недостатки?

Случай использования имени функции:
  • небольшое снижение быстродействия в связи с поиском и сравнением заданного имени функции в таблице IAT (import address table).
  • нет проблем использования функции в случае расширения функциональности библиотеки – каждому имени соответствует указатель на функцию, связанный с этим
    именем.

Случай использования порядкового номера:
  • быстрый поиск в таблице IAT – порядковый номер однозначно определяет смещение, по которому находится требуемый адрес вызываемой функции;
  • проблемы поиска функции в случае расширения функциональности библиотеки – при изменении версии библиотеки у пользователя не может быть твердой уверенности, что получен адрес именно той функции, вызов которой запланирован в программе.

Если вы уверены, что функциональность библиотеки изменяться в дальнейшем не будет, то вы можете смело использовать способ получения адреса функции по ее порядковому номеру. В случае же если вероятность изменения библиотеки в дальнейшем не нулевая, уверенности в правильности работы программ, использующих эту DLL посредством получения необходимых адресов функции через порядковые номера, не может быть никакой!
Ответить с цитированием
Ads.
  (#6 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:20

3.5. Как подключить к своему проекту чужую DLL?</span>

Подключить к своему проекту чужую DLL можно двумя способами – неявной загрузкой (implicit linking) – для этого потребуется просто подключить к проекту соответствующий *.lib-файл, или же явной загрузкой – вызовом функций LoadLibrary и GetProcAddress, с последующим вызовом функции FreeLibrary.

При использовании неявной загрузки DLL вам необходимо только подключить к проекту требуемый lib-файл и объявить необходимые функции (или переменные) как импортируемые.

Как вы уже знаете, lib-файл, поставляемый вместе с файлом динамической библиотеки, содержит всю необходимую линкеру информацию для автоматического (неявного) связывания имен вызываемых функций с соответствующими RVA (relative virtual address). Например, для уже изученной нами функции getSum из библиотеки XDll6 необходимо написать:

<div class=\'codetop\'>Код C++</div><div class=\'codemain\'>extern "C" __declspec(dllimport) int getSum(int, <span style=\'color:blue\'>int
);[/code]

Дальнейшее использование объявленной таким образом функции (в приведенном примере – функции getSum) не отличается от обычного. Всю необходимую работу по связыванию вызовов с кодом используемой DLL компоновщик (линкер) выполнит самостоятельно.

Строго говоря, даже объявление импортируемой функции с модификатором __declspec(dllimport) не обязательно - линкер все равно правильно выполнит всю необходимую работу по связыванию; однако компилятор сгенерирует более эффективный код, если ему заранее будет известно, что искомая функция импортируется из DLL.

Об использовании неявной загрузки также можно почитать во второй главе в разделе "Неявная загрузка".

<blockquote>Замечание:
При использовании неявной загрузки обратите внимание, что форматы lib-файлов, генерируемых различными компиляторами (в частности, компиляторами Borland и Microsoft), различаются. Поэтому возможен такой поворот событий, когда имеющийся у вас lib-файл ваш компилятор "не понимает". Для разрешения этой проблемы вам необходимо будет создать DEF-файл и сгенерировать новый lib-файл, "понятный" вашему компилятору. Как это сделать – подробно описано в разделе "Использование DLL, созданных различными средами программирования".</blockquote>

При использовании явной загрузки DLL вам необходимо точно знать имя искомой функции, соглашение вызова и набор передаваемых ей параметров - как правило, эта информация доступна из заголовочных файлов или документации. Весь процесс распадается на три шага:
  • 1. Вызов функции LoadLibrary и загрузка DLL.
  • 2. Получение адреса экспортируемой функции (или переменной) вызовом функции GetProcAddress.

Выполнение вашего кода...
  • 3. Вызов функции FreeLibrary и выгрузка DLL.

Пример явной загрузки DLL и вызова функции приведен в разделе "Явная загрузка" главы 2.

Хотя функция, используемая для получения адреса, и называется GetProcAddress, она может возвращать не только адрес экспортируемой функции, но и адрес любого экспортируемого объекта (например, переменной). Разумеется, в этом случае также необходимо использовать явное приведение типа.

По завершении использования библиотеки, необходимо закрыть дескриптор модуля и выгрузить библиотеку вызовом функции FreeLibrary. Не забывайте также проверять результат вызова FreeLibrary – функция вернет FALSE, если вызов завершился неудачно (например, если дескриптор по ошибке был уже закрыт где-то в другом месте).

И последнее замечание. Разумеется, если ваша среда поддерживает отложенную загрузку (и вам необходимо использовать именно этот способ работы с DLL), то вы тоже можете использовать и отложенную загрузку при работе с чужой библиотекой. Делается это аналогично тому, как это описано во второй главе в разделе "Отложенная загрузка".
Ответить с цитированием
  (#7 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:20

3.6.4. Visual Basic

И здесь нам опять придется огорчить поклонников этого языка – средствами "чистого" Visual Basic создание ресурсной DLL невозможно. Как уже упоминалось, Visual Basic поддерживает создание только ActiveX DLL, т.е. DLL-библиотек, содержащих COM-объекты (впрочем, ничто не мешает ActiveX DLL нести в себе ресурсы; однако речь в данном случае идет о чисто ресурсной DLL, т.е. DLL, не содержащей исполняемого кода).

Тем не менее, с помощью дополнительных утилит сторонних фирм все-таки оказывается возможным создать и ресурсную DLL. Отправим любознательного читателя на уже упомянутый сайт www.vbadvance.com.
Ответить с цитированием
  (#8 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:21

3.7. Как получить доступ к ресурсам?

Для доступа к ресурсам, содержащимся в динамически загружаемой библиотеке, используются следующие функции:
  • FindResource
  • FindResourceEx
  • LoadResource
  • LoadAccelerators
  • LoadBitmap
  • LoadCursor
  • LoadIcon
  • LoadMenu
  • LoadString

Итак, по порядку. Сначала необходимо найти требуемый ресурс в библиотеке. Для этого служат функции FindResource и FindResourceEx. Их прототипы представлены ниже.

Код:
HRSRC FindResource(
    HMODULE hModule, 
    LPCTSTR lpName, 
    LPCTSTR lpType
);

HRSRC FindResourceEx(
    HMODULE hModule,
    LPCTSTR lpName,
    LPCTSTR lpType,
    WORD wLanguage
);
где:
  • hModule – идентификатор модуля (например, полученный функцией LoadLibrary),
  • lpName - как уже упоминалось выше, доступ к ресурсам, в отличие от функций DLL, осуществляется исключительно по целочисленным идентификаторам. Идентификатор можно передать через параметр lpName двумя способами: либо в виде строки, содержащей символ '#' и десятичный идентификатор ресурса (например, #254), либо с помощью макроса MAKEINTRESOURCE (например, MAKEINTRESOURCE(254)). Последний способ предпочтительнее, так как он ускоряет доступ к ресурсам.
  • lpType – тип ресурса. Существует множество констант для основных типов ресурсов в Windows. Все эти константы имеют префикс RT_, например, RT_BITMAP или RT_DIALOG. Подробнее об этих константах – MSDN.
  • wLanguage – идентификатор языка ресурса. Для создания идентификатора используется макрос MAKELANGID:

Код:
WORD MAKELANGID
(
    USHORT usPrimaryLanguage,
    USHORT usSubLanguage
);
Константы для usPrimaryLanguage и usSubLanguage содержатся в соответствующих разделах MSDN.
  • MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) – текущий язык исполняемого модуля,
  • MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) – язык по умолчанию для текущего пользователя,
  • MAKELANGID(LANG_DEFAULT, SUBLANG_DEFAULT) – язык по умолчанию в системе.

Следующий шаг – загрузить найденный ресурс. Для этого служит функция LoadResource:

Код:
HGLOBAL LoadResource
(
    HMODULE hModule,
    HRSRC hResInfo
);
где:
  • hModule – идентификатор модуля DLL,
  • hResInfo – идентификатор ресурса, возвращенный функциями FindResourceили FindResourceEx.

Функция возвращает идентификатор (дескриптор) ресурса в памяти.

Для поиска и загрузки конкретных типов ресурсов используются соответствующие функции:

<div align='center'>Таблица 3.1. Функции для загрузки ресурсов</div>
<table width='80%' border align='center'><tr><td>Тип ресурса</td><td>Функция для его загрузки</td></tr><tr><td>Картинка (BITMAP)</td><td>LoadBitmap</td></tr><tr><td>Иконка (ICON)</td><td>LoadIcon</td></tr><tr><td>Курсор (CURSOR)</td><td>LoadCursor</td></tr><tr><td>Меню (MENU)</td><td>LoadMenu</td></tr><tr><td>Строка (STRING)</td><td>LoadString</td></tr><tr><td>Горячие клавиши (ACCELERATORS)</td><td>LoadAccelerators</td></tr></table>

Кроме того, функция LoadImage позволяет загружать картинки, иконки и метафайлы.

Подробные описания этих функций находятся в MSDN.

Для непосредственного доступа к двоичным данным ресурса используется функция LockResource:

Код:
LPVOID LockResource
(
    HGLOBAL hResData
);
Эта функция фиксирует положение ресурса в памяти и возвращает указатель на его данные.

После того как работа с ресурсом завершена, его следует выгрузить. Для этого используется функция FreeResource:

Код:
BOOL FreeResource
(
    HGLOBAL hgblResource
);
Однако эта функция сохраняется для совместимости с ранними версиями Windows, и Microsoft рекомендует использовать вместо нее следующие функции:

<div align='center'>Таблица 3.2. Функции для выгрузки ресурсов</div>
<table width='80%' border align='center'><tr><td>Тип ресурса</td><td>Функция для его выгрузки</td></tr><tr><td>Картинка (BITMAP)</td><td>DeleteObject</td></tr><tr><td>Иконка (ICON)</td><td>DestroyIcon</td></tr><tr><td>Курсор (CURSOR)</td><td>DestroyCursor</td></tr><tr><td>Меню (MENU)</td><td>DestroyMenu</td></tr><tr><td>Горячие клавиши (ACCELERATORS)</td><td>DestroyAcceleratorTable</td></tr></table>

Прототипы и подробные описания этих функций содержатся в MSDN.
Ответить с цитированием
  (#9 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:22

3.8. Базовый адрес загрузки DLL

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

Что произойдет, если две или более библиотек DLL имеют одинаковый базовый адрес загрузки? Очевидно, загрузчик не сможет поместить все библиотеки в одну и ту же область памяти. Поэтому загрузчик ОС загрузит по запрошенному адресу только одну библиотеку, а остальные переместит в свободные регионы.

<div align='center'>
Рисунок 3.5. Пример получения информации о базовом адресе загрузки DLL</div>

Обратите внимание на значение поля "Image Base" на этом рисунке – это и есть базовый адрес загрузки.

По умолчанию, большинство компоновщиков устанавливают базовый адрес загрузки DLL в 0x10000000. Можно поспорить, что сегодня как минимум половина библиотек DLL в мире пытается загрузиться по этому адресу.
Ответить с цитированием
  (#10 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:22

3.9. А как его изменить?

Существует два способа изменения базового адреса загрузки библиотеки DLL.

Первый состоит в использовании утилиты rebase.exe из Platform SDK. Эта утилита имеет множество различных параметров, но лучше всего вызывать ее, задав ключ командной строки /b, базовый адрес загрузки и имя соответствующей DLL. Если в командной строке утилиты rebase.exe задаются сразу несколько имен DLL, то их базовые адреса будут изменены так, что они будут последовательно загружены вплотную друг к другу, начиная с заданного адреса. Пример командной строки:

Код:
rebase.exe /b 0x12000000 MyDLL.dll
Второй способ изменения адреса загрузки DLL - явно задать адрес загрузки при компоновке. В Visual Basic этот адрес задается в поле "DLL Base Address" на вкладке "Compile", в Borland C++ Builder и Delphi – в поле "Image base" вкладки "Linker", а в Visual C++ - в поле "Base Address" на вкладке "Link" или же параметром командной строки компоновщика link.exe после ключа /BASE.

<div align='center'>
Рисунок 3.6. Настройка базового адреса загрузки DLL в Visual C++</div>
Ответить с цитированием
  (#11 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:22

3.10. Зачем это нужно?

Зачем нужно изменять базовый адрес загрузки библиотеки DLL?

Для этого есть две достаточно веские причины.

Первая из них состоит в том, что известный адрес загрузки DLL облегчает поиск причины ошибки при сбое в приложении. Как правило, в подобном случае система выводит окно с маловразумительным сообщением типа "Инструкция по адресу 0x105C0F23 попыталась обратиться по адресу 0x00000010: память не может быть read."

Если Ваше приложение загружает десяток-полтора библиотек DLL, то как вы по адресу ошибки 0x105C0F23 установите, в какой именно библиотеке возник сбой, если все они имеют один и тот же базовый адрес загрузки (0x10000000)? Как сказано выше, в такой ситуации загрузчик операционной системы переместит каждую DLL в свободный регион памяти, но как вы сможете установить, какая именно DLL была отображена на адрес (например) 0x105C0000? Совсем иначе выглядит ситуация с поиском ошибки, если вы знаете, какая именно DLL должна загружаться по базовому адресу 0x105C0000 - найти причину сбоя будет намного легче.

Вторая причина состоит в том, что перемещение библиотеки DLL замедляет загрузку и запуск приложения. Во время перемещения загрузчик операционной системы должен считать из соответствующей секции DLL нужную для этого информацию, обойти все участки кода, которые обращаются к адресам внутри DLL, и изменить их, поскольку теперь библиотека находится в другой области памяти. Если в приложении несколько конфликтов адресов загрузки, то запуск приложения иногда может замедлиться более чем вдвое.
Ответить с цитированием
  (#12 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:22

3.11. Порядок поиска файла DLL при загрузке

Когда загрузчик операционной системы пытается подключить (спроецировать) файл динамически загружаемой библиотеки на адресное пространство процесса, он проводит поиск DLL-файла в каталогах в строго определенной последовательности:
  1. Каталог, содержащий ЕХЕ-файл,
  2. Текущий каталог процесса,
  3. Системный каталог Windows (например, "C:\Windows\System32"),
  4. Основной каталог Windows (например, "C:\Windows"),
  5. Каталоги, указанные в переменной окружения PATH.

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

Когда разрабатывались первые версии Windows, оперативная намять и дисковое пространство были крайне дефицитным ресурсом, так что Windows была рассчитана на предельно экономное их использование - с максимальным разделением между потребителями. В связи с этим Microsoft рекомендовала размещать все модули, используемые многими приложениями (например, библиотеку С/С++ и DLL, относящиеся к MFC), в системном каталоге Windows, где их можно было легко найти.

Однако со временем это вылилось в серьезную проблему: программы установки приложений то и дело перезаписывали новые системные файлы старыми или не полностью совместимыми (см. также раздел "DLL Hell и конфликты версий"). Из-за этого уже установленные приложения переставали работать.

Поэтому Microsoft сменила свою позицию на прямо противоположную: теперь она настоятельно рекомендует размещать все файлы приложения в своем каталоге и ничего не трогать в системном каталоге Windows. Тогда Ваше приложение не нарушит работу других программ, и наоборот. А ваши версии необходимых вашему приложению динамических библиотек заведомо будут найдены и подключены к адресному пространству вашего процесса раньше, чем соответствующие библиотеки из системного или общего каталогов Windows.
Ответить с цитированием
Ads
  (#13 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:23

3.12. А как изменить порядок поиска?</span>

Существует два способа изменения порядка поиска файла DLL при загрузке.

Первый из них применим при явной загрузке DLL, и заключается в вызове функции LoadLibraryEx с флагом LOAD_WITH_ALTERED_SEARCH_PATH. Этот флаг изменяет алгоритм, используемый функцией LoadLibraryEx при поиске DLL-файла. Обычно поиск осуществляется так, как описано выше. Однако если данный флаг установлен, функция ищет файл, просматривая каталоги в таком порядке:
  1. Каталог, заданный в napaмeтре pszDLLPathName функции LoadLibraryEx,
  2. Текущий каталог процесса,
  3. Системный каталог Windows,
  4. Основной каталог Windows,
  5. Каталоги, перечисленные в переменной окружения PATH.

Второй способ применим как для неявно загружаемых DLL, так и для явно загружаемых. Он заключается в создании особого строкового параметра в ключе реестра

Код:
HKEY_LOCAL_MACHTNE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
<div align=\'center\'>
Рисунок 3.6. Пример списка известных DLL</div>

В этом ключе реестра описываются так называемые известные DLL (known DLLs). Они ведут себя точно так же, как и любые другие DLL - с тем исключением, что система всегда ищет их в одном и том же каталоге. Ключ содержит набор параметров, имена которых совпадают с именами известных DLL. Значения этих параметров представляют собой строки, идентичные именам параметров, но дополненные расширением .dll. Когда вы вызываете LoadLibrary или LoadLibraryEx, каждая из них сначала проверяет, указано ли имя DLL вместе с расширением .dll. Если нет, поиск DLL ведется по обычным правилам.

Если расширение .dll указано, функция его отбрасывает и ищет в разделе реестра KnownDLLs параметр, имя которого совпадает с именем DLL. Если его нет, вновь применяются обычные правила поиска.

А если он есть, система считывает значение этого параметра и пытается загрузить заданную в нем DLL. При этом система ищет DLL в каталоге, на который указывает значение, связанное с параметром реестра DllDirectory. По умолчанию в Windows 2000 и Windows XP параметру DllDirectory присваивается значение %SystemRoot%\System32. Таким образом, для изменения порядка поиска файла DLL (например, MyOwnDLL.dll) описанным способом необходимо:
  • создать в описанном ключе реестра строковый параметр с именем MyOwnDLL,
  • установить значение этого параметра, например, в SomeOtherDLL.dll.

Теперь, если написать следующий код:

<div class=\'codetop\'>Код C++</div><div class=\'codemain\'>HMODULE hDll = LoadLibrary(<span style=\'color:brown\'>"MyOwnDLL.dll"
);[/code]

то загрузчик ОС загрузит библиотеку SomeOtherDLL.dll из каталога (например) C:\Windows\System32.

Если вы все еще не поняли, как это происходит, перечитайте этот раздел еще раз.
Ответить с цитированием
  (#14 (permalink)) Старый
Коллектив авторов
Guest
 
Сообщений: n/a
По умолчанию 06.08.2009, 15:25

3.13. DLL Hell и конфликты версий

Ничто не постоянно, и то, что сегодня кажется самым современным, уже завтра совершенно устаревает и отмирает. Не избегают этой судьбы и динамически компонуемые библиотеки. Однако DLL после смерти не попадают в свой ад (DLL Hell). Туда попадает программист, который их использует.
<blockquote>Замечание:
Рекомендуем отыскать забавную статью в Интернете по данной тематике "Истории программных эволюций
от Microsoft".</blockquote>
Представьте себе ситуацию: вы разрабатываете библиотеку, реализующую некую безумно полезную функциональность (БПФ). Вашу библиотеку замечают, оценивают, и многие разработчики ПО начинают ей пользоваться. Отпраздновав свой успех, вы начинаете думать: а что бы еще такого хорошего запихнуть в библиотеку?

Вам приходит блестящая идея, и в результате вы выпускаете версию 2.0 своей библиотеки, которая содержит уже две безумно полезных функциональности: БПФ-1 и БПФ-2. Конечно же, вы рассылаете всем своим клиентам обновленную версию, а они, в свою очередь, выкладывают у себя на сайтах соответствующие обновления.

Но представим себя на месте пользователя. Он купил два программных продукта и по очереди устанавливает их на свой компьютер. В первую очередь он ставит программу, которая поставляется с новой библиотекой версии 2.0. Все работает, и все счастливы. А затем неразумный пользователь ставит еще одну программу устаревшей версии, которая использует библиотеку версии 1.0.

И эта программа, в приступе старческого маразма перезаписывает новенькую библиотеку 2.0 ее устаревшей бабушкой 1.0. А теперь угадайте с трех попыток, что будет, когда первая программа попытается обратиться к вашей библиотеке за БПФ-2? Результат будет печальный.

Суть проблемы в том, что в Windows отсутствует системное средство контроля версий библиотек. Так что проверить, какая из двух DLL старше, практически не представляется возможным. Конфликты различных версий библиотек DLL и все связанные с этим проблемы получили название DLL Hell ("Ад DLL").

Компания Microsoft пыталась решить эту проблему, введя ресурс VERSIONINFO в структуру DLL. Однако это достаточно слабое решение.

Во-первых, это не решает проблем со старыми библиотеками, созданными еще до появления этого ресурса.

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

И, наконец, не редок случай, когда именно более новые версии библиотек являются источниками ошибок. В таком случае попытка подсунуть программе старую и надежную DLL окончатся крахом.
Ответить с цитированием
Ads
Ответ

Опции темы
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Trackbacks are Вкл.
Pingbacks are Вкл.
Refbacks are Выкл.


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Как написать ряд 0,-2*x,4*x^2,(-2)^3*x^3,.......,(-2)^n*x^n Alenka-dev Lisp 0 05.05.2010 07:10
Веб-бот как его написать tsi Общие вопросы создания ПО 3 11.02.2010 19:15
Как написать TSR программу в С++ kosetsky Вопросы начинающих программистов 1 03.10.2009 21:25
Написать программу blackcat Задания за деньги 3 20.09.2009 12:12
Как написать RLE архиватор imported_Witcher Delphi 1 15.09.2009 20:57
Калькулятор как его написать KOV Visual Basic 8 22.09.2008 13:16
Как написать? Angel07 Prolog 0 10.11.2007 13:39
Как это написать на VB? SOm26 Visual Basic 0 16.03.2007 04:48
Как написать DLL для 1С Root_in C++ Builder 1 22.11.2005 12:09
Как написать DLL ? HeiHeShang Prolog 7 28.09.2005 16:28
Как написать AVI-проигрыватель Anonymous Visual C++ 1 29.01.2004 16:11
Как написать CGI Anonymous Некоммерческие проекты 1 02.10.2002 16:15



Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.
Нardforum.ru - компьютерный форум и программирование, форум программистов