DLL — Введение
Динамическое связывание — это механизм, который связывает приложения с библиотеками во время выполнения. Библиотеки остаются в своих собственных файлах и не копируются в исполняемые файлы приложений. Библиотеки DLL ссылаются на приложение при запуске приложения, а не при его создании. DLL могут содержать ссылки на другие DLL.
Часто библиотеки DLL помещаются в файлы с различными расширениями, такими как .EXE, .DRV или .DLL.
Преимущества DLL
Ниже приведены несколько преимуществ наличия файлов DLL.
Использует меньше ресурсов
DLL файлы не загружаются в оперативную память вместе с основной программой; они не занимают места, если не требуется. Когда требуется файл DLL, он загружается и запускается. Например, пока пользователь Microsoft Word редактирует документ, файл DLL принтера не требуется в оперативной памяти. Если пользователь решает распечатать документ, то приложение Word вызывает загрузку и запуск DLL-файла принтера.
Способствует модульной архитектуре
DLL помогает продвигать разработку модульных программ. Это помогает вам разрабатывать большие программы, которые требуют многоязычных версий, или программу, которая требует модульной архитектуры. Примером модульной программы является бухгалтерская программа, имеющая много модулей, которые могут динамически загружаться во время выполнения.
Помогите легко развернуть и установить
Когда функция в DLL нуждается в обновлении или исправлении, развертывание и установка DLL не требует повторного связывания программы с DLL. Кроме того, если несколько программ используют одну и ту же DLL, все они получают выгоду от обновления или исправления. Эта проблема может возникать чаще, когда вы используете сторонние библиотеки DLL, которые регулярно обновляются или исправляются.
Приложения и библиотеки DLL могут автоматически ссылаться на другие библиотеки DLL, если связь с DLL указана в разделе ИМПОРТ файла определения модуля как часть компиляции. Иначе, вы можете явно загрузить их, используя функцию Windows LoadLibrary.
Важные DLL-файлы
-
COMDLG32.DLL — Управляет диалоговыми окнами.
-
GDI32.DLL — содержит множество функций для рисования графики, отображения текста и управления шрифтами.
-
KERNEL32.DLL — содержит сотни функций для управления памятью и различными процессами.
-
USER32.DLL — содержит множество функций интерфейса пользователя. Участвует в создании программных окон и их взаимодействиях друг с другом.
COMDLG32.DLL — Управляет диалоговыми окнами.
GDI32.DLL — содержит множество функций для рисования графики, отображения текста и управления шрифтами.
KERNEL32.DLL — содержит сотни функций для управления памятью и различными процессами.
USER32.DLL — содержит множество функций интерфейса пользователя. Участвует в создании программных окон и их взаимодействиях друг с другом.
DLL — Как написать
Сначала мы обсудим проблемы и требования, которые вы должны учитывать при разработке своих собственных библиотек DLL.
Типы DLL
Когда вы загружаете DLL в приложение, два метода связывания позволяют вам вызывать экспортированные функции DLL. Два метода связывания:
- динамическое связывание во время загрузки и
- динамическое связывание во время выполнения.
Динамическое связывание во время загрузки
При динамическом связывании во время загрузки приложение выполняет явные вызовы экспортируемых функций DLL, таких как локальные функции. Чтобы использовать динамическое связывание во время загрузки, предоставьте файл заголовка (.h) и файл библиотеки импорта (.lib) при компиляции и компоновке приложения. Когда вы сделаете это, компоновщик предоставит системе информацию, необходимую для загрузки DLL, и разрешит расположение экспортированных функций DLL во время загрузки.
Динамическое связывание во время выполнения
При динамическом связывании во время выполнения приложение вызывает либо функцию LoadLibrary, либо функцию LoadLibraryEx, чтобы загрузить DLL во время выполнения. После успешной загрузки DLL вы используете функцию GetProcAddress, чтобы получить адрес экспортированной функции DLL, которую вы хотите вызвать. Когда вы используете динамическое связывание во время выполнения, вам не нужен файл библиотеки импорта.
В следующем списке описаны критерии приложения для выбора между динамическим связыванием во время загрузки и динамическим связыванием во время выполнения:
-
Производительность при запуске: если важна начальная производительность при запуске приложения, следует использовать динамическое связывание во время выполнения.
-
Простота использования : при динамическом связывании во время загрузки экспортированные функции DLL похожи на локальные функции. Это помогает вам легко вызывать эти функции.
-
Логика приложения . При динамическом связывании во время выполнения приложение может выполнять ветвление для загрузки различных модулей по мере необходимости. Это важно при разработке многоязычных версий.
Производительность при запуске: если важна начальная производительность при запуске приложения, следует использовать динамическое связывание во время выполнения.
Простота использования : при динамическом связывании во время загрузки экспортированные функции DLL похожи на локальные функции. Это помогает вам легко вызывать эти функции.
Логика приложения . При динамическом связывании во время выполнения приложение может выполнять ветвление для загрузки различных модулей по мере необходимости. Это важно при разработке многоязычных версий.
Точка входа в DLL
Когда вы создаете DLL, вы можете дополнительно указать функцию точки входа. Функция точки входа вызывается, когда процессы или потоки присоединяются к DLL или отсоединяются от DLL. Вы можете использовать функцию точки входа для инициализации или уничтожения структур данных в соответствии с требованиями DLL.
Кроме того, если приложение является многопоточным, вы можете использовать локальное хранилище потоков (TLS) для выделения памяти, которая является частной для каждого потока в функции точки входа. Следующий код является примером функции точки входа DLL.
BOOL APIENTRY DllMain( HANDLE hModule, // Handle to DLL module DWORD ul_reason_for_call, LPVOID lpReserved ) // Reserved { switch ( ul_reason_for_call ) { case DLL_PROCESS_ATTACHED: // A process is loading the DLL. break; case DLL_THREAD_ATTACHED: // A process is creating a new thread. break; case DLL_THREAD_DETACH: // A thread exits normally. break; case DLL_PROCESS_DETACH: // A process unloads the DLL. break; } return TRUE; }
Когда функция точки входа возвращает значение FALSE, приложение не запускается, если вы используете динамическое связывание во время загрузки. Если вы используете динамическое связывание во время выполнения, не будет загружаться только отдельная DLL.
Функция точки входа должна выполнять только простые задачи инициализации и не должна вызывать какие-либо другие функции загрузки или завершения DLL. Например, в функции точки входа не следует прямо или косвенно вызывать функцию LoadLibrary или функцию LoadLibraryEx . Кроме того, вы не должны вызывать функцию FreeLibrary, когда процесс завершается.
ВНИМАНИЕ : В многопоточных приложениях убедитесь, что доступ к глобальным данным DLL синхронизирован (потокобезопасен), чтобы избежать возможного повреждения данных. Для этого используйте TLS, чтобы предоставить уникальные данные для каждого потока.
Экспорт функций DLL
Чтобы экспортировать функции DLL, вы можете добавить ключевое слово функции в экспортированные функции DLL или создать файл определения модуля (.def), в котором перечислены экспортированные функции DLL.
Чтобы использовать ключевое слово функции, вы должны объявить каждую функцию, которую вы хотите экспортировать, со следующим ключевым словом:
__declspec(dllexport)
Чтобы использовать экспортированные функции DLL в приложении, вы должны объявить каждую функцию, которую вы хотите импортировать, с помощью следующего ключевого слова:
__declspec(dllimport)
Как правило, вы использовали бы один заголовочный файл, имеющий оператор определения и оператор ifdef, чтобы разделить оператор экспорта и оператор импорта.
Вы также можете использовать файл определения модуля для объявления экспортированных функций DLL. Когда вы используете файл определения модуля, вам не нужно добавлять ключевое слово function к экспортируемым функциям DLL. В файле определения модуля вы объявляете оператор LIBRARY и оператор EXPORTS для DLL. Следующий код является примером файла определения.
// SampleDLL.def // LIBRARY "sampleDLL" EXPORTS HelloWorld
Написать пример DLL
В Microsoft Visual C ++ 6.0 вы можете создать DLL, выбрав либо тип проекта Win32 Dynamic-Link Library, либо тип проекта MFC AppWizard (dll) .
Следующий код представляет собой пример библиотеки DLL, созданной в Visual C ++ с использованием типа проекта Win32 Dynamic-Link Library.
// SampleDLL.cpp #include "stdafx.h" #define EXPORTING_DLL #include "sampleDLL.h" BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } void HelloWorld() { MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK); }
// File: SampleDLL.h // #ifndef INDLL_H #define INDLL_H #ifdef EXPORTING_DLL extern __declspec(dllexport) void HelloWorld() ; #else extern __declspec(dllimport) void HelloWorld() ; #endif #endif
Вызов примера библиотеки DLL
Следующий код является примером проекта приложения Win32, который вызывает экспортированную функцию DLL в библиотеке SampleDLL.
// SampleApp.cpp #include "stdafx.h" #include "sampleDLL.h" int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HelloWorld(); return 0; }
ПРИМЕЧАНИЕ . При динамическом связывании во время загрузки необходимо связать библиотеку импорта SampleDLL.lib, которая создается при сборке проекта SampleDLL.
При динамическом связывании во время выполнения вы используете код, подобный следующему коду, для вызова экспортированной функции DLL SampleDLL.dll.
... typedef VOID (*DLLPROC) (LPTSTR); ... HINSTANCE hinstDLL; DLLPROC HelloWorld; BOOL fFreeDLL; hinstDLL = LoadLibrary("sampleDLL.dll"); if (hinstDLL != NULL) { HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld"); if (HelloWorld != NULL) (HelloWorld); fFreeDLL = FreeLibrary(hinstDLL); } ...
Когда вы компилируете и связываете приложение SampleDLL, операционная система Windows ищет библиотеку SampleDLL в следующих местах в следующем порядке:
-
Папка приложения
-
Текущая папка
-
Системная папка Windows (функция GetSystemDirectory возвращает путь к системной папке Windows).
-
Папка Windows (функция GetWindowsDirectory возвращает путь к папке Windows).
Папка приложения
Текущая папка
Системная папка Windows (функция GetSystemDirectory возвращает путь к системной папке Windows).
Папка Windows (функция GetWindowsDirectory возвращает путь к папке Windows).
DLL — Регистрация
Чтобы использовать DLL, она должна быть зарегистрирована путем внесения соответствующих ссылок в Реестр. Иногда случается, что ссылка на реестр повреждена, и функции библиотеки DLL больше не могут использоваться. DLL можно перерегистрировать, открыв Start-Run и введя следующую команду:
regsvr32 somefile.dll
Эта команда предполагает, что somefile.dll находится в каталоге или папке, которая находится в PATH. В противном случае необходимо использовать полный путь к DLL. DLL-файл также может быть незарегистрирован с помощью ключа «/ u», как показано ниже.
regsvr32 /u somefile.dll
Это может быть использовано для включения и выключения службы.
DLL — Инструменты
Доступно несколько инструментов, которые помогут вам решить проблемы с DLL. Некоторые из них обсуждаются ниже.
Зависимость Уокер
Средство Dependency Walker ( disabled.exe ) может рекурсивно сканировать все зависимые библиотеки DLL, которые используются программой. Когда вы открываете программу в Dependency Walker, Dependency Walker выполняет следующие проверки:
- Проверяет наличие недостающих DLL.
- Проверяет недопустимые программные файлы или библиотеки DLL.
- Проверяет, совпадают ли функции импорта и функции экспорта.
- Проверяет круговые ошибки зависимости.
- Проверяет недопустимые модули, поскольку модули предназначены для другой операционной системы.
Используя Dependency Walker, вы можете задокументировать все библиотеки DLL, которые использует программа. Это может помочь предотвратить и исправить проблемы с DLL, которые могут возникнуть в будущем. При установке Microsoft Visual Studio 6.0 Dependency Walker находится в следующем каталоге:
drive\Program Files\Microsoft Visual Studio\Common\Tools
DLL Универсальный Решатель Проблем
Инструмент универсального решения проблем DLL (DUPS) используется для аудита, сравнения, документирования и отображения информации DLL. В следующем списке описаны утилиты, которые составляют инструмент DUPS:
-
Dlister.exe — эта утилита перечисляет все библиотеки DLL на компьютере и записывает информацию в текстовый файл или файл базы данных.
-
Dcomp.exe — эта утилита сравнивает библиотеки DLL, перечисленные в двух текстовых файлах, и создает третий текстовый файл, содержащий различия.
-
Dtxt2DB.exe — эта утилита загружает текстовые файлы, созданные с помощью утилиты Dlister.exe и Dcomp.exe, в базу данных dllHell.
-
DlgDtxt2DB.exe — эта утилита предоставляет версию графического интерфейса пользователя (GUI) утилиты Dtxt2DB.exe.
Dlister.exe — эта утилита перечисляет все библиотеки DLL на компьютере и записывает информацию в текстовый файл или файл базы данных.
Dcomp.exe — эта утилита сравнивает библиотеки DLL, перечисленные в двух текстовых файлах, и создает третий текстовый файл, содержащий различия.
Dtxt2DB.exe — эта утилита загружает текстовые файлы, созданные с помощью утилиты Dlister.exe и Dcomp.exe, в базу данных dllHell.
DlgDtxt2DB.exe — эта утилита предоставляет версию графического интерфейса пользователя (GUI) утилиты Dtxt2DB.exe.
DLL — Советы
Помните следующие советы при написании DLL:
-
Используйте правильное соглашение о вызовах (C или stdcall).
-
Помните о правильном порядке аргументов, передаваемых в функцию.
-
НИКОГДА не изменяйте размеры массивов и не объединяйте строки, используя аргументы, передаваемые непосредственно в функцию. Помните, что передаваемые вами параметры являются данными LabVIEW. Изменение размеров массива или строки может привести к сбою при перезаписи других данных, хранящихся в памяти LabVIEW. Вы МОЖЕТЕ изменить размер массивов или объединить строки, если вы передадите дескриптор массива LabVIEW или дескриптор строки LabVIEW и используете компилятор Visual C ++ или Symantec для компиляции вашей DLL.
-
При передаче строк в функцию выберите правильный тип строки для передачи. C или Паскаль или LabVIEW Строка Ручка.
-
Длина строк Паскаля ограничена 255 символами.
-
Строки C заканчиваются на NULL. Если ваша DLL-функция возвращает числовые данные в формате двоичной строки (например, через GPIB или последовательный порт), она может вернуть значения NULL как часть строки данных. В таких случаях передача массивов коротких (8-битных) целых чисел является наиболее надежной.
-
Если вы работаете с массивами или строками данных, ВСЕГДА передавайте буфер или массив, достаточно большой, чтобы хранить любые результаты, помещенные в буфер функцией, если вы не передаете их как дескрипторы LabVIEW, и в этом случае вы можете изменить их размер с помощью CIN. работает под компилятором Visual C ++ или Symantec.
-
Перечислите функции DLL в разделе EXPORTS файла определения модуля, если вы используете _stdcall.
-
Перечислите функции DLL, которые другие приложения вызывают в разделе EXPORTS файла определения модуля, или включите ключевое слово _declspec (dllexport) в объявление функции.
-
Если вы используете компилятор C ++, экспортируйте функции с помощью оператора extern .C. {} В заголовочном файле, чтобы предотвратить искажение имени.
-
Если вы пишете свою собственную DLL, вы не должны перекомпилировать DLL, пока DLL загружается в память другим приложением. Перед перекомпиляцией DLL убедитесь, что все приложения, использующие эту конкретную DLL, выгружены из памяти. Это гарантирует, что сама DLL не загружается в память. Возможно, вам не удастся восстановить правильно, если вы забудете об этом и ваш компилятор не предупредит вас.
-
Протестируйте свои DLL с другой программой, чтобы убедиться, что функция (и DLL) работают правильно. Тестирование с помощью отладчика вашего компилятора или простой C-программы, в которой вы можете вызывать функцию в DLL, поможет вам определить, являются ли возможные трудности присущими DLL или LabVIEW.
Используйте правильное соглашение о вызовах (C или stdcall).
Помните о правильном порядке аргументов, передаваемых в функцию.
НИКОГДА не изменяйте размеры массивов и не объединяйте строки, используя аргументы, передаваемые непосредственно в функцию. Помните, что передаваемые вами параметры являются данными LabVIEW. Изменение размеров массива или строки может привести к сбою при перезаписи других данных, хранящихся в памяти LabVIEW. Вы МОЖЕТЕ изменить размер массивов или объединить строки, если вы передадите дескриптор массива LabVIEW или дескриптор строки LabVIEW и используете компилятор Visual C ++ или Symantec для компиляции вашей DLL.
При передаче строк в функцию выберите правильный тип строки для передачи. C или Паскаль или LabVIEW Строка Ручка.
Длина строк Паскаля ограничена 255 символами.
Строки C заканчиваются на NULL. Если ваша DLL-функция возвращает числовые данные в формате двоичной строки (например, через GPIB или последовательный порт), она может вернуть значения NULL как часть строки данных. В таких случаях передача массивов коротких (8-битных) целых чисел является наиболее надежной.
Если вы работаете с массивами или строками данных, ВСЕГДА передавайте буфер или массив, достаточно большой, чтобы хранить любые результаты, помещенные в буфер функцией, если вы не передаете их как дескрипторы LabVIEW, и в этом случае вы можете изменить их размер с помощью CIN. работает под компилятором Visual C ++ или Symantec.
Перечислите функции DLL в разделе EXPORTS файла определения модуля, если вы используете _stdcall.
Перечислите функции DLL, которые другие приложения вызывают в разделе EXPORTS файла определения модуля, или включите ключевое слово _declspec (dllexport) в объявление функции.
Если вы используете компилятор C ++, экспортируйте функции с помощью оператора extern .C. {} В заголовочном файле, чтобы предотвратить искажение имени.
Если вы пишете свою собственную DLL, вы не должны перекомпилировать DLL, пока DLL загружается в память другим приложением. Перед перекомпиляцией DLL убедитесь, что все приложения, использующие эту конкретную DLL, выгружены из памяти. Это гарантирует, что сама DLL не загружается в память. Возможно, вам не удастся восстановить правильно, если вы забудете об этом и ваш компилятор не предупредит вас.
Протестируйте свои DLL с другой программой, чтобы убедиться, что функция (и DLL) работают правильно. Тестирование с помощью отладчика вашего компилятора или простой C-программы, в которой вы можете вызывать функцию в DLL, поможет вам определить, являются ли возможные трудности присущими DLL или LabVIEW.
DLL — Примеры
Мы видели, как написать DLL и как создать программу «Hello World». Этот пример, должно быть, дал вам представление об основной концепции создания DLL.
Здесь мы дадим описание создания DLL с использованием Delphi, Borland C ++ и снова VC ++.
Давайте возьмем эти примеры один за другим.
Как писать и вызывать DLL в Delphi