На чем написана dll

Обновлено: 05.07.2024

Часто библиотеки 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-файлы

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

Типы DLL

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

  • динамическое связывание во время загрузки и
  • динамическое связывание во время выполнения.

Динамическое связывание во время загрузки

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

Динамическое связывание во время выполнения

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

В следующем списке описаны критерии приложения для выбора между динамическим связыванием во время загрузки и динамическим связыванием во время выполнения:

Производительность при запуске: если важна начальная производительность при запуске приложения, следует использовать динамическое связывание во время выполнения.

Простота использования : при динамическом связывании во время загрузки экспортированные функции DLL похожи на локальные функции. Это помогает вам легко вызывать эти функции.

Логика приложения . При динамическом связывании во время выполнения приложение может выполнять ветвление для загрузки различных модулей по мере необходимости. Это важно при разработке многоязычных версий.

Производительность при запуске: если важна начальная производительность при запуске приложения, следует использовать динамическое связывание во время выполнения.

Простота использования : при динамическом связывании во время загрузки экспортированные функции DLL похожи на локальные функции. Это помогает вам легко вызывать эти функции.

Логика приложения . При динамическом связывании во время выполнения приложение может выполнять ветвление для загрузки различных модулей по мере необходимости. Это важно при разработке многоязычных версий.

Точка входа в DLL

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

Кроме того, если приложение является многопоточным, вы можете использовать локальное хранилище потоков (TLS) для выделения памяти, которая является частной для каждого потока в функции точки входа. Следующий код является примером функции точки входа DLL.

Когда функция точки входа возвращает значение FALSE, приложение не запускается, если вы используете динамическое связывание во время загрузки. Если вы используете динамическое связывание во время выполнения, не будет загружаться только отдельная DLL.

Функция точки входа должна выполнять только простые задачи инициализации и не должна вызывать какие-либо другие функции загрузки или завершения DLL. Например, в функции точки входа не следует прямо или косвенно вызывать функцию LoadLibrary или функцию LoadLibraryEx . Кроме того, вы не должны вызывать функцию FreeLibrary, когда процесс завершается.

ВНИМАНИЕ : В многопоточных приложениях убедитесь, что доступ к глобальным данным DLL синхронизирован (потокобезопасен), чтобы избежать возможного повреждения данных. Для этого используйте TLS, чтобы предоставить уникальные данные для каждого потока.

Экспорт функций DLL

Чтобы экспортировать функции DLL, вы можете добавить ключевое слово функции в экспортированные функции DLL или создать файл определения модуля (.def), в котором перечислены экспортированные функции DLL.

Чтобы использовать ключевое слово функции, вы должны объявить каждую функцию, которую вы хотите экспортировать, со следующим ключевым словом:

Чтобы использовать экспортированные функции DLL в приложении, вы должны объявить каждую функцию, которую вы хотите импортировать, с помощью следующего ключевого слова:

Как правило, вы использовали бы один заголовочный файл, имеющий оператор определения и оператор ifdef, чтобы разделить оператор экспорта и оператор импорта.

// 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.

Вызов примера библиотеки DLL

Следующий код является примером проекта приложения Win32, который вызывает экспортированную функцию DLL в библиотеке SampleDLL.

ПРИМЕЧАНИЕ . При динамическом связывании во время загрузки необходимо связать библиотеку импорта SampleDLL.lib, которая создается при сборке проекта SampleDLL.

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

Когда вы компилируете и связываете приложение SampleDLL, операционная система Windows ищет библиотеку SampleDLL в следующих местах в следующем порядке:

Системная папка Windows (функция GetSystemDirectory возвращает путь к системной папке Windows).

Папка Windows (функция GetWindowsDirectory возвращает путь к папке Windows).

Системная папка Windows (функция GetSystemDirectory возвращает путь к системной папке Windows).

Папка Windows (функция GetWindowsDirectory возвращает путь к папке Windows).

Это может быть использовано для включения и выключения службы.

Доступно несколько инструментов, которые помогут вам решить проблемы с DLL. Некоторые из них обсуждаются ниже.

Зависимость Уокер

Средство Dependency Walker ( disabled.exe ) может рекурсивно сканировать все зависимые библиотеки DLL, которые используются программой. Когда вы открываете программу в Dependency Walker, Dependency Walker выполняет следующие проверки:

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

DLL Универсальный Решатель Проблем

Инструмент универсального решения проблем DLL (DUPS) используется для аудита, сравнения, документирования и отображения информации DLL. В следующем списке описаны утилиты, которые составляют инструмент DUPS:

Помните следующие советы при написании DLL:

Используйте правильное соглашение о вызовах (C или stdcall).

Помните о правильном порядке аргументов, передаваемых в функцию.

НИКОГДА не изменяйте размеры массивов и не объединяйте строки, используя аргументы, передаваемые непосредственно в функцию. Помните, что передаваемые вами параметры являются данными LabVIEW. Изменение размеров массива или строки может привести к сбою при перезаписи других данных, хранящихся в памяти LabVIEW. Вы МОЖЕТЕ изменить размер массивов или объединить строки, если вы передадите дескриптор массива LabVIEW или дескриптор строки LabVIEW и используете компилятор Visual C ++ или Symantec для компиляции вашей DLL.

При передаче строк в функцию выберите правильный тип строки для передачи. C или Паскаль или LabVIEW Строка Ручка.

Длина строк Паскаля ограничена 255 символами.

Если вы работаете с массивами или строками данных, ВСЕГДА передавайте буфер или массив, достаточно большой, чтобы хранить любые результаты, помещенные в буфер функцией, если вы не передаете их как дескрипторы 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 символами.

Если вы работаете с массивами или строками данных, ВСЕГДА передавайте буфер или массив, достаточно большой, чтобы хранить любые результаты, помещенные в буфер функцией, если вы не передаете их как дескрипторы 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 и как создать программу «Hello World». Этот пример, должно быть, дал вам представление об основной концепции создания DLL.

Здесь мы дадим описание создания DLL с использованием Delphi, Borland C ++ и снова VC ++.

image

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

Под катом вас ожидает статья с различными примерами, исходниками и пояснениями к ним.

Содержание

  • Структура DLL
  • DLL & Python
    • Подключение DLL
    • Типы данных в С и Python
    • Аргументы функция и возвращаемые значения
    • Своя DLL и ее использование

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

    Структура DLL

    DLL — Dynamic Link Library — динамическая подключаемая библиотека в операционной системе (ОС) Windows. Динамические библиотеки позволяют сделать архитектуру более модульной, уменьшить количество используемых ресурсов и упрощают модификацию системы. Основное отличие от .EXE файлов — функции, содержащиеся в DLL можно использовать по одной.

    Учитывая, что статья не о самих библиотеках, лучше просто оставить здесь ссылку на довольно информативную статью от Microsoft: Что такое DLL?.

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

    DLL содержит набор различных функций, которые потом можно использовать по-отдельности. Но также есть возможность дополнительно указать функцию точки входа в библиотеку. Такая функция обычно имеет имя DllMain и вызывается, когда процессы или потоки прикрепляются к DLL или отделяются от неё. Это можно использовать для инициализации различных структур данных или их уничтожения.

    Рисунок 1 — Пустой template, предлагаемый Code Blocks для проекта DLL.

    На рисунке 1 приведен шаблон, который предлагает Code Blocks, при выборе проекта типа DLL. В представленном шаблоне есть две функции:

    Для начала стоит подробнее рассмотреть функцию DllMain . Через нее ОС может уведомлять библиотеку о нескольких событиях (fdwReason):

    DLL_PROCESS_ATTACH – подключение DLL. Процесс проецирования DLL на адресное пространство процесса. С этим значением DllMain вызывается всякий раз, когда какой-то процесс загружает библиотеку с явной или неявной компоновкой.

    DLL_PROCESS_DETACH – отключение DLL от адресного пространства процесса. С этим значением DllMain вызывается при отключении библиотеки.

    DLL_THREAD_ATTACH – создание процессом, подключившим DLL, нового потока. Зачем DLL знать о каких-то там потоках? А вот зачем, далеко не каждая динамическая библиотека может работать в многопоточной среде.

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

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

    У DllMain не так много аргументов, самый важный fdwReason уже рассмотрен выше, теперь о двух других:

    • Аргумент lpvReserved указывает на способ подключения DLL:
      • 0 — библиотека загружена с явной компоновкой.
      • 1 — библиотека загружена с неявной компоновкой.

      О явной и неявной компоновке можно прочесть подробно в статье: Связывание исполняемого файла с библиотекой DLL.

      В предложенном на рисунке 1 шаблоне есть функция SomeFunction , которая может быть экспортирована из динамической библиотеки. Для того, чтобы это показать, при объявлении функции указывается __declspec(dllexport) . Например, так:

      Функции, не объявленные таким образом, нельзя будет вызывать снаружи.

      DLL & Python

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

      Подключение DLL

      Основной библиотекой в Python для работы с типами данных, совместимыми с типами языка С является ctypes . В документации на ctypes представлено много примеров, которым стоит уделить внимание.

      Чтобы начать работать с DLL, необходимо подключить библиотеку к программе на Python. Сделать это можно тремя способами:

      • cdll — загружает динамическую библиотеку и возвращает объект, а для использования функций DLL нужно будет просто обращаться к атрибутам этого объекта. Использует соглашение вызовов cdecl.
      • windll — использует соглашение вызовов stdcall. В остальном идентична cdll.
      • oledll — использует соглашение вызовов stdcall и предполагается, что функции возвращают код ошибки Windows HRESULT. Код ошибки используется для автоматического вызова исключения WindowsError.

      Для первого примера будем использовать стандартную Windows DLL библиотеку, которая содержит всем известную функцию языка С — printf() . Библиотека msvcrt.dll находится в папке C:\WINDOWS\System32 .

      Код Python:

      Результат:

      Можно использовать подключение библиотеки с помощью метода windll либо oledll , для данного кода разницы не будет, вывод не изменится.

      Если речь не идет о стандартной библиотеке, то конечно следует использовать вызов с указанием пути на dll. В ctypes для загрузки библиотек предусмотрен метод LoadLibrary . Но есть еще более эффективный конструктор CDLL , он заменяет конструкцию cdll.LoadLibrary . В общем, ниже показано два примера вызова одной и той же библиотеки msvcrt.dll.

      Код Python:

      Иногда случается, что необходимо получить доступ к функции или атрибуту DLL, имя которого Python не "примет"… ну бывает. На этот случай имеется функции getattr(lib, attr_name) . Данная функция принимает два аргумента: объект библиотеки и имя атрибута, а возвращает объект атрибута.

      Код Python:

      Результат:

      Теперь становится понятно, как подключить библиотеку и использовать функции. Однако, не всегда в DLL нужно передавать простые строки или цифры. Бывают случаи, когда требуется передавать указатели на строки, переменные или структуры. Кроме того, функции могут и возвращать структуры, указатели и много другое.

      Типы данных в С и Python

      Модуль ctypes предоставляет возможность использовать типы данных совместимые с типами в языке С. Ниже приведена таблица соответствия типов данных.

      Сtypes type C type Python type
      c_bool _Bool bool (1)
      c_char char 1-character string
      c_wchar wchar_t 1-character unicode string
      c_byte char int/long
      c_ubyte unsigned char int/long
      c_short short int/long
      c_ushort unsigned short int/long
      c_int int int/long
      c_uint unsigned int int/long
      c_long long int/long
      c_ulong unsigned long int/long
      c_longlong __int64 or long long int/long
      c_ulonglong unsigned __int64 or unsigned long long int/long
      c_float float float
      c_double double float
      c_longdouble long double float
      c_char_p char * (NUL terminated) string or None
      c_wchar_p wchar_t * (NUL terminated) unicode or None
      c_void_p void * int/long or None

      Таблица 1 — Соответствие типов данных языка Python и языка C, которое предоставляет модуль ctypes .

      Первое, что стоит попробовать — это использовать указатели, куда без них? Давайте напишем программу, где создадим строку и указатель на неё, а потом вызовем printf() для них:

      Результат:

      Если вы создали указатель, то разыменовать (получить доступ к значению, на которое он указывает) можно с использованием атрибута value , пример выше.

      Аргументы функций и возвращаемые значения

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

      Например, стандартная функция strcat принимает два указателя на строки и возвращает один указатель на новую строку. Давайте попробуем ей воспользоваться.

      Код Python:

      Результат:

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

      Своя DLL и ее использование

      Пример 1

      Шаблон DLL уже был рассмотрен выше, а сейчас, когда дело дошло до написания своей DLL и работы с ней, выскочили первые и очевидные грабли — несовместимость разрядности DLL и Python. У меня на ПК установлен Python x64, оказалось, что как бы не были DLL универсальны, разрядность DLL должна соответствовать разрядности Python. То есть, либо ставить компилятор x64 и Python x64, либо и то и то x32. Хорошо, что это не сложно сделать.

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

      Код DLL на С:

      Код Python:

      Функция SomeFunction получает указатель на строку и выводит её в окно. На рисунке ниже показана работа программы.




      Рисунок 2 — Демонстрация работы шаблона библиотеки из Code Blocks.

      Все действия происходящие в кейсе DLL_PROCESS_ATTACH , код которого приведен ниже, вызываются лишь одной строкой в Python коде:



      Рисунок 3 — Действия происходящие при подключении DLL.

      Пример 2

      Чтобы подвести итог по использованию DLL библиотек из Python, приведу пример, в котором есть начальная инициализация параметров и передача новых через указатели на строки и структуры данных. Этот код дает понять, как написать аналог структуры С в Python. Ниже привожу код main.c , man.h и main.py .

      Код DLL на С:

      В коде main.h определена структура Passport с тремя полями: два указателя и целочисленная переменная. Кроме того, четыре функции объявлены, как экспортируемые.

      Код DLL на С:

      Внутри кейса DLL_PROCESS_ATTACH происходит выделение памяти под строки и начальная инициализация полей структуры. Выше DllMain определены функции:

      GetPassport — вывод полей структуры passport в консоль.

      *SetName(char new_name)** — установка поля name структуры passport .

      *SetSurname(char new_surname)** — установка поля surname структуры passport .

      *SetPassport(Passport new_passport)** — установка всех полей структуры passport . Принимает в качестве аргумента указатель на структуру с новыми полями.

      Теперь можно подключить библиотеку в Python.

      Код на Python

      В коде выше многое уже знакомо, кроме создания структуры аналогичной той, которая объявлена в DLL и передачи указателя на эту структуру из Python в DLL.

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

      Я постараюсь за 10 минут на примерах показать весь процесс создания простых DLL библиотек и раскрою некоторые технические детали нашей реализации связывания. Демонстрация будет на примере Visual Studio 2005 / 2008, бесплатные Express-версии которых можно свободно скачать с сайта Microsoft.

      1. Создание проекта DLL на С++ в Visual Studio 2005/2008

      Запустите визард через меню 'File -> New', выберите тип проекта 'Visual C++', шаблон 'Win32 Console Application' и укажите имя проекта (например, 'MQL5DLLSamples'). Выберите отдельный корневой каталог хранения проектов 'Location' вместо предлагаемого по умолчанию, отключите галочку 'Create directory for solution' и нажмите на кнопку 'OK':


      Рис 1. Win32 Application Wizard, создание проекта DLL

      На следующем шаге просто нажмите на кнопку 'Next' для перехода на страницу настроек:


      Рис 2. Win32 Application Wizard, параметры проекта

      На финальной странице выберите тип 'DLL', оставив остальные поля пустыми как есть, и нажмите на 'Finish'. Ставить галочку на 'Export symbols' не нужно, чтобы потом не удалять автоматически добавленный демонстрационный код:


      Рис 3. Win32 Application Wizard, н астройка свойств приложения

      В результате получите пустой проект:


      Рис 4. Пустой проект DLL

      Для удобства тестирования лучше всего прямо в настройках 'Output Directory' указать выкладку DLL файлов напрямую в каталог '. \MQL5\Libraries' клиентского терминала. Это сэкономит много времени в последующей работе:


      Рис 5. Каталог выкладки DLL файлов

      2. Подготовка к добавлению функций

      Добавьте макрос '_DLLAPI' в конец файла stdafx.h, чтобы можно было удобно и просто описывать экспортируемые функции:

      В вызовах функций MQL5 используется соглашение о связях __stdcall и __cdecl. Хотя вызовы stdcall и cdecl отличаются вариантами извлечения параметров со стека, но исполняющая среда MQL5 позволяет безболезненно использовать оба варианта за счет специального враппера DLL вызовов.

      По умолчанию в настройках компилятора С++ для функций используется __cdecl, но я рекомендую для экспортируемых функций явным образом указывать режим __stdcall.

      Правильно оформленная экспортная функции должна иметь следующий вид:

      а в MQL5 программе описываться и вызываться так:

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

      При отсутствии полного имени функции в таблице экспорта для совместимости используется поиск упрощенного описания без декорирования. Такие имена вида fnCalculateSpeed создаются при описаниях функции в формате __cdecl:

      3. Способы передачи параметров и обмен данными

      Давайте посмотрим на несколько вариантов передаваемых параметров:

      1. Прием и передача простых переменных
        С простыми переменными все просто - их можно передавать по значению или по ссылке через &.
        Вызов из MQL5:
        Результат:
      2. Прием и передача массива с заполнением элементов

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

      4. Перехват исключений в DLL функциях

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

      Для проверки работоспособности этого механизма создадим следующий код:

      и вызовем его из терминала:

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


      5. Враппер DLL вызовов и потери скорости на вызовах

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

      Такой объем выполняемой работы не приводит к существенному замедлению вызова функций.

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

      Что такое DLL

      Современное программирование стало достаточно сложным, уже написаны километры строк кода, и некоторые его части время от времени приходится дублировать в разных программах. Если бы каждый программист в каждой новой программе заново писал весь необходимый код, языки программирования никогда бы не получили такого стремительного развития. Однако мы в своих проектах можем использовать инструменты, созданные другими программистами - типы, процедуры, функции… Мы уже неоднократно подключали к нашим проектам различные модули и пользовались описанными в них средствами. И всё было бы хорошо, если бы не одно НО…

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

      Есть еще один недостаток модульного подхода - мы можем использовать чужой код, только если он написан на таком же языке программирования, каким пользуемся мы. Ну, в Lazarus мы можем использовать модули Delphi, а также код на Ассемблере. Но если код был написан на С++ или Visual Basic , к примеру? Короче говоря, требовался новый подход, позволяющий использовать чужой код, не внедряя его непосредственно в программу, и, кроме того, "языконезависимый". И таким подходом стала DLL.

      DLL (англ., Dynamic Link Library - Динамически Подключаемые Библиотеки) - универсальный механизм внедрения в программу функций и процедур, написанных другими программистами, и возможно, на другом языке программирования. Внедрение происходит не в момент компиляции проекта, как в случае модулей, а в момент загрузки готовой программы в оперативную память.

      Использование DLL дает нам еще одно очень важное преимущество. Представьте, что у вас десятки программ импортируют какой-то код. И вот, этот код нужно изменить, или дополнить, потому что вы перешли на другую ОС, на другие стандарты или просто потому, что начальство потребовало от вас расширить функциональность ваших программ. Если бы вы использовали модули, вам пришлось бы туго - нужно было бы переделывать не только этот модуль , но и перекомпилировать ваши программы. Но вы ведь их создавали не неделю и не месяц, правда? Что, если часть из них вы писали когда-то на Delphi 7, другую часть на Delphi XE3, а третью - уже на Lazarus? Работа была бы та еще. Но с использованием DLL эта задача сильно упрощается. Вам нужно переделать всего лишь эту самую DLL -библиотеку, а программы, использующие её, переделывать уже не нужно!

      Каким же образом используются эти DLL ? Фактически, DLL - это библиотека тех инструментов, которые могут быть использованы вами в различных проектах. Причем с учетом того, что вы, может быть, будете использовать различные языки программирования. Итак, первым делом, вы создаете саму библиотеку - файл *.dll. Создается он, как отдельное приложение , но после компиляции сам по себе работать все равно не сможет, поскольку не имеет собственной точки входа, как выполняемый exe- файл .

      Далее, вы создаете приложение . Файл используемой динамической библиотеки вы помещаете в ту же папку, что и проект. В случае, если несколько ваших проектов будут использовать эту библиотеку, то её лучше поместить в какую-либо системную папку, например,

      C:\Windows\system32

      Тогда все эти программы смогут подгружать данную библиотеку, не указывая её адреса - Windows просматривает все свои системные папки и знает, где находится эта DLL . Функции и процедуры, описанные в DLL , наша программа будет воспринимать, как свои собственные. И когда во время выполнения программы происходит вызов таких функций и процедур, то будет подгружена соответствующая DLL .

      Во время компиляции проекта код библиотеки не будет внедрен в программу. Это означает две вещи:

      1. ваша программа не будет иметь лишнего кода;
      2. при распространении программы между пользователями вам нужно будет также позаботиться, чтобы у них была установлена и эта dll-библиотека.

      Когда пользователь загружает вашу программу, то также загружается и используемый dll - файл , на это тратится дополнительное время. Зато библиотека остается в оперативной памяти даже после завершения работы загрузившей её программы. И если затем будет загружена другая программа , использующая ту же DLL , она уже не будет тратить время на повторную загрузку этой библиотеки, а воспользуется уже загруженной в память версией. Более того, если будут одновременно работать несколько программ, использующих одну и ту же DLL , то они не будут загружать для себя по копии этой библиотеки, поскольку Windows гарантирует, что одновременно может быть загружена только одна копия динамической библиотеки. Так что первая из этих программ загрузит DLL в оперативную память , остальные будут использовать её же, не тратя лишнего времени на загрузку. Вот почему такие программы, как MS Word или MS Excel медленно загружаются в первый раз - они обращаются ко множеству библиотек, которые также подгружаются в память . Зато когда вы загружаете MS Word вторично, он грузится гораздо быстрее, так как нужные библиотеки уже в оперативной памяти! Почувствовали разницу между DLL и модулями?

      Кстати, динамическим библиотекам принято давать расширение *.dll, однако это не всегда так. Вы можете дать такой библиотеке любое расширение, или даже вообще не указывать его! Например, драйверы устройств имеют расширение *.drv, но это такие же динамические библиотеки.

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

      В нашем курсе подразумевается, что мы используем операционную систему семейства Windows , однако механизм динамически подключаемых библиотек существует во всех современных операционных системах. В Windows такая библиотека представляет собой файл *.dll, в Linux и Unix - *.so (Shared Object Library ), а в MacOS X - *.dylib ( Dynamic Shared Library ). И все эти библиотеки можно создавать с помощью Lazarus. Учитывая, что мы используем ОС Windows , мы будем рассматривать создание dll-файлов.

      Анализ DLL библиотеки динамической компоновки Windows

      DLL: Библиотека динамической компоновки, а именноБиблиотека динамической компоновкиЭтот вид библиотеки содержит код и данные, которые могут использоваться несколькими программами одновременно.

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

      • Файлы элементов управления ActiveX (.ocx): например, элемент управления календарем в Windows.
      • Файл панели управления (.cpl). Каждый элемент панели управления представляет собой выделенную DLL.
      • Файлы драйвера устройства (.drv): например, драйверы принтера, управляющие печатью на принтере.

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



      преимущество:

      (1) Экономия памяти и повторное использование кода: Когда несколько программ используют одну и ту же библиотеку функций, DLL может уменьшить количество повторений загрузки кода на диск и в физическую память, а также помочь повторно использовать код.

      (2) Модуляризация: DLL способствует развитию модульных программ. Модульность позволяет изменять только код и данные в DLL, совместно используемой несколькими приложениями, без изменения самого приложения. Базовая форма этого языка модуля позволяет крупным приложениям, таким как Microsoft Office, Microsoft Visual Studio и даже самой Windows, использовать более компактные исправления и пакеты обновления.

      Недостаток:

      DLL Hell :которыйDLL ад, Относится к конфликту версий, когда несколько приложений используют одну и ту же общую библиотеку DLL.

      Причина в восьми словах:Делитесь успехами и неудачами. Потому что DLL Hell происходит именно потому, что библиотека динамической компоновки может делиться функциями и ресурсами с другими программами.

      Есть две основные ситуации:

      Представьте себе такой сценарий: программа A будет использовать версию 1.0 библиотеки динамической компоновки X, а затем, когда программа A будет установлена ​​в системе, одновременно будет установлена ​​версия 1.0 библиотеки динамической компоновки X. Предполагая, что другая программа B также будет использовать библиотеку динамической компоновки X, тогда программу B можно напрямую скопировать на жесткий диск для нормальной работы, потому что библиотека динамической компоновки уже существует в системе. Однако однажды другая программа C также будет использовать библиотеку динамической компоновки X, но из-за поздней разработки программы C ей потребуется более новая версия - версия 2.0 библиотеки динамической компоновки X. Когда программа C установлена ​​в системе, в системе также должна быть установлена ​​версия 2.0 библиотеки динамической компоновки X. В это время версия 1.0 библиотеки динамической компоновки в системе будет заменена (заменена) версией 2.0.

      Ситуация 1: Новая версия библиотеки динамической компоновки несовместима со старой версией. Например, A и B нуждаются в функции, предоставляемой X. После обновления до 2.0 новая версия X отменила эту функцию (это трудно представить, но иногда бывает так . ). В настоящее время, хотя C может работать нормально, ни A, ни B не могут работать.

      Ситуация 2: Новая версия библиотеки динамической компоновки совместима со старой версией, но есть ошибка.

      Взгляните на следующий пример (чтобы проиллюстрировать проблему):

      1. // X1.0 version
      2. void func( int count)
      3. if (count < 0)
      4. count = 0;
      5. ….
      6. >
      7. // X2.0 version
      8. void func( int count)
      9. // Обработка отрицательных чисел удалена!
      10. >

      Если счетчик станет отрицательным, у программы A возникнут проблемы с новой версией.

      Решение :Side-by-side Assembly, Является ли технология, используемая системами Windows XP и выше для разрешения конфликтов версий библиотеки динамической компоновки. Ключевым моментом является то, что при компиляции программы VS генерирует файл манифеста, который указывает номер версии библиотеки динамической компоновки, используемой текущим приложением; он должен быть выпущен одновременно с выпуском программы. Файл манифеста используется DLL на клиентском компьютере Loader для загрузки соответствующей версии DLL в соответствии с манифестом. Если манифест не выпущен, клиент загружает DLL в соответствии с версией по умолчанию. Следующая картина представляет собой типичную сцену:



      На первый взгляд: lib - это библиотека статической компоновки; DLL - это библиотека динамической компоновки, одна предоставляется во время компиляции; другая предоставляется во время выполнения, завершена.

      Статическая библиотека: Он помещает декларацию экспорта (описанную ниже) и реализацию в библиотеку, а весь код встраивается в основную программу после компиляции.

      Динамическая библиотека: Эквивалент h-файла, который является объявлением части экспорта части реализации (.DLL). После компиляции в хост-программу компилируется только часть объявления экспорта, и во время выполнения требуется поддержка соответствующего файла DLL, в противном случае она не будет работать. Когда создается новая DLL, также будет сгенерирована соответствующая библиотека (то есть обе библиотеки должны быть распределены вместе), и эта библиотека на данный момент является динамической библиотекой (эксперименты будут позже).

      В среде разработки VC ++ 6.0 откройте опцию File \ New \ Project, вы можете выбрать Win32 Dynamic-Link Library или MFC AppWizard [dll] для создания различных типов не-MFC DLL, обычной DLL, DLL расширения и т. Д. Различными способами. Библиотека динамической компоновки. Затем выберите Win32 Dynamic-Link Library, чтобы создать DLL (реализоватьдополнениеЭксплуатации):

      Читайте также: