Как сделать из so dll

Добавил пользователь Владимир З.
Обновлено: 05.10.2024

Очень часто в своей работе, Вы будете сталкиваться с такой ситуацией.

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

Через пару недель программа была написана, затем она попала в Интернет, пользователи оценили новый продукт и стали им пользоваться. Цель достигнута.

Проходит время и перед вами ставят новую задачу, написать программу "Супер парсер". Одной из функции данной программы, будет подсчет слов в тексте. Вы понимаете, что снова придется разрабатывать метод, который будет вести подсчёт слов. Но, при этом вспоминаете, что совсем не давно уже разрабатывали программу, в которой применялась данная функция. Чтобы не изобретать велосипед, Вы открываете исходник программы "Супер блокнот"; и копируете весь метод в исходник новой программы "Супер парсер". Отлично, теперь Вам не придется тратить время на написание этого метода заново, и Вы можете посветить больше времени остальным элементам программы. Задача решена.

28409

Но, что если метод по подсчету слов, писали не Вы, а допустим, какой-нибудь коллега по работе и по каким-то причинам, Вы не можете получить доступ к исходному коду программы "Супер блокнот". То есть первый вариант, копирование метода из исходника, не прокатит и данный метод придется писать самому ммм, печалька.

Но, тут вам звонит ваш коллега по работе и говорит: Ты знаешь, я тут вспомнил, когда я разрабатывал данный метод, я подумал, что возможно мне придется его использовать ещё где-то, и по этому я решил вынести его в отдельную сборку, в виде файла динамической библиотеки (dll).Ты просто скопируй этот файл dll в свой проект, и подключи его, как внешнюю сборку, после чего ты получишь доступ к моему методу и сможешь им пользоваться.

Отлично! Вы проделываете все описанные действия, в программе “Супер парсер” появляется нужный метод, задача решена и вам вновь не пришлось повторно писать код руками.

28410

На этом присказка закончена и теперь переходим к более подробному изучению.

Что такое DLL

DLL (dynamic-link library) - это динамически подключаемая библиотека, или сокращено динамическая библиотека.


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

Создание файла dll

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

Выбираем Class Library, то есть создаем файл динамической библиотеки (dll)

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

28411

После того, как Visual Studio создаст каркас проекта, Вы увидите следующее:

Так будет выглядеть окно Solution Explorer

28412

А так будет выглядеть рабочая область, где Вы обычно пишите код программы

28413

И так дано пространство имён: Car и класс: Class1. Class1 не удачное название, давайте немного изменим наш код, заменив Class1 на BMW, и добавим метод, который будет выводить имя нашего класса.

28414

И так код написан, и теперь необходимо выполнить компиляцию, чтобы получить сборку.
Если сейчас попытаться нажать F5 или Ctrl+F5, то вы увидите данное окно

28415

Данная ошибка, говорит лишь о том, что был создан файл динамической библиотеки (dll), а не исполняемый файл (exe), который не может быть запущен.

Для того, чтобы скомпилировать проект, нажмите клавишу F6, после чего в директории bin\Debug появиться файл Car.dll.

Чтобы проверить был ли создан файл библиотеки, воспользуйтесь кнопкой Show All Files на вкладке Solution Explorer

28416

Сборка, в виде файла динамической библиотеки успешно создана.

Теперь перейдем в папку bin\Debug, для того, чтобы быстро переместиться в текущую директорию проекта, в том же Solution Explorer воспользуйтесь пунктом Open Folder in Windows Explorer

28417

Скопируйте полученный файл сборки (в нашем случае - это файл Car.dll) в какую-нибудь временную папку. На самом деле, это делать необязательно, Вы можете оставить данный файл в этой папке, но для удобства, создадим новую папку, и поместим туда созданный файл библиотеки.

Создаем новый проект.

28418

Новый проект создан. Теперь подключим в текущий проект, нашу библиотеку (Car.dll)

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

Для этого на папке References, в окне Solution Explorer нужно нажать правую кнопку мыши и выбрать пункт Add Reference, откроется вот такое окно:

28419

  1. Выберите вкладку Browse
  2. Укажите папку, в которой лежит файл созданной библиотеки (в нашем примере - Car.dll)
  3. Выделите файл нужной библиотеки и нажмите кнопку ОК;

28420

На ней видно, что в наш текущий проект была успешна, добавлена ссылка на нашу сборку Car.dll, в которой храниться наш код на языке IL. Надеюсь, Вы помните, что после компиляции весь написанный вами код преобразуется в промежуточный код на языке IL (CIL, MSIL - это одно и тоже). А так же видно, что в папку bin\Debug была помещёна копия нашей библиотеки.

28421

Если вдруг Вы забыли, какое пространство имен, типы, или члены содержит ваша созданная библиотека. Вы всегда можете воспользоваться таким инструментом Visual Studio, как Object Browser. Для этого перейдите на вкладку Solution Explorer, откройте папку References, и просто щёлкните правой кнопкой мыши по добавленной ранее библиотеке, в нашем случае напоминаю - это файл (Car.dll) и выберите пункт View in Object Browser, появиться вот такое окно.

В окне Object Browser можно посмотреть содержимое нашей сборки.

Сборка подключена и теперь Вы можете работать с её содержимым. Далее выполним необязательный пункт.

Добавим, с помощью ключевого слова using пространство имен Car из созданной нами библиотеки Car.dll, после чего создадим объект класса BMW и выполним метод Вывести_Имя_Класса().

28423

28424

  1. Создаем файл динамической библиотеки (dll)
  2. Подключаем созданную библиотеку в наш проект, путем добавления в папку References ссылки на наш файл dll.
  3. (Необязательный пункт) Подключаем пространство имен из подключенной сборки, с помощью ключевого слова using, либо используем полное наименование, то есть Пространство имён.Тип (Car.BMW).
  4. Profit

И в конце не много информации о типах сборок:

Сборки бывают следующих основных видов: общие и частные.

Частная сборка (private assembly)

Это файлы библиотек, как наш ранее созданный файл Car.dll, которые содержаться на протяжении всего времени в каталоге текущего приложения или любом из его подкаталогов.

Вернёмся к началу статьи.

После того, как было создано приложение “Супер парсер”, мы получили сборку в виде файла (exe). Затем мы решили протестировать нашу программу и отдаём её нашему другу, при этом Вы так же упоминаете, что если он хочет иметь дополнительные функции в программе, то ему нужно просто рядом с его exe файлом поместить файл библиотеки Car.dll. После чего он получит возможность подсчёта слов в тексте. То есть библиотека будет храниться в той же директории, что и исполняемый файл.

28425

Общие сборки (shared assembly)

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

Я участвовал в некоторых дебатах по поводу библиотек в Linux и хотел бы подтвердить некоторые вещи.

Насколько я понимаю (пожалуйста, исправьте меня, если я ошибаюсь, и я отредактирую свой пост позже), что есть два способа использования библиотек при создании приложения:

  1. Статические библиотеки (файлы .a): во время компоновки копия всей библиотеки помещается в конечное приложение, чтобы функции внутри библиотеки всегда были доступны вызывающему приложению
  2. Общие объекты (файлы .so). Во время соединения объект просто проверяется на соответствие его API через соответствующий файл заголовка (.h). Библиотека фактически не используется до времени выполнения, где это необходимо.

Очевидное преимущество статических библиотек заключается в том, что они позволяют полностью автономно использовать все приложение, тогда как преимущество динамических библиотек заключается в том, что файл ".so" можно заменить (т.е. в случае необходимости его обновления из-за безопасности. ошибка) без необходимости перекомпиляции базового приложения.

Я слышал, что некоторые люди делают различие между общими объектами и динамически связанными библиотеками (DLL), хотя они оба являются файлами ".so". Есть ли какое-либо различие между общими объектами и библиотеками DLL, когда речь заходит о разработке C/C++ в Linux или любой другой POSIX-совместимой ОС (например, MINIX, UNIX, QNX и т.д.)? Мне говорят, что одно ключевое отличие (до сих пор) заключается в том, что общие объекты просто используются во время выполнения, в то время как DLL необходимо сначала открыть с помощью вызова dlopen() в приложении.

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

Заранее благодарю всех за помощь.

  1. Общий объект: библиотека, которая автоматически связывается с программой при запуске и существует как отдельный файл. Библиотека включается в список ссылок во время компиляции (то есть: LDOPTS+=-lmylib для файла библиотеки с именем mylib.so ). Библиотека должна присутствовать во время компиляции и при запуске приложения.
  2. Статическая библиотека: библиотека, которая объединяется с самой программой во время сборки для одного (большего) приложения, содержащего код приложения и код библиотеки, который автоматически связывается с программой при сборке программы, и конечный двоичный файл, содержащий оба основная программа и сама библиотека существуют в виде отдельного двоичного файла. Библиотека включается в список ссылок во время компиляции (то есть: LDOPTS+=-lmylib для файла библиотеки с именем mylib.a). Библиотека должна присутствовать во время компиляции.
  3. DLL: по сути то же самое, что и общий объект, но вместо того, чтобы быть включенным в список ссылок во время компиляции, библиотека загружается с помощью команд dlopen() / dlsym() , так что библиотека не должна присутствовать во время сборки для программа для компиляции. Кроме того, библиотека не обязательно должна присутствовать (обязательно) при запуске приложения или во время компиляции, поскольку она необходима только в момент выполнения вызовов dlopen / dlsym .
  4. Общий архив: по сути, такой же, как статическая библиотека, но компилируется с флагами "export-shared" и "-fPIC". Библиотека включается в список ссылок во время компиляции (то есть: LDOPTS+=-lmylibS для файла библиотеки с именем mylibS.a ). Различие между ними заключается в том, что этот дополнительный флаг необходим, если общий объект или DLL хотят статически связать общий архив с собственным кодом и иметь возможность сделать функции в общем объекте доступными для других программ, а не просто использовать их. внутренний в DLL. Это полезно в случае, когда кто-то предоставляет вам статическую библиотеку, и вы хотите переупаковать ее как SO. Библиотека должна присутствовать во время компиляции.

Различие между " DLL " и " shared library " было просто (ленивым, неточным) коллоквиализмом в компании, в которой я работал в то время (разработчики Windows были вынуждены перейти к разработке Linux, и термин застрял), придерживаясь к описаниям, указанным выше.

Кроме того, конечный литерал " S " после имени библиотеки, в случае "общих архивов", был просто соглашением, используемым в этой компании, а не в отрасли в целом.

Я всегда думал, что DLL и общие объекты - это просто разные термины для одного и того же - Windows называет их DLL, в то время как в UNIX-системах они являются общими объектами, причем общий термин - динамически связанная библиотека - охватывает как (даже функция для открытия .so в UNIX называется dlopen() после "динамической библиотеки" ).

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

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

A статическая библиотека (.a) - это библиотека, которая может быть напрямую связана с конечным исполняемым файлом, созданным компоновщиком, она содержится в нем, и нет необходимости иметь библиотеку в системы, в которой будет развернут исполняемый файл.

A общая библиотека (.so) - это библиотека, которая связана, но не внедрена в окончательный исполняемый файл, поэтому будет загружаться при запуске исполняемого файла и должна присутствовать в системе, исполняемый файл.

Динамическая библиотека ссылок на Windows (.dll) похожа на общую библиотеку (.so) на linux, но есть некоторые различия между двумя реализациями, связанными с ОС (Windows vs Linux ):

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

Библиотека SO для Linux не нуждается в специальной инструкции экспорта для указания экспортируемых символов, поскольку все символы доступны для процесса опроса.

Я могу подробно рассказать о DLL файлах в Windows, чтобы помочь прояснить эти тайны моим друзьям здесь, в NIX-land.

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

У библиотек Windows есть таблица экспорта. Экспорт может быть указан по имени или по таблице (числовой). Последний метод считается "старой школой" и гораздо более хрупким - перестройка DLL и изменение позиции функции в таблице закончится катастрофой, в то время как нет реальной проблемы, если связывание точек входа по имени. Итак, забудьте об этом как о проблеме, но просто имейте это в виду, если вы работаете с кодом "динозавр", например, сторонними поставщиками libs.

Библиотеки Windows создаются путем компиляции и компоновки, как и для EXE (исполняемого приложения), но DLL предназначена не для одиночки, как SO, предназначенная для использования приложением, либо через динамическое загрузка или привязка времени привязки (ссылка на SO встроена в бинарные метаданные приложения, а загрузчик программ ОС будет автоматически загружать ссылки SO). DLL могут ссылаться на другие DLL, так же как SO могут ссылаться на другие SO.

В Windows, библиотеки DLL будут предоставлять только определенные точки входа. Они называются "экспорт". Разработчик может либо использовать специальное ключевое слово компилятора, чтобы сделать символ видимым извне (другим компоновщикам и динамическим загрузчиком), либо экспорт может быть указан в файле определения модуля, который используется во время соединения, когда сама DLL создается. Современная практика заключается в том, чтобы украсить определение функции ключевым словом для экспорта имени символа. Также возможно создавать файлы заголовков с ключевыми словами, которые будут объявлять этот символ как один, который будет импортирован из DLL за пределами текущего модуля компиляции. Для получения дополнительной информации найдите ключевые слова __declspec (dllexport) и __declspec (dllimport).

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

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

Путь к Windows может привести к некоторой путанице; система использует расширение .LIB для обозначения как обычных статических библиотек (архивов, таких как файлы POSIX *.a), так и библиотек "export stub", необходимых для привязки приложения к DLL во время ссылки. Поэтому всегда нужно посмотреть, имеет ли файл *.LIB файл с одинаковым именем *.DLL; если нет, то возможно, что файл *.LIB является архивом статической библиотеки, а не экспортирует метаданные связывания для DLL.

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

Итак, начнем с создания. Используется Microsoft Visual Studio 2005
File->New->Project->Win32 Project, затем вписываем имя, я назову свою DLL просто - MyDLL. Затем нажимаем ОК. В появившемся окне щелкаем на кнопку Next. Ставим галочку напротив DLL. Поля ATL и MFC оставляем пустыми. Empty project тоже нужно поставить галочку. Будем писать все с нуля. Нажимаем Finish.
Теперь нам нужно добавить в проект *.cpp файл с исходным кодом.
Правой кнопкой по папке Source Files в Solution Explorer ->New Item.
Выбираем cpp-файл, я дал ему имя Source.cpp.
Теперь допишем сюда нашу функцию. Моя функция будет считать количество вхождений символа в строку, и возвращать значение типа int.
Но сначала добавим в этот проект еще один файл - MyDll.h, и скопируем туда вот этот код

Как вы поняли int SymbolNumber(const char *, char, int) - это моя функция. Здесь я написал прототип.
extern "C" _declspec(dllexport) - это означает, что моя функция будет экспортироваться в ехе файл программы.
Дальше пишем реализацию в файл Source.cpp
Итак, готовый код

Теперь нажимаем F7 и линкуем нашу DLL.
Наша DLL готова.
Следующий пост я напишу как загрузить мою функцию из DLL в проект.

Итак, как загрузить DLL в программу. Существует два способа - статически и динамически. Первый загружает DLL в память на этапе компиляции, а второй - на этапе выполнения программы.
1)Статическое подключение
Когда мы слинковали нашу DLL, мы получили 3 файла.
1. MyDll.dll
2. MyDll.lib
3. MyDll.exp
Первый файл - собственно DLL. Второй - таблица импорта. Третий - таблица экспорта.
Чтобы подключить статически DLL к программе, нам нужно:
1. Подключить h-файл этой DLL к нашей программе (если такого файла нет, то подключать ничего не нужно )
2. Подключить lib-файл к нашей программе.
В нашем случае нужно написать две строки в самом начале программы (после инклудов)

Перед этим неплохо бы скинуть эти два файла в папку с программой. Или прописать полный путь к этим файлам.
Все, дальше можно вызывать эту функцию как обычную функцию.
Для примера привожу готовый код программы, которая импортирует функцию из DLL. На WinApi
File->New->Project->Win32 Project->Windows Application->Empty Project->Finish
Source Files->New Item->Main.cpp

2)Динамическое подключение
Чтобы загрузить DLL в программу на этапе выполнения, нужно пройти несколько шагов.
Шаг 1:
Использовать функцию LoadLibrary или LoadLibraryEx, чтобы загрузить DLL.
Шаг 2:
Использовать функцию GetProcAddress, чтобы получить указатель на интересующую нас функцию.
Шаг 3:
По окончании работы не забыть выгрузить DLL с помощью функции FreeLibrary.

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

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

Дальше - нам нужно получить указатель на функцию. Но функция у нас не простая, а с кучей параметров. Тоесть нужно определить тип указателя. Это делается строкой (в нашем случае)

Тоесть мы определили functionDll как тип, который указывает на функцию, которая принимает три параметра и возвращает значение int.
Далее - дело за малым.

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

Покажите пожалуйста код для создания C++ DLL, которая будет содержать пару строк (string или int), чтобы при обращении к ней, к определенному методу - возвращало ту или иную строку.

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

3 ответа 3

При этом в проекте сборки dll, должен присутствовать DEFINE MYLIBRARY

Для Qt-creator для этого нужно в pro-файл добавить DEFINES += MYLIBRARY

В Visual Studio в настройках проекта С/С++ -> Preprocessor -> Preprocessor Definitions просто в конце добавить MYLIBRARY.

Стоит помнить, что библиотеки, в которых используются не только встроенные типы (char, int, float, double, bool, long), а и что-то другое(в том числе и std::string) требуют использования в основной программе(откуда загружается библиотека) использования того же компилятора, в случае с visual c++ еще и требуется соблюдения версии vc10, vc12, vc14 и пр. Так как разные компиляторы могут формировать разные имена экспортируемым функциям.

Если нужно использовать функции из других языков программирования, или с другим компилятором, или с помощью динамической загрузки библиотеки и использования GetProcAddress. То в h файле должны быть только объявления с использованием встроенных типов: (char, int, float, double, bool, long) и с добавлением extern "C", например:

Вы хотите использовать свои инструменты командной строки для сборки динамической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.2.

Выполните следующие шаги.

1. Используйте компилятор для компиляции исходных файлов в объектные файлы. Если вы используете Windows, то для определения макросов, необходимых для организации экспорта символов динамической библиотеки, используйте опцию -D. Например, чтобы собрать динамическую библиотеку из примера 1.2, вы должны определить макрос GEORGERINGO_DLL. Если вы собираете библиотеку, написанную кем-то другим, то макросы, которые требуется определить, должны быть описаны в инструкции по установке.

2. Используйте компоновщик для создания из объектных файлов, созданных на шаге 1, динамической библиотеки.


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

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

Табл. 1.11. Команды для создания динамической библиотеки libgeorgeringo.so, libgeorgeringo.dll или libgeorgeringo.dylib

Инструментарии Командная строка GCC g++ -shared -fPIC -o libgeorgeringo.so george.o ringo.с georgeringo.о GCC (Mac OS X) g++ -dynamclib -fPIC -o libgeorgeringo.dylib george.o ringo.о georgeringo.o GCC (Cygwin) g++ -shared -o libgeorgeringo.dll -Wl,--out-implib,libgeorgeringo.dll,a -Wl,--export- all-symbols -Wl,--enable-auto-image-base george.o ringo.o georgeringo.o GCC (MinGW) g++ -shared -о libgeorgeringo.dll -Wl,-out-implib,libgeorgeringo.a -Wl,--export-all- symbols, -Wl,--enable-auto-image-base george.о ringo.о georgeringo.o Visual C++ link -nologo -dll -out:libgeorgeringo.dll -implib:libgeorgeringo.lib george.obj ringo.obj georgeringo.obj Intel (Windows) xilink -nologo -dll -out:libgeorgeringo.dll -implib:libgeorgeringo.lib george.obj ringo.obj georgeringo.obj Intel (Linux) g++ -shared -fPIC -lrt -o libgeorgeringo.so george.o ringo.о georgeringo.o georgeringo.obj Metrowerks (Windows) mwld -shared -export dllexport -runtime dm -o libgeorgeringo.dll implib libgeorgeringo.lib george.obj ringo.obj georgeringo.obj Metrowerks (Mac OS X) mwld -shared -export pragma -o libgeorgeringo.dylib george.o ringo.о georgeringo.о CodeWarrior 10.0 (Mac OS X)? Сверьтесь с документацией Metrowerks Borland bcc32 -q -WD -WR -elibgeorgeringo.dll george.obj ringo.obj georgeringo.obj implib -c libgeorgeringo.lib libgeorgeringo.dll Digital Mars dmc -WD -L/implib:libgeorgeringo.lib -о libgeorgeringo.dll george.obj ringo.obj georgeringo.obj user32.lib kernel32.lib

? CodeWarrior 10.0 для Mac OS X будет содержать динамический вариант своих библиотек. При сборке libgeorgeringo.dylib следует использовать именно их. (См. рецепт 1.23.)


По состоянию на сентябрь 2005 года инструментарий Comeau не поддерживал сборку динамических библиотек в Unix или Windows. Однако Comeau Computing работает над поддержкой динамических библиотек, и ожидается, что к концу 2005 года эта поддержка будет реализована для некоторых платформ Unix, включая Linux.

Например, чтобы скомпилировать исходные файлы из примера 1.2 в объектные файлы с помощью компилятора Borland, предполагая, что директория, в которой находятся инструменты Borland, включена в переменную PATH, перейдите в директорию georgeringo и введите следующие команды.

> bcc32 -с -a -WR -о george.obj george.cpp

> bcc32 -c -q -WR -o ringo.obj ringo.cpp

> bcc32 -c -q -WR -DGERORGERINGO_DLL -o georgeringo.obj georgeringo.cpp

Здесь опция компилятора -WR используется для указания того, что применяется динамический вариант рабочей библиотеки. Эти три команды сгенерируют объектные файлы george.obj, ringo.obj и georgeringo.obj. Затем введите команду:

> bcc32 -q -WD -WR -elibgeorgeringo.dll george.obj ringo.obj georgeringo.obj

Она сгенерирует динамическую библиотеку libgeorgeringo.dll. Наконец, введите команду:

> implib -с libgeorgeringo.lib libgeorgeringo.dll

Она сгенерирует библиотеку импорта libgeorgeringo.lib.

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

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

В случае с большинством инструментариев для Windows, чтобы символ, определенный в динамической библиотеке, был доступен коду, использующему эту библиотеку, он должен быть явно экспортирован при сборке динамической библиотеки и импортирован при сборке исполняемого файла или другой динамической библиотеки, использующей эту библиотеку. Некоторые инструменты для Unix также предлагают такие возможности. Это верно для последних версий GCC для некоторых платформ, для Metrowerks на Mac OS X и для Intel для Linux. Однако в некоторых случаях нет никакого выбора, кроме как сделать все символы видимыми.

Передача библиотек компоновщику

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

Библиотеки импорта и файлы определения модулей

Библиотеки импорта, грубо говоря, являются статическими библиотеками, содержащими информацию, необходимую для вызова функций, содержащихся в DLL, во время исполнения. Нет необходимости знать, как они работают, надо только знать, как их создавать и использовать. Большинство компоновщиков создает библиотеки импорта автоматически при сборке DLL, но в некоторых случаях может оказаться необходимо использовать отдельный инструмент, который называется библиотекарь импорта (import librarian). В табл. 1.11 с целью избежать запутанного синтаксиса командной строки, требуемого компоновщиком Borland ilink32.exe, я использовал библиотекарь импорта Borland implib.exe.

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

Пример 1.5. Файл определения модуля для libgeorgeringo.dll

Экспорт символов из DLL

Имеется два стандартных метода экспорта символов из Windows DLL.

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

Атрибут __dеclspec(dllexport) должен указываться в начале объявления экспортируемой функции или данных, вслед за какими-либо спецификаторами сборки, и сразу за ним должно следовать ключевое слово class или struct для экспортируемого класса. Это проиллюстрировано в примере 1.6. Заметьте, что __declspec(dllexport) не является частью языка С++; это расширение языка, реализованное для большинства компиляторов для Windows.

• Создание файла .def, описывающего функции и данные, экспортируемые из динамической библиотеки.

Пример 1.6. Использование атрибута __declspec(dllexport)

__declspec(dllexport) int m = 3; // Экспортируемое определение данных

extern __declspec(dllexport) int n; // Экспортируемое объявление данных

__declspec(dllexport) void f(); // Экспортируемое объявление функции class

Использование файла .def имеет несколько преимуществ — например, он может позволить осуществлять доступ к функциям в DLL по номеру, а не по имени, что сокращает размер DLL. Он также устраняет необходимость запутанных директив препроцессора, таких как показанные в примере 1.2 в заголовочном файле georgeringo.hpp. Однако он также имеет и несколько серьезных недостатков. Например, файл .def не может использоваться для экспорта классов. Более того, можно забыть обновить свой файл .def при добавлении, удалении или изменении функций в вашей DLL. Таким образом, я рекомендую вам всегда использовать __declspec(dllexport). Чтобы изучить полный синтаксис файлов .def, а также научиться их использовать, обратитесь к документации по своему инструментарию.

Импорт символов из DLL

Как есть два способа экспорта символов из DLL, так есть и два способа импорта символов.

• В заголовочных файлах, включенных в исходный код, использующий DLL, используйте атрибут __declspec(dllimport) и при сборке этого кода передайте библиотеку импорта компоновщику.

• При сборке кода, использующего DLL, укажите файл .def.

Как и в случае с экспортом символов, я рекомендую вместо файлов .def использовать в вашем исходном коде атрибут __declspec(dllimport). Атрибут __declspec(dllimport) используется точно так же, как и атрибут __declspec(dllexport), обсуждавшийся ранее. Аналогично __declspec(dllexport) атрибут __declspec(dllimport) не является частью языка С++, а является расширением языка, реализованным для большинства компиляторов для Windows.

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

Вместо этого обычно используют определение макроса, который при сборке DLL расширяется как __declspec(dllexport), а в противном случае — как __declspec(dllimport). В примере 1.2 я использовал для этой цели макроопределение GEORGERINGO_DECL. В Windows, если определен символ GEORGERINGO_SOURCE, то GEORGERINGO_DECL раскрывается как __declspec(dllexport), а в противном случае — как __declspec(dllimport). Описанный результат вы получите, определив GEORGERINGO_SOURCE при сборке DLL libgeorgeringo.dll, но не определяя его при компиляции кода, использующего libgeorgeringo.dll.

Сборка DLL с помощью GCC

Порты GCC Cygwin и MinGW, обсуждавшиеся в рецепте 1.1, работают с DLL по-другому, нежели остальные инструменты для Windows. При сборке DLL с помощью GCC по умолчанию экспортируются все функции, классы и данные. Это поведение можно изменить, использовав опцию компоновщика --no-export-all-symbols, применив в исходных файлах атрибут __declspec(dllexport) или используя файл .def. В каждом из этих трех случаев, если вы не используете опцию --export-all-symbols, чтобы заставить компоновщик экспортировать все символы, экспортируются только те функции, классы и данные, которые помечены атрибутом __declspec(dllexport) или указаны в файле .def.

Таким образом, инструментарий GCC можно использовать для сборки DLL двумя способами: как обычный инструментарий Windows, экспортирующий символы явно с помощью __declspec, или как инструментарий Unix, автоматически экспортирующий все символы[1]. В примере 1.2 и табл. 1.11 я использовал последний метод. Если вы выберете этот метод, вы должны в целях предосторожности использовать опцию --export-all-symbols — на тот случай, если у вас окажутся заголовки, содержащие __declspec(dllexport).

GCC отличается от других инструментов для Windows и еще в одном: вместо того чтобы передавать компоновщику библиотеку импорта, связанную с DLL, вы можете передать саму DLL. Это обычно быстрее, чем использование библиотеки импорта. Однако это может привести к проблемам, так как в одной и той же системе может существовать несколько версий одной DLL, и вы должны быть уверены, что компоновщик выберет правильную версию. В табл. 1.11 при демонстрации того, как создавать библиотеки импорта с помощью GCC, я решил не использовать эту возможность.


В случае с Cygwin библиотека импорта для DLL xxx.dll обычно называется xxx.dll.a, в то время как в случае с MinGW она обычно называется xxx.a. Это просто вопрос соглашения.

Опция -fvisibility в GCC 4.0

Последние версии GCC на некоторых платформах, включая Linux и Mac OS X, дают программистам возможность более тонкого управления экспортом символов из динамических библиотек: опция командной строки -fvisibility используется для указания видимости символов динамической библиотеки по умолчанию, а специальный атрибут, аналогичный __declspec(dllexport) в Windows, используется в исходном коде для изменения видимости символов по отдельности. Опция -fvisibility имеет несколько различных значений, но два наиболее интересных — это default и hidden. Грубо говоря, видимость default означает, что символ доступен для кода других модулей, а видимость hidden означает, что не доступен. Чтобы включить выборочный экспорт символов, укажите в командной строке -fvisibility=hidden и используйте атрибут visibility (видимость) для пометки символов как видимых, как показано в примере 1.7.

Пример 1.7. Использование атрибута visibility с опцией командной строки -fvisibility=hidden

extern __attribute__((visibility("default"))) int m; // экспортируется

extern int n; // не экспортируется

__attribute__((visibility("default"))) void f(); // экспортируется

void g(); // не экспортируется

struct __attribute__((visibility("default"))) S < >; // экспортируется

struct T < >; //не экспортируется

В примере 1.7 атрибут __attribute__((visibility("default"))) играет ту же роль, что и __declspec(dllexport) в коде Windows.

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