Как подключить dll к проекту c

Обновлено: 07.07.2024

Как же подключить/загрузить внешнюю динамическую библиотеку в свою программу/проект, написанную на языке программирования C++ в IDE Microsoft Visual Studio, в ОС Microsoft Windows?

Состав

Типы файлов

Для начала, давайте разберёмся, что обычно представляет собой любая динамическая библиотека, написанная на, и созданная для программ С++? Это от 1 до 4 типов файлов:

Из всего списка главным являются только файлы с расширением .dll, т.е. остальных файлов может и не быть.

Количество

У одной библиотеки может быть множество файлов таких типов. Все они, обычно, рассортированы по соответствующим папкам, чтобы было легче подключать в IDE.

Вариации

Файлы dll, lib и exp различаются по платформе (ОС), архитектуре, конфигурации, etc. В одной версии библиотеки может предоставляться сразу несколько альтернативных вариаций одинаковых по функционалу файлов. E.g.:

Всё это крайне важно, всё надо учитывать при подключении библиотеки к проекту. Свойства проекта в Visual C++ могут устанавливаться отдельно для каждой конфигурации (Release, Debug) и архитектуры/платформы (x32 (x86), x64).

Подключение

Перед тем, как подключать файлы библиотеки к своей программе, надо сперва открыть необходимый проект в IDE Visual Studio: Главное меню > Файл > Открыть проект.

Заголовочные файлы (.h)

Файлы таблиц импорта и экспорта (.lib, .exp)

Файл таблицы импорта (.lib)

Помимо указания IDE директории расположения файлов таблиц импорта (.lib), их также для линковки надо дополнительно явно указать. Сделать это можно двумя способами:

      Прописать полные названия (путь, название и расширение) .lib файлов сюда: Главное меню > Проект > Свойства проекта > Свойства конфигурации > Компоновщик > Ввод > Дополнительные зависимости. Каждая библиотека в отдельной строке. Кавычки необязательны.
      В итоге они будут вписаны автоматически в строку в двойных кавычках через точку с запятой (;). E.g.:

    Можно использовать сразу два способа.

    Я рекомендую использовать второй вариант, когда все подключения пишутся непосредственно в коде. Так нагляднее и переносимость кода увеличивается, когда всё, что нужно написано в самом коде, а не где-то там в настройках IDE.

    Файлы библиотек (.dll)

    Параметры конфигурации препроцессора

    Также не забывайте указывать параметры конфигурации препроцессора тут: Главное меню > Проект > Свойства проекта > Свойства конфигурации > C/C++ > Препроцессор > Определения препроцессора. Параметры являются комбинированием в определённой последовательности определённых литералов. Вот некоторые из них:

    Некоторые возможные варианты параметров:

    • MT_StaticRelease
    • MTd_StaticDebug
    • MD_DynamicRelease

    Это далеко не все возможные параметры.

    Примечания

    Пути директории указывать без конечного слеша.

    Опция Компоновщик
    Библиотеки

    Заключение

    На самом деле, подключать сразу столько всего зачастую не нужно. Как правило, предоставляется только исходный код и всё. Приходится самостоятельно из исходников компилировать библиотеки (файлы .dll и .lib). Т.е. нужно будет указать только заголовочные файлы .h, которые являются частью исходников.

    В качестве примера мы рассмотрим подключение библиотеки SDL к нашему проекту в Visual Studio 2017 (работать будет и с более новыми версиями Visual Studio).

    Шаг №1: Создаем папку для хранения библиотеки

    Создаем папку Libs на диске C ( C:\Libs ).

    Шаг №2: Скачиваем и устанавливаем библиотеку



    Шаг №3: Указываем путь к заголовочным файлам библиотеки

    Открываем свой любой проект в Visual Studio или создаем новый, переходим в "Обозреватель решений" > кликаем правой кнопкой мыши (ПКМ) по названию нашего проекта > "Свойства" :


    В "Свойства конфигурации" ищем вкладку "С/С++" > "Общие" . Затем выбираем пункт "Дополнительные каталоги включаемых файлов" > нажимаем на стрелочку в конце > "Изменить" :


    В появившемся окне кликаем на иконку с изображением папки, а затем на появившееся троеточие:


    Заголовочные файлы находятся в папке include внутри нашей библиотеки, поэтому переходим в нее ( C:\Libs\SDL2-2.0.9\include ) и нажимаем "Выбор папки" , а затем "ОК" :



    Шаг №4: Указываем путь к файлам с реализацией библиотеки

    Переходим на вкладку "Компоновщик" > "Общие" . Ищем пункт "Дополнительные каталоги библиотек" > нажимаем на стрелочку в конце > "Изменить" :


    Опять же, нажимаем на иконку с папкой, а затем на появившееся троеточие. Нам нужно указать следующий путь: C:\Libs\SDL2-2.0.9\lib\x86 . Будьте внимательны, в папке lib находятся две папки: x64 и x86 . Даже если у вас Windows разрядности x64, указывать нужно папку x86 . Затем "Выбор папки" и "ОК" :


    После этого переходим в "Компоновщик" > "Ввод" . Затем "Дополнительные зависимости" > нажимаем на стрелочку в конце > "Изменить" :


    В появившемся текстовом блоке вставляем:


    Затем переходим в "Компоновщик" > "Система" . После этого "Подсистема" > нажимаем на стрелочку вниз > выбираем "Консоль (/SUBSYSTEM:CONSOLE)" > "Применить" > "ОК" :



    Шаг №5: Копируем dll-ку в папку с проектом

    Переходим в папку x86 ( C:\Libs\SDL2-2.0.9\lib\x86 ), копируем SDL2.dll и вставляем в папку с вашим проектом в Visual Studio. Чтобы просмотреть папку вашего проекта в Visual Studio, нажмите ПКМ по названию вашего проекта > "Открыть содержащую папку" :


    Затем вставляем скопированный файл (SDL2.dll) в папку с проектом (где находится рабочий файл .cpp):


    Шаг №6: Тестируем

    Теперь, чтобы проверить, всё ли верно мы сделали — копируем и запускаем следующий код:

    3 января стартует курс «SQL-injection Master» © от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

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

    Запись на курс до 10 января. Подробнее .

    папка references

    2. Нажмите правую кнопку мыши и в появившемся контекстном меню выберите пункт Add Reference

    добавить сборку

    3. Если вы хотите добавить в проект свою или стороннюю библиотеку, то перейдите к пункту №7, если же вы хотите подключить сборку, входящую в состав FCL, тогда в меню Reference Manager выберите пункт Assemblies

    пункт меню Assemblies

    4. Выберите подпункт Framework

    пункт меню Framework

    5. В появившемся по центу списке выберите нужную вам сборку и нажмите кнопку OK.

    список сборок

    6. В папке References должна появиться ссылка на добавленный вами файл.

    ссылка на сборку

    7. Если вам требуется подключить в проект свою или чью-то скаченную библиотеку, то тогда, сначала, нажмите кнопку Browse

    кнопка Browse

    8. Откроется диалоговое окно. Перейдите в папку, которая содержит нужный вам dll файл, выберите его и нажмите кнопку Add

    диалоговое окно

    9. Выберите пункт Browse

    пункт меню Browse

    10. Убедитесь, что добавленный файл выбран и нажмите кнопку OK

    Reference Manager

    11. В папке References должна отобразиться ссылка на добавленный вами файл

    Solution Explorer

    1 декабря стартует зимний поток курса " Тестирование Веб-Приложений на проникновение " от команды codeby . Общая теория, подготовка рабочего окружения, пассивный фаззинг и фингерпринт, активный фаззинг, уязвимости, пост-эксплуатация, инструментальные средства, Social Engeneering и многое другое. На курс можно записаться до 10 декабря включительно. Подробнее .

    Для начала нужно разобраться, что же такое DLL.
    DLL — это сокращение фразы Dynamic-Link Library — динамически подключаемая библиотека.
    Первоначальная идея введения библиотек заключалась в более рациональном использовании ресурсов ПК, таких как ОЗУ и НЖМД. Предполагалось, что различные приложения будут использовать одну библиотеку, тем самым ликвидируя проблему сохранения одинаковых участков кода в различных местах и многократную загрузку одинаковых участков в ОЗУ.

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

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

    Секция экспорта перечисляет те идентификаторы объектов (классы, функции, переменные), доступ к которым предоставляет данная DLL. В большинстве случаев именно эта секция вызывает особый интерес у разработчиков. Тем не менее, DLL может вовсе не содержать секцию экспорта. Кроме функций, к которым можно получить доступ извне (exported), существуют также внутренние функции (internal), которые спрятаны от "посторонних глаз" и могут использоваться лишь кодом самой DLL.

    Секция импорта предназначена для связи данной DLL с другими.
    Большинство библиотек импортируют функции из системных DLL (kernel32.dll, user32.dll и др.). Иногда в стандартный список нужно добавить другие библиотеки, например ws2_32.dll для работы с Socket'ами.

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

    При выполнении команды call, процессор передает управление на код с адреса 010199, и выполняет его до команды ret, а затем возвращает управление команде следующей за call. Код который вызывается командой call называется процедурой.

    Файл, являющийся динамически загружаемой библиотекой не всегда носит расширение *.dll.
    Есть также: *.cpl (библиотеки, используемые апплетом панели управления) и *.ocx.

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

    • 1.3. Необходимость внедрения DLL. Нужно ли это?

    Настало время ответить на вопрос: Когда же нужно использовать DLL?
    Если вы разрабатываете небольшой проект или тестовое приложение, то внедрение DLL будет для вас пустой тратой времени и сил. Тратой времени: не только времени на создание библиотеки, но ещё и времени, необходимого для загрузки DLL в память хост-процесса. До того, как вы будете использовать объекты из DLL, вам рано или поздно прийдётся выгрузить содержимое в память. А это требует некоторого времени.
    Однако, если вашу DLL будет использовать более, чем одна копия приложения (или несколько различных приложений) - наступит явный выигрыш.

    Для эксперимента запустите Microsoft Office. Первый запуск долгий. Это обусловлено тем, что все необходимые модули загрузаются в оперативную память. Теперь полностью закройте Office и снова откройте его! Окно появится почти мгновенно. Это обусловлено тем, что модули хранились в ОЗУ (система не тронет их, пока ей не понадобится память для других целей).

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

    Но не старайтесь придать вашему проекту чрезвычайной "уникальности" за счёт помещения каждой функции в отдельную библиотеку! Это можно делать, только зачем тратить время для загрузки каждого модуля?

    • 2.1. Создаём первую DLL своими руками.

    Пришло время постепенно перейти от теории к практике.
    Открываем IDE (для написании данной статьи я использовал RAD Studio 2010).
    Переходим к File -> New -> Other -> Dynamic-Link Library.
    Перед нами возникает диалог:

    Source Type - язык, на котором ведётся разработка.
    Use VCL - использование библиотеки визуальных компонентов.
    Multi Threaded - опция, указывающая на то, будет ли использоваться многопоточность в данной DLL (VCL уже подразумевает в себе многопоточность).
    VC++ Style DLL - опция, указывающая на то, будет ли DLL совместима с компиляторами Microsoft.

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

    Для обеспечения совместимости точка входа изменяется на:

    Оставим диалог без изменений и нажмём "ОК".
    Перед нами появится шаблон минимальной DLL. Сохраним его.
    Как вы помните, сама по себе DLL работать не может, ей нужен клиент. Поэтому, для удобства сразу создадим новый проект VCL Forms Application.
    Для этого переходим в Project Manager, вызываем контекстное меню у нашей Project Group и переходим к Add New Project -> VCL Forms Application.
    Для удобста я назвал проекты TestDLL и TestVCL соответственно (и сохранил их в одном каталоге - это избавит меня от копирования DLL или указания абсолютного пути):

    Без изменений запускаем TestVCL, сохраняем и переключаемся к проекту TestDLL (дабл-клик на проекте в Project Manager).

    Переходим к Run -> Parameters и в поле Host Application указываем путь к нашему проекту TestVCL.

    К шаблону DLL добавляем функцию, которая будет вычислять сумму и выводить результат на экран:

    В проекте TestDLL добавим также заголовочный файл (TestDLL.h) с таким содержанием:

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

    При неявной загрузке DLL загружается (проецируется на адресное пространство вызывающего процесса) при его создании. Если при загрузке возникает ошибка - процесс останавливается и разрушается.

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

    В проекте TestVCL подключим наш заголовочный файл:

    Также, не забываем сделать Project -> Add To Project и добавить в проект TestDLL.lib
    Далее, объявим прототип:
    Теперь осталось только вызвать функцию там, где это необходимо.
    Чтобы убедится в том, что всё работает, прописываем в конструкторе формы:

    Запускаем и смотрим на результат. Думаю, "3 + 2 = 5" всех устраивает.

    Для того, чтобы выполнить явную загрузку программист должен попыхтеть, управляя DLL через функции WinAPI.
    Наиболее часто рассматриваемые WinAPI функции:
    DisableThreadLibraryCalls, FreeLibrary, FreeLibraryAndExitThread, GetModuleFileName, GetModuleHandle, GetProcAddress, LoadLibrary .

    При этом, основными функциями являются:
    LoadLibrary[Ex] - позволяют загрузить DLL в адресное пространство хост-процесса.
    FreeLibrary - функция, используемая для явной выгрузки DLL.
    GetProcAddress - функция, позволяющая получить виртуальный адрес экспортируемой из DLL функции(или переменной) для ее последующего вызова.

    Общая методика выглядит так:
    1. Загрузить DLL с помощью LoadLibrary.
    2. Получить указатели на необходимые объекты с помощью GetProcAddress.
    3. Выгрузить DLL после завершения всех действий.

    Теперь возникает вопрос, как же проверить теорию на практике?
    Всё, что нужно, это добавить TestDLL.lib к проекту (также, как и при неявной загрузке).
    А дальше, для проверки снова пишем в конструкторе формы:

    И на экране снова красуется победная надпись "3 + 2 = 5"

    Остался один неосвещенный вопрос. Почему же название функции "ShowSum" мы ищем в библиотеке с нижним подчёркиванием?

    Виновато во всём декорирование имён.

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

    Прототип функции Test(int); мог быть преобразован компилятором, например в ?Test@@YAKH@Z.
    Естественно, такое декорирование нам вообще не по душе. Избавиться от него можно объявляя все экспортируемые функции с модификатором extern "C" - тогда компилятор не будет искажать имя функции.

    Однако, как мы видим, нижние подчёркивание всё же добавилось.
    Это один из нюансов среды C++ Builder. Однако, можно отучить его добавлять нижнее подчёркивание таким образом:
    Project -> Options -> C++ Compiler -> Output -> Generate underscores on symbol names - перевести в состояние false.

    Для чего же нужна отложенная загрузка?
    Представьте себе ситуацию: вы написали приложение, использующее стандартные системные библиотеки вашей новой операционной системы, скажем, для проверки орфографии.
    Даёте это приложение пользователю, который использует ОС более старой версии, чем у вас и в этой ОС нет функций для проверки орфографии. А пользователю это не сильно и надо. Приложение будет работать, пока не обратится к необходимой функции. То есть, фактически, DLL не нужна до обращения к определённой функции. Исключение отсутствия можно обработать и выдать пользователю предупреждение с просьбой обновить библиотеки своей ОС (и т.п.).

    Использование отложенной загрузки DLL в C++ Builder мало отличается от неявной загрузки.
    В проект добавляется заголовочный (*.h) файл с описаниями и библиотечный файл (*.lib).
    Далее, переходим в Project -> Options -> C++ Linker -> Advanced -> Delay Load DLLs и вписываем название нашей библиотеки (TestDLL.dll).

    Когда библиотека теряет свою необходимость её нужно явно выгрузить с помощью __FUnloadDelayLoadedDLL. В качестве параметра передаём имя DLL (с расширением + параметр регистрозависим).
    Если вы используете многопоточные приложения - убедитесь, что все потоки завершили работу с DLL.

    Примечание: Нельзя использовать отложенную загрузку для библиотек, имеющих секцию импорта (т.е. использующих другие библиотеки), а также Kernel32.dll и RTLDLL.dll (т.к. функции поддержки отложенной загрузки как раз и находятся в последней).

    3. Заключение.

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

    Подведя итоги можно выявить плюсы и минусы описанных методов:

    Явная загрузка:
    + контроль и управление процессом жизни DLL.
    - управлением DLL занимается программист посредством WinAPI.

    Неявная загрузка:
    + все заботы берет на себя компилятор и сборщик.
    - ресурсы заняты всё время жизни приложения.

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

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