Как статически подключить dll

Обновлено: 06.07.2024

Использование DLL в программе на Visual C++

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

При неявном подключении (implicit linking) линкеру передается библиотека импорта (обычно имеет расширение lib), содержащая список переменных и функций DLL, которые могут использовать приложения. Обнаружив, что программа обращается хотя бы к одной из них, линкер добавляет в целевой exe-файл таблицу импорта . Таблица импорта содержит список всех DLL, которые использует программа, с указанием конкретных переменных и функций, к которым она обращается. Позже, когда exe-файл будет запущен, загрузчик проецирует все DLL, перечисленные в таблице импорта, на адресное пространство процесса; в случае неудачи весь процесс немедленно завершается.

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

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

Теперь рассмотрим, как каждый из перечисленных методов используется на практике. Для этого будем считать, что у нас есть библиотека MyDll.dll, которая экспортирует переменную Var, функцию Function и класс Class. Их объявления содержатся в заголовочном файле MyDll.h, который выглядит следующим образом:

Кроме того, будем считать, что библиотека импорта содержится в файле MyDll.lib.

Неявное подключение

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

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

Явное подключение

Загрузка DLL

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

В нашем примере загрузка DLL выглядит так.

Вызов функций

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

Обратите внимание на приведение указателя к ссылке на тип FARPROC. FARPROC - это указатель на функцию, которая не принимает параметров и возвращает int. Именно такой указатель возвращает функция GetProcAddress. Приведение типа необходимо, чтобы умиротворить компилятор, который строго следит за соответствием типов параметров оператора присваивания. Альтернативный подход заключается в использовании оператора typedef с последующим приведением значения, возвращаемого GetProcAddress, к указателю на функцию с нужным прототипом.

Доступ к переменным

Хотя это не всегда очевидно из документации, получить указатель на переменную из DLL можно, используя все ту же функцию GetProcAddress. В нашем примере это выглядит так.

Использование классов

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

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

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

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

Продемонстрирую все сказанное на примере. Сначала мы выделяем память для объекта и вызываем для него конструктор. Память можно выделить как на стеке, так и в куче (с помощью оператора new). Рассмотрим оба варианта.

Обратите внимание на использование операторов .* и ->* для вызова функции-члена класса по указателю на нее. Этими операторами мы будем пользоваться и дальше.

ПРИМЕЧАНИЕ

Как правило, имена функций, экспортируемых из DLL, искажаются линкером. Поэтому вместо понятного имени, такого как "Constructor", получается совершенно нечитабельное имя вида "??0Class@@QAE@XZ". В рассматриваемом примере я назначил переменным и функциям нормальные имена при помощи def-файла следующего содержания:

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

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

В нашем примере виртуальная функция вызывается так.

После того, как работа с объектом завершена, его нужно уничтожить, вызвав для него деструктор. Если объект был создан на стеке, деструктор необходимо вызвать до его выхода из области видимости, иначе возможны неприятные последствия (например, утечки памяти). Если объект был распределен при помощи new, его необходимо уничтожить перед вызовом delete. В нашем примере это выглядит так.

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

Выгрузка библиотеки

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

Отложенная загрузка

Использование отложенной загрузки

Вот и все. Теперь можно использовать функции и классы DLL прозрачно, как и в случае с неявным подключением. Единственная проблема возникает с переменными: их невозможно использовать напрямую. Дело в том, что при обращении к одной из функций в DLL мы на самом деле вызываем функцию __delayLoadHelper, которая и выполняет загрузку DLL (если она еще не загружена), затем получает адрес функции с помощью GetProcAddress и перенаправляет все последующие вызовы функции по этому адресу. Но при обращении к переменной вызова функции не происходит, а значит использовать __delayLoadHelper не удается.

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

Выгрузка библиотеки

Итак, мы установили, что при использовании отложенной загрузки DLL грузится в память, когда происходит обращение к одной из ее функций. Но в последствии нам может потребоваться выгрузить ее, чтобы не занимать зря системные ресурсы. Специально для этого предназначена функция __FUnloadDelayLoadedDLL, объявленная в файле Delayimp.h. Если вы планируете использовать ее, вам нужно задать еще один ключ линкера - /DELAY:UNLOAD. Например:

Имя, которое вы передаете функции __FUnloadDelayLoadedDLL, должно в точности соответствовать имени, заданному в ключе /DELAYLOAD. Если, к примеру, передать ей "MYLIB.DLL" или "mylib.dll", библиотека останется в памяти.

ПРЕДУПРЕЖДЕНИЕ

Не используйте FreeLibrary, чтобы выгрузить DLL с отложенной загрузкой.

Обработка исключений

Как я уже говорил, в случае ошибки функция __delayLoadHelper возбуждает исключение. Если нужная DLL не обнаружена, возбуждается исключение с кодом VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND). Если в DLL не обнаружена требуемая функция, исключение будет иметь код VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND).

ПРИМЕЧАНИЕ

VcppException - это макрос, который используется для формирования кода ошибки в подсистеме Visual C++. Первый параметр задает "степень серьезности" ошибки, а второй - код исключения.

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

Написанный вами фильтр исключений может также получить дополнительную информацию с помощью функции GetExceptionInformation. Эта функция возвращает указатель на структуру EXCEPTION_POINTERS. В ней содержится поле ExceptionRecord - указатель на структуру EXCEPTION_RECORD. А структура EXCEPTION_RECORD в свою очередь содержит поле ExceptionInformation[0], в которое __delayLoadHelper помещает указатель на структуру DelayLoadInfo, содержащую дополнительную информацию. Эта структура объявлена следующим образом (файл Delayimp.h).

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

Функции-ловушки

Функции-ловушки должны иметь следующий прототип:

Первый параметр функции содержит код уведомления или ошибки, второй - указатель на уже знакомую нам структуру DelayLoadInfo. Все возможные коды уведомления описаны в файле Delayimp.h при помощи следующего перечисления:

В качестве примера приведу текст функции-ловушки, которая подменяет вызов функции SomeFunc на вызов функции YetAnotherFunc.

Исполняемый файл можно связать с библиотекой DLL (загрузить ее) одним из двух способов:

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

Явное связывание — операционная система загружает библиотеку DLL по запросу во время выполнения. Исполняемый файл, который использует библиотеку DLL, должен явно загружать и выгружать ее. Кроме того, в нем должен быть настроен указатель функции для доступа к каждой используемой функции из библиотеки DLL. В отличие от вызовов функций в статически скомпонованной или неявно связанной библиотеке DLL, при работе с явно связанной DLL исполняемый файл клиента должен вызывать экспортированные функции с помощью указателей функций. Процесс явного связывания также иногда называют динамической загрузкой или динамической компоновкой времени выполнения.

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

Определение подходящего метода связывания

Решение о применении неявного или явного связывания принимается на уровне архитектуры приложения. Каждый из этих способов имеет свои преимущества и недостатки.

Неявное связывание

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

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

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

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

Как и в случае с остальном кодом программы, загрузчик сопоставляет код DLL в адресном пространстве процесса при запуске процесса. Операционная система загружает его в память только при необходимости. В связи с этим атрибуты кода PRELOAD и LOADONCALL , которые использовались DEF-файлами для управления загрузкой в предыдущих версиях Windows, более не имеют смысла.

Явное связывание

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

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

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

Процесс с неявным связыванием также завершается, если в любой из связанных библиотек DLL функция DllMain завершается сбоем. Процесс с явным связыванием в таких ситуациях не завершается.

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

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

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

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

Если в библиотеке DLL статические элементы данных объявляются как __declspec(thread) , при явном связывании может произойти сбой системы защиты. После загрузки библиотеки DLL посредством вызова LoadLibrary сбой системы защиты будет происходить каждый раз, когда в коде используется ссылка на такие данные. Статические элементы данных включают как глобальные, так и локальные статические элементы. Поэтому при создании библиотеки DLL не рекомендуется использовать локальную память потока. Если это все же необходимо, следует проинформировать пользователей библиотеки о возможных проблемах при ее динамической загрузке. Дополнительные сведения см. в разделе Использование локальной памяти потока в библиотеке динамической компоновки (пакет SDK для Windows).

Использование неявного связывания

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

Один или несколько файлов заголовка (с расширением h), в которых содержатся объявления экспортированных данных, функций и классов C++ в библиотеке DLL. Все классы, функции и данные, экспортируемые из библиотеки DLL, должны быть отмечены в файле заголовка как __declspec(dllimport) . Дополнительные сведения см. в статье dllexport, dllimport.

Библиотека импорта, которая связывается с исполняемым файлом. Библиотека импорта создается компоновщиком при построении DLL. Дополнительные сведения см. в разделе Использование LIB-файлов в качестве входных данных для компоновщика.

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

Для построения исполняемого файла клиента необходимо выполнить связывание с библиотекой импорта DLL. Если вы используете внешние файл makefile или систему сборки, необходимо указывать библиотеку импорта вместе с другими связываемыми файлами объектов или библиотеками.

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

Явное связывание с библиотекой DLL

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

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

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

По завершении работы с библиотекой DLL вызовите FreeLibrary.

В приведенном ниже примере функции вызывается LoadLibrary для загрузки библиотеки MyDLL, затем вызывается GetProcAddress для получения указателя на функцию DLLFunc1, далее вызывается эта функция и сохраняется результат, после чего вызывается FreeLibrary для выгрузки библиотеки DLL.

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

Библиотека — это «сборник» кода, который можно многократно использовать в самых разных программах. Как правило, библиотека в языке C++ состоит из двух частей:

Заголовочный файл, который объявляет функционал библиотеки.

Предварительно скомпилированный бинарный файл, содержащий реализацию функционала библиотеки.

Некоторые библиотеки могут быть разбиты на несколько файлов и/или иметь несколько заголовочных файлов.

Типы библиотек

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

Есть 2 типа библиотек: статические и динамические.

Статическая библиотека (или «архив») состоит из подпрограмм, которые непосредственно компилируются и линкуются с вашей программой. При компиляции программы, которая использует статическую библиотеку, весь функционал статической библиотеки (тот, что использует ваша программа) становится частью вашего исполняемого файла. В Windows статические библиотеки имеют расширение .lib (сокр. от «library»), тогда как в Linux статические библиотеки имеют расширение .a (сокр. от «archive»).

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

Динамическая библиотека (или «общая библиотека») состоит из подпрограмм, которые подгружаются в вашу программу во время её выполнения. При компиляции программы, которая использует динамическую библиотеку, эта библиотека не становится частью вашего исполняемого файла — она ​​так и остается отдельным модулем. В Windows динамические библиотеки имеют расширение .dll (сокр. от «dynamic link library»), тогда как в Linux динамические библиотеки имеют расширение .so (сокр. от «shared object»). Одним из преимуществ динамических библиотек является то, что разные программы могут совместно использовать одну копию динамической библиотеки, что значительно экономит используемое пространство. Еще одним преимуществом динамической библиотеки является то, что её можно обновить ​​до более новой версии без необходимости перекомпиляции всех исполняемых файлов, которые её используют.

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

Установка библиотек

Теперь, когда мы уже разобрались с типами библиотек, давайте поговорим о том, как их использовать в наших программах. Установка библиотеки в языке C++ состоит из четырех последовательных шагов:

Шаг №1: Получите библиотеку. Наилучшим вариантом является найти уже предварительно скомпилированный код (если он вообще существует) под вашу операционную систему, чтобы вам не пришлось компилировать библиотеку самостоятельно. В Windows библиотеки обычно распространяются в виде архивов (файлов .zip), а в Linux это пакеты кода (например, пакеты .rpm).

Шаг №2: Установите библиотеку. В Linux это делается путем вызова менеджера пакетов, а дальше он всё делает сам. В Windows вам придется разархивировать библиотеку самостоятельно в любую выбранную вами папку. Рекомендуется хранить все используемые библиотеки в одном месте для быстрого доступа к ним. Например, создайте папку Libs ( C:\Libs ) и выделяйте для каждой (используемой вами) библиотеки свою отдельную подпапку.

Шаг №3: Убедитесь, что компилятор знает, где искать заголовочные файлы библиотеки. В Windows это обычно подпапка include внутри основной папки библиотеки (например, если вы установили библиотеку в C:\Libs\SDL-1.2.11 , то заголовочные файлы находятся в C:\Libs\SDL-1.2.11\include ). В Linux библиотеки обычно устанавливаются в /usr/include . Однако, если файлы находятся в другом месте, вам нужно будет сообщить компилятору, где именно.

Шаг №4: Сообщите линкеру, где искать файлы с реализацией функционала библиотеки. Аналогично с предыдущим шагом, вам нужно указать линкеру место, где находятся файлы с реализацией библиотеки. В Windows это обычно подпапка \lib внутри основной папки библиотеки ( C:\Libs\SDL-1.2.11\lib ), а в Linux это обычно /usr/lib .

Использование библиотек

Как только библиотека установлена, ​​и ваша IDE знает, где искать её файлы, то для того, чтобы вы могли использовать эту библиотеку в ваших проектах, вам необходимо выполнить следующие 3 шага:

Шаг №5: Если вы используете статические библиотеки или библиотеки импорта, сообщите линкеру, какие файлы библиотеки нужно связать с вашей программой.

Шаг №6: Подключите заголовочные файлы библиотеки к вашей программе.

Шаг №7: Если вы используете динамические библиотеки, то убедитесь, что исполняемые файлы будут иметь доступ к файлам библиотеки. Самый простой способ использовать .dll — это скопировать .dll в папку с исполняемым файлом. Поскольку .dll-ка обычно распространяется вместе с исполняемым файлом, то это не составит труда.

Заключение

Шаги №3-№5 включают настройку вашей IDE. К счастью, почти все IDE работают одинаково, когда дело доходит до выполнения подобных задач. На следующем уроке мы рассмотрим, как выполнить данные шаги в Visual Studio.

Библиотеки могут также распространяться в виде скомпилированных двоичных файлов, которые могут быть прилинкованы к программе клиента. Линковка может быть статическая и динамическая. Статическая представляет собой собранние *.obj-файлов библиотеки в *.lib, который мы можем, указав линкеру, прицепить к нашей программе в момент компиляции. Содержимоей библиотеки, как всегда, описывается в хэдерах, которые распространяются вместе с *.lib . На выходе мы получим одинокий исполняемый файл вашей программы (*.exe).
Динамическая линковка выполняется средствами платформы (операционной системы) в процессе работы программы. Все так же у нас в руках *.lib и *.h файлы, однако, теперь к ним добавляется *.dll (*.so для Linux). *.lib-файл теперь содержит только вызовы к *.dll, где лежат непосредственно алгоритмы и которые вызываются уже на ходу, а не компилируются. Потому теперь у нас *.exe + *.dll . Несколько программ могут использовать один *.dll одновременно, тем самым не занимая оперативную память одинаковыми кусками и сами программы меньше размером. Так, например, работают многие драйверы и графические библиотеки (DirectX и OpenGL). Однако, сейчас это не такая актуальная проблема, тянут недостатки - несовместимости версий, отсутствие нужных библиотек, ад зависимостей для установки приложений (работая в Linux с графическим окружением Gnome (основанной на библиотеке GTK+) если скачать малюсенький текстовый редактор Kate для ГО KDE (основанной на Qt), то придется тянуть этот-самый Qt на десятки мегобайт). Потому, сейчас рекомендуют не увлекаться динамической линковкой и стараться связывать программы статически.

Статическая компоновка

Пусть у нас есть простой проект с одним классом, который мы хотим передать клиенту в двоичном виде:

И его реализация:

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

Попробуем слинковать статически с другим проектом в Microft Visual Studio 2010:

Сначала убедимся, что компилируем наш проект-библиотеку как *.lib: зайдем в свойства проекта (Project -> имя_проекта Properties. ), там на Configuration Properties -> General. Далее в Project Defaults в пункте Configuration Type должно быть установлено Static library (*.lib). Соберем проект, убедимся, что в папке Debug/Release появился файл *.lib
Затем зайдем в свойства проекта-клиента (Project -> имя_проекта Properties. ), там на Configuration Properties -> C/C++ в Additional Include Directories добавим папку с хэдером библиотеки (можно также добавить хэдер в раздел Header Files в Solution Explorer`е к остальным исходникам). Теперь нам нужно сказать линкеру, чтобы он нашел и прилинковал *.lib к нашему проекту. Там же в Configuration Properties -> Linker -> General в Additional Library Directories указать папку с *.lib . И в соседнем пункте дерева настроек линкера Input найти самый верхний пункт Additional Dependencies, куда добавить имя_библиотеки.lib . Все готово! Клиент не знает, что делают ваш Implementation.cpp, ибо у него на руках только имя_библиотеки.lib и Header.h .

Динамическая компоновка

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