Формат dll как сделать
Ещё во-времена старушки дос стало очевидно, что некоторые программные модули лучше хранить не внутри исполняемого файла, а вынести их наружу и подгружать по мере необходимости. В то время такие "прицепы" называли оверлеями и с точки зрения экономии 1М-байтного адресного пространства это было разумно – одну большую программу кромсали на мелкие части, и эти части отрабатывали в памяти по очереди. Подобная техника докатилась и до наших дней, только теперь это динамически подгружаемые DLL.
По сути, нет смысла копать данную тему в очередной раз – всё давно расписано, поэтому добавим экшена и сделаем ставку на нестандартное их применение. Как показывает практика, фишка с защитой программ на основе статически прилинкованных DLL пользуется спросом среди коммерческого софта, значит пора сорвать с неё вуаль и познакомиться по-ближе.
---------------------------------------
"Dynamic Link Library" или DLL – это часть исполняемого РЕ-файла в виде внешнего модуля. Он оформлен как ларчик с N-нным количеством уникальных для наших программ функций, которых нет в составе системных Win32-API. Программный экзо-скелет динамических библиотек идентичен исполняемым файлам экзе, однако есть и некоторые нюансы:
1. Наличие секции-экспорта в коде, которой могут похвастаться исключительно библиотеки. Приложение может вызывать (импортировать) только те функции из DLL, которые оглашены в её секции-экспорта ".edata" . Однако бывают и внутренние функции, которые DLL не экспортирует во-внешний мир, используя только внутри своей тушки для производственных нужд – к их числу можно отнести, например функцию точки-входа в библиотеку DllEntryPoint() . По сути, в данной статье мы сделаем акцент только на этой процедуре инициализации.
2. Второе отличие DLL от EXE – это не способность ими исполнять свой код самостоятельно, поскольку каждый участок кода обёрнут в отдельную функцию. Все эти функции тупо ждут своего часа, пока их услуги не станут восстребованы экзе-файлу и он не затребует их явно. Исключение здесь составляет только упомянутая точка-входа, с которой без нашего ведома активно общается загрузчик LDR, на протяжении всего времени работы приложения.
Система предоставляет нам два способа подключения DLL к своим проектам – статический и динамический . В первом случае мы подключаем библиотеку и указываем импортируемые из неё функции на этапе компиляции РЕ-файла, и эти функции сразу загружаются в наше адресное пространство, вместе с приложением. Во-втором (динамическом) случае, можно загрузить функцию из DLL в произвольный момент времени, сыграв аккордом LoadLibrary() , GetProcAddress() и FreeLibrary() .
Скомпоновать библиотеку довольно просто – пишем обычный ЕХЕ, только в шапке указываем директиву "format PE DLL" . В результате, из выхлопной трубы fasm'a получим файл в формате *.dll. Однако при программировании пользовательских библиотек нужно учитывать ряд их особенностей, в частности релокацию образа в памяти.
Чтобы DLL не загрузилась поверх исполняемого приложения (конфликт базовых адресов), её ImageBase обязательно должна быть перемещаемой – достаточно добавить секцию .reloc в коде, об остальном компилятор позаботится сам. В этой секции будут собраны т.н. фиксапы (fixups) – адреса, к которым загрузчик должен будет внести поправки. Фиксапы применяются исключительно к инструкциям, которые обращаются по абсолютным адресам в памяти. Если адрес относительный (в пределах 127 байт), то он не требует модификации:
Такие отладчики как OllyDbg подчёркивают адреса, которые требуют коррекции после перемещения образа в памяти – на рис.выше их всего 4, и непосредственно опкод инструкции не учитывается (здесь push\call, хотя могут быть и условные\безусловные переходы). Размер самих фиксапов равен 12-бит (выделены красным), а это 2^12=4096 или одна страница виртуальной памяти. Соответственно фиксап не может адресовать блок памяти свыше 4 Кбайт. Другими словами, каждая страница (блок) имеет свой набор фиксов.
Точка входа в DLL-библиотеку
Теперь о насущном..
Подобно исполняемым экзе-приложениям, библиотеки тоже имеют свою точку входа – в доках MSDN эта функция известна как DllEntryPoint() (или DllMain в терминологии си). Здесь и кроется всё самое интересное, чему мы посвятим весь последующий разговор.
Любое обращение EXE-модуля к функциям из DLL происходит через системного посредника LdrLoadDLL() . В системном ансамбле, эта кошерная функция из Ntdll.dll играет огромную роль. Она не только загружает библиотеки в пространство юзера на этапе проецирования образа в память, но и обслуживает функции динамического вызова процедур типа LoadLibrary() , GetModuleHandle() и прочии, от которых мы ожидаем получить дескрипторы модулей. Вот её прототип:
Диалог этого загрузчика с вызываемой библиотекой происходит по схеме "запрос-ответ". Любая библиотека должна иметь упомянутую функцию взаимодействия с загрузчиком DllEntryPoint() , от которой в регистре EAX лоадер ждёт или TRUE (библиотека способна обработать запрос), или FALSE (что-то пошло не так). Не соблюдение этих правил приводит к краху приложения.
LdrLoadDLL() может извещать библиотеку о четырёх событиях, которые происходят во-внешнем (по отношению к библиотеке) мире. Эта информация передаётся точке-входа в DLL в параметре fdwReason . Кроме события, загрузчик сразу передаёт либе её базу в памяти, и способ подключения к исполняемому файлу. Прототип описывается так:
Из всей этой братии, нам интересен лишь аргумент DLL_PROCESS_ATTACH = 1 , благодаря которому статически присобачив библиотеку к нашему процессу, мы можем например, предварительно расшифровать основной код программы, обнаружить отладчик в фоне и т.д. Дело в том, что загрузчик проецирует DLL в пространство процесса задолго до точки-входа в программу, с которой начинают анализ все отладчики, а значит Оля пропустит этот этап между ног. Здесь уместно вспомнить про функции TLS-Callback , ..но поскольку загрузчик парсит импорт из библиотек вторым (а TLS аж десятым), то выигрыш тут на лицо.
DLL – промышленная реализация
Соответственно, мы можем снимать эти аргументы прямо со-стека, и сразу проверять их – код ниже придерживается именно такой политики:
Теперь у нас есть либа, и нужно написать родительское приложение, которое будет статически привязывать к себе эту библиотеку. Во-первых, обратим внимание на имя новоиспечённой DLL – здесь, в секции-экспорта я определил его как "about.dll", это важно! Теперь просто импортируем эту библиотеку по имени, и вызываем из неё функцию примерно так:
Тёмная сторона луны
Пробежавшись по макушкам кода библиотек, посмотрим на них из другой проекции..
Алгоритм работы загрузчика образов LDR плохо освещён в документации и это не удивительно – весь ядерный код мастдая, коммерческая тайна (будь она не ладна). Как это принято у Microsoft, она советует нам ознакомиться с третьей поправкой, восьмого исправления, четвёртой редакции от 32 февраля где сказано, что "..в военное время не только прямой угол может достигть 100 градусов, но и функция инициализации DllEntryPoint() может использоваться не по назначению". Самое главное: кто, где и когда объявляет это положение неизвестно, а значит мы вольны назначать его сами.
Мощь (и беспомощность) точки-входа в библиотеку в том, что некоторая часть театра действий происходит под управлением системных механизмов, отследить которые из прикладного уровня довольно сложно. В документации на РЕ-файл можно найти формат каталога секций "Data-Directories" . В этом дире рассчитавшись на первый-второй выстроены в ряд все секции, которые обходит загрузчик образов LdrLoadDll() при инициализации приложения. Причём последовательность секций строго регламентируется. Вот как выглядит эта структура в представлении редактора PE-Explorer:
Таким образом, импорт анализируется загрузчиком на самом начальном этапе, и большинство служебных структур прикладного уровня в этот момент даже не инициализированы ещё до конца – в частности, это относится к структуре PEB , не говоря уже о дочерней к ней структуре ТЕВ. Например, если мы внутри DllEntryPoint() захотим из РЕВ получить флаг-отладки нашего приложения "BeingDebugger" , то потерпим фиаско (проверено на практике). На скамейку запасных сразу отправляется и функция IsDebuggerPresent() , которая читает этот-же флаг из РЕВ. Значит нужно спускаться на уровень ниже, а для защитных механизмов это только гуд.
Если развивать мысль дальше, то наша библиотека не единственная у приложения. Кроме неё, в память каждого процесса система загружает и свои либы Ntdll.dll (собственно в ней и живёт загрузчик LDR), а так-же библиотеку kernel32.dll. С очерёдностью загрузки в память системных библиотек можно ознакомится в отладчике WinDbg, озадачив его командой !peb – поле InMemoryOrderModuleList как-раз отрапортует нам об этом:
Здесь видно, что первым десантируется в память мой исполняемый файл "DLL_attach.exe", следом за ним системные библиотеки, и только потом моя пользовательская либа "about.dll". Повторюсь, что система выстраивает структуру РЕВ только когда окончательно покончит с окружением процесса, скидывая в неё результаты проделанной работы. А лог на рисунке выше, WinDbg парсит уже из рабочего процесса, поэтому РЕВ как-бы готова к употреблению.
DllEntryPoint() на страже приложения
Теперь будем мыслить так.. Если точка-входа в библиотеку с аргументом DLL_PROCESS_ATTACH отрабатывает на низком уровне, значит на её основе можно соорудить защитный механизм. Система вызывает DllEntryPoint() с аргументом ATTACH сразу после того-как DLL спроецирована на адресное пространство процесса – такая ситуация возможна всего один раз, и на протяжении всего "сеанса" больше не повторяется! В следующий раз, когда тред вызовет LoadLibrary() для уже спроецированной на память DLL, система просто увеличит счётчик обращения к ней и всё.
В предоставленном на суд примере, статически (явно) прилинкованная библиотека внутри DllEntryPoint() будет искать отладчик, и если обнаружит таковой, то вернёт ошибку. Как упоминалось выше, флаги-отладки из структуры РЕВ для этих целей уже не подходят, поэтому придётся искать обходные пути.
Одним из вариантов обнаружения факта отладки является проверка своего статуса в системе. Дело в том, что в дефолте, запущенный на исполнение процесс не имеет привилегии SeDebugPrivilege, зато ею обладает отладчик. Когда он загружает нас в своё тело, то автоматом передаёт и свою привилегию, чекнув которую мы можем определить этот факт. Есть куча способов узнать привилегию своего процесса, и мы воспользуемся самым простым – попытаемся открыть системный процесс csrss.exe.
CSRSS.EXE – это часть пользовательской подсистемы Win32, и при обычных обстоятельствах он не доступен прикладным задачам. Однако привилегия Debug снимает этот запрет, и мы можем открыть его функцией OpenProcess() со-всеми вытекающими последствиями. CSRSS (client\server run-time subsystem) отвечает за консоль, работу с потоками Thread, и за 16-битную среду MS-DOS (на х64 её кастрировали). Это процесс пользовательского режима, который перехватывает обращения к ядру и решает простые вопросы на уровне прикладных задач.
Проблема в том, что функции OpenProcess() требуется идентификатор PID открываемого процесса, т.е. нам нужно будет просканировать всю память и найти нужный процесс по его имени – тривиальная задача по обнаружению отладчика превращается в ад. В сети можно встретить разные варианты перечисления процессов – это CreateToolhelp32Snapshot() , обход в цикле через Process32First\Next() , EnumProcess() и тяжеловес NtQuerySystemInformation() .
Однако получить PID именно процесса CSRSS.EXE можно специально предназначенной для этого функцией из Ntdll.dll под названием CsrGetProcessId() – у неё нет аргументов и в EAX она сразу возвращает столь необходимый нам PID. С использованием этой функции, проверка на отладчик укладывается в пару строк ассемблерного кода. Мы поместим её внутрь DllEntryPoint() и будем проверять запрос на DLL_PROCESS_ATTACH .
В общем случае, программа будет следовать такому алго..
Мы пишем приложение, которое запрашивает пароль. Если юзер введёт валидный пасс, то управление примет зашифрованная функция, которую расшифрует декриптор из внешней библиотеки, с непримечальным именем "about.dll". Алгоритм декриптора – самый примитивный ксор 1-байтныйм ключом, однако тут есть подвох! Пароль на валидность мы вообще не будем проверять, а декриптор сняв с него хэш-сумму сразу расшифрует ей критический блок в основном приложении. Теперь уже взломщик не сможет просто обратить условие проверки, и ему придётся осуществлять только брут, перебором всех возможных ключей.
Если юзер подсунет левый пароль и его хэш не совпадёт с тем, которым мы зашифровали блок, то рано или поздно процессор нарвётся на исключение , поскольку пойдёт пахать зашифрованный код. Чтобы защитить честь его мундира, для таких случаев мы устанавливаем SEH-обработчик, который и будет отлавливать эти исключения. То-есть, если SEH примет управление, значит пасс невалидный и мы подкорректировав значение регистра EIP в контексте, выводим мессагу Wrong и на выход..
Основная проблема тут – правильно зашифровать критический блок кода в основном приложении. Я уже приводил пример шифрования в hex-редакторе HIEW, поэтому повторяться не буду. Если возникнут вопросы, их всегда можно задать в комментах этой темы. Ключ – это сумма всех символов любой строки. Например, в данном случае я использовал пароль "codeby.net" и получил его 1-байтную хэш сумму = 0xEB . Для этого можно воспользоваться услугами редактора HxD и виндовым калькулятором:
После того-как получим хэш валидного пассворда, можно приступать к шифрованию всего блока этим ключом. Если на запрос юзер введёт валидный пасс, то функция декриптора в библиотеке рассчитает его хэш на автомате, и опять проксорит этим-же клюсом – в результате получим расшифрованный блок, и процессор не споткнётся уже об него. Ниже приведён готовый к употреблению код, который остаётся только скомпилировать, и в основном приложении зашифровать указанный блок:
В Windows библиотека динамической компоновки (DLL) является исполняемым файлом, который выступает в качестве общей библиотеки функций и ресурсов. Динамическая компоновка — это возможность операционной системы. Она позволяет исполняемому файлу вызывать функции или использовать ресурсы, хранящиеся в отдельном файле. Эти функции и ресурсы можно компилировать и развертывать отдельно от использующих их исполняемых файлов.
Библиотека DLL не является отдельным исполняемым файлом. Библиотеки DLL выполняются в контексте приложений, которые их вызывают. Операционная система загружает библиотеку DLL в область памяти приложения. Это делается либо при загрузке приложения (неявная компоновка), либо по запросу во время выполнения (явная компоновка). Библиотеки DLL также упрощают совместное использование функций и ресурсов различными исполняемыми файлами. Несколько приложений могут осуществлять одновременный доступ к содержимому одной копии библиотеки DLL в памяти.
Различия между динамической и статической компоновкой
При статической компоновке весь код объектов копируется из статической библиотеки в использующие их исполняемые файлы во время сборки. При динамической компоновке включаются только те сведения, которые позволяют Windows найти и загрузить библиотеку DLL, содержащую элемент данных или функцию, во время выполнения. При создании библиотеки DLL также создается библиотека импорта, содержащая эту информацию. При сборке исполняемого файла, который вызывает библиотеку DLL, компоновщик использует экспортированные символы в библиотеке импорта, чтобы сохранить эти сведения для загрузчика Windows. Когда загрузчик загружает библиотеку DLL, она сопоставляется с областью памяти приложения. Для выполнения операций инициализации, необходимых библиотеке DLL, вызывается специальная функция DllMain из библиотеки DLL (если она имеется).
Различия между приложениями и библиотеками DLL
Хотя и библиотеки DLL, и приложения являются исполняемыми модулями, они отличаются некоторыми особенностями. Наиболее очевидное различие заключается в том, что библиотеку DLL нельзя запустить. С точки зрения системы, между приложениями и библиотеками DLL имеется два существенных различия.
В системе может одновременно выполняться несколько экземпляров приложения. Экземпляр библиотеки DLL может быть только один.
Преимущества использования библиотек DLL
Динамическая компоновка кода и ресурсов имеет некоторые преимущества над статической.
Динамическая компоновка экономит память и сокращает подкачку. Многие процессы могут использовать библиотеку DLL совместно, одновременно обращаясь к одной доступной только для чтения копии ее частей в памяти. В отличие от этого, каждое приложение, созданное с помощью библиотеки статической компоновки, имеет полную копию кода библиотеки, которую система Windows должна загрузить в память.
Динамическая компоновка экономит место на диске и пропускную способность. Несколько приложений могут совместно использовать одну копию библиотеки DLL на диске. В отличие от этого, каждое приложение, созданное с помощью библиотеки статической компоновки, имеет код библиотеки, связанный с исполняемым образом. Это увеличивает занимаемое на диске место и используемую для передачи данных пропускную способность.
Обслуживание, применение исправлений для системы безопасности и обновление могут быть проще. Если приложения используют общие функции в библиотеке DLL, можно реализовать исправления ошибок и развернуть обновления для нее. При обновлении библиотек DLL использующие их приложения не нужно перекомпилировать или повторно компоновать. Они могут использовать новые библиотеки DLL сразу после их развертывания. В отличие от этого, при внесении исправлений в код статически скомпонованного объекта необходимо повторно скомпоновать и развернуть каждое использующее его приложение.
С помощью библиотек DLL можно оказывать послепродажную поддержку. Например, библиотеку DLL драйвера дисплея можно изменить так, чтобы она поддерживала дисплей, который не был доступен на момент предоставления приложения.
С помощью явной компоновки можно обнаруживать и загружать библиотеки DLL во время выполнения. Например, это могут быть расширения приложения, которые добавляют новые функциональные возможности без повторной сборки и развертывания приложения.
Динамическая компоновка упрощает поддержку приложений, написанных на разных языках программирования. Программы, написанные на разных языках программирования, могут вызывать одну и ту же функцию в библиотеке DLL при условии соблюдения соглашения о ее вызове. Программы и функция в библиотеке DLL должны отвечать следующим требованиям к совместимости: ожидаемый функцией порядок передачи аргументов в стек; выполнение очистки стека функцией или приложением; передача аргументов в регистрах.
Динамическая компоновка обеспечивает механизм для расширения классов библиотеки Microsoft Foundation Classes (MFC). На основе существующих классов MFC можно создавать производные классы и помещать их в библиотеку расширения DLL, используемую приложениями MFC.
Динамическая компоновка упрощает создание международных версий приложения. Библиотеки DLL — это удобный способ предоставления ресурсов для конкретных языковых стандартов, благодаря чему значительно упрощается создание международных версий приложения. Вместо предоставления множества локализованных версий приложения можно поместить строки и изображения для каждого языка в отдельную библиотеку DLL ресурсов. Затем приложение может загружать ресурсы для нужного языкового стандарта во время выполнения.
Возможным недостатком использования библиотек DLL является то, что приложения не являются автономными. Они требуют наличия отдельного модуля DLL, которое должно проверяться в процессе установки.
Дополнительные сведения о создании и использовании библиотек DLL
В приведенных ниже статьях приводятся подробные сведения о создании библиотек DLL на C и C++ в Visual Studio.
Пошаговое руководство. Создание и использование библиотеки DLL (C++)
Описывает создание и использование библиотек DLL при помощи Visual Studio.
Типы библиотек DLL
Предоставляет сведения о различных типах библиотек DLL, которые доступны для сборки.
Вопросы и ответы по библиотекам DLL
Ответы на часто задаваемые вопросы о библиотеках DLL.
Связывание исполняемого файла с библиотекой DLL
Описание явного и неявного соединения с библиотекой DLL.
Инициализация библиотеки DLL
Описывается код инициализации библиотеки DLL, который должен выполняться при загрузке библиотеки DLL.
Библиотеки DLL и поведение библиотеки времени выполнения Visual C++
Описывается последовательность запуска библиотеки DLL средой выполнения.
Функции LoadLibrary и AfxLoadLibrary
Описывается использование функций LoadLibrary и AfxLoadLibrary для явной связи с библиотекой DLL во время выполнения.
Функция GetProcAddress
Описывается использование GetProcAddress для получения адреса экспортированной функции в DLL.
Функции FreeLibrary и AfxFreeLibrary
Описывается использование функций FreeLibrary и AfxFreeLibrary , когда модуль DLL больше не нужен.
Порядок поиска библиотеки динамической компоновки (DLL)
Описание пути поиска, который операционная система Windows использует для поиска библиотеки DLL в системе.
Состояния модулей обычной библиотеки DLL MFC, динамически связанной с MFC
Описываются состояния модулей обычной библиотеки DLL, динамически связываемой с MFC.
Библиотеки DLL расширений MFC
Описываются библиотеки DLL, которые обычно реализуют классы многократного использования, производные от существующих классов MFC.
Создание библиотек DLL, содержащих только ресурсы
Библиотека DLL, содержащая только ресурсы, например значки, растровые изображения, строки и диалоговые окна.
Локализованные ресурсы в приложениях MFC: вспомогательные библиотеки DLL
Расширенная поддержка библиотек спутниковой связи DLL и содержит возможность, которая позволяет создавать приложения, локализированные на различные языки.
Импорт и экспорт
Импортирование открытых символов в приложение или экспортирование функций из библиотеки DLL
Технология Active и библиотеки DLL
Размещение серверов объектов внутри библиотеки DLL.
Автоматизация в библиотеке DLL
Параметр автоматизации в решениях мастера библиотек DLL MFC.
Соглашения об именовании библиотек DLL MFC
Способ встраивания библиотек DLL в MFC, опираясь на четко структурированное соглашение об именовании.
Связанные разделы
Использование MFC как части библиотеки DLL
Описываются постоянные библиотеки DLL MFC, которые позволяют использовать библиотеку MFC как часть библиотеки динамической компоновки Windows.
Версия библиотеки DLL MFC
Описывается механизм использования общих библиотек динамической компоновки MFCxx.dll и MFCxxD.dll (где x является номером версии MFC) с приложениями MFC и расширениями библиотек DLL MFC.
Библиотека — это скомпилированный код, который обеспечивает функции исполняемого приложения и предоставляет ему данные. Библиотеки могут быть связаны статически и динамически, обычно у них расширение LIB и DLL соответственно. Статические библиотеки (например, библиотека времени выполнения C) связываются с приложением во время компиляции и становятся частью полученного исполняемого файла. Приложение загружает библиотеку DLL, когда она необходима (обычно при запуске). Одна библиотека DLL может загружать другую библиотеку DLL и динамически ссылаться на нее.
Преимущества библиотек DLL
Ниже приведены основные преимущества библиотек DLL.
С помощью DLL можно добавлять функции и команды в Microsoft Excel.
Ресурсы для создания библиотек DLL
Вот что нужно, чтобы создать библиотеку DLL:
- Редактор исходного кода.
- Компилятор для преобразования исходного кода в объектный, совместимый с оборудованием.
- Компоновщик для добавления кода из статических библиотек и создания исполняемого DLL-файла.
В современных интегрированных средах разработки (IDE), например Microsoft Visual Studio, есть все эти компоненты, а также смарт-редакторы, инструменты для отладки кода и управления несколькими проектами, мастера создания проектов и другие важные инструменты.
Вы можете создавать библиотеки DLL на нескольких языках, например C/C++, Pascal и Visual Basic. Так как исходный код API Excel — C и C++, в этой документации рассматриваются только эти два языка.
Экспорт функций и команд
При компиляции проекта DLL компилятор и компоновщик должны знать, какие функции экспортировать, чтобы предоставить к ним доступ в приложении. В этом разделе описаны возможные способы.
При компиляции исходного кода компиляторы обычно изменяют имена функций. Для этого они обычно добавляют символы в начало или конец имени. Этот процесс называется декорирование имени. Необходимо убедиться, что приложение, загружающее библиотеку DLL, может распознать имя экспортируемой функции. Для этого может потребоваться дать указание компоновщику сопоставить расширенное имя с более простым именем экспорта. Это может быть имя из исходного кода или другое.
Способ декорирования имени зависит от языка и способа вызова функции, задаваемого компилятором, т. е. соглашения о вызовах. Стандартное межпроцессное соглашение о вызовах для Windows, используемое в библиотеках DLL, — это WinAPI. В файлах заголовков Windows оно обозначено как WINAPI с помощью декларатора Win32 __stdcall.
Функция, экспортируемая в DLL-файл для Excel (функция листа, функция, эквивалентная листу макросов, или пользовательская команда) всегда должна использовать соглашение о вызовах WINAPI / __stdcall. В определение функции необходимо явно включить указатель WINAPI, так как по умолчанию в компиляторах Win32 используется соглашение о вызовах __cdecl, также обозначенное как WINAPIV.
Вы можете сообщить компоновщику о необходимости экспорта функции, а также ее внешнее имя несколькими способами:
В проекте можно использовать все три способа, они поддерживаются и компилятором, и компоновщиком, но не следует экспортировать одну функцию более чем одним способом. Например, предположим, что библиотека DLL содержит два модуля исходного кода, C и C++, которые содержат две функции для экспорта — my_C_export и my_Cpp_export соответственно. Для простоты предположим, что функции принимают один числовой аргумент двойной точности и возвращают данные того же типа. В следующих разделах описываются варианты экспорта функций с помощью каждого из этих методов.
С помощью DEF-файла
DEF-файл должен содержать следующие строки.
EXPORTS my_C_export = _my_C_export@8 my_Cpp_export
Ниже приведен общий синтаксис строки, следующей за оператором EXPORTS.
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
Обратите внимание, что функция C декорирована, но в DEF-файле компоновщику явно дано указание предоставлять эту функцию, используя имя из исходного кода (в данном примере). Компоновщик неявно экспортирует функцию C++, используя имя исходного кода, так что в DEF-файл необязательно включать расширенное имя.
Соглашение для вызовов функций API 32-разрядной Windows предусматривает следующее декорирование функций C: function_name становится function_name@ n, где n — количество байт, выраженное в виде десятичного числа, принимаемого всеми аргументами, при этом количество байт для каждого округляется до ближайшего числа, кратного четырем.
Размер всех указателей в Win32 — четыре байта. Тип возвращаемого значения не влияет на декорирование имени.
Чтобы компилятор C++ предоставлял функции, используя нерасширенные имена, заключите функцию и ее прототипы во внешний блок "C" , как показано в приведенном ниже примере. (Фигурные скобки <> здесь опущены, так как объявление относится только к блоку кода функции, расположенному непосредственно за ним.)
При размещении прототипов функции C в файлах заголовков, которые могут быть включены в исходные файлы C или C++, необходимо включить следующую директиву препроцессора.
С помощью декларатора __declspec(dllexport)
Ключевое слово __declspec(dllexport) можно использовать в объявлении функции указанным ниже образом.
Ключевое слово __declspec(dllexport) необходимо добавить в крайней левой части объявления. Преимущества этого подхода: функцию не нужно указывать в DEF-файле, а состояние экспорта указывается вместе с определением.
Если вы не хотите декорировать имя функции C++, необходимо объявить функцию следующим образом.
Компоновщик сделает функцию доступной под именем my_undecorated_Cpp_export, то есть именем из исходного кода, без декорирования.
Например, при использовании Microsoft Visual Studio эти строки можно включить в общий файл заголовка, как показано ниже.
Если такой заголовок включен в исходные файлы, две функции, приведенные в качестве примера, можно экспортировать следующим образом.
Обратите внимание, что директиву необходимо поместить в код функции. Ее можно развернуть, только если параметры /EP и /P компилятора не заданы. При использовании этого способа исчезает необходимость в DEF-файле и объявлении __declspec(dllexport), а спецификация состояния экспорта указывается в коде функции.
Очень часто в своей работе, Вы будете сталкиваться с такой ситуацией.
Перед вами стоит задача, нужно написать программу " Супер Блокнот" которая должна сохранить все функции стандартного блокнота, но при этом иметь ряд каких-то дополнительных функций, благодаря которым, при выборе программы для работы с текстом, пользователь будет отдавать предпочтение именно вашей программе. Для этого было решено добавить несколько новых функций, одна из них, будет отвечать за подсчет и вывод количества слов в тексте.
Через пару недель программа была написана, затем она попала в Интернет, пользователи оценили новый продукт и стали им пользоваться. Цель достигнута.
Проходит время и перед вами ставят новую задачу, написать программу "Супер парсер". Одной из функции данной программы, будет подсчет слов в тексте. Вы понимаете, что снова придется разрабатывать метод, который будет вести подсчёт слов. Но, при этом вспоминаете, что совсем не давно уже разрабатывали программу, в которой применялась данная функция. Чтобы не изобретать велосипед, Вы открываете исходник программы "Супер блокнот"; и копируете весь метод в исходник новой программы "Супер парсер". Отлично, теперь Вам не придется тратить время на написание этого метода заново, и Вы можете посветить больше времени остальным элементам программы. Задача решена.
Но, что если метод по подсчету слов, писали не Вы, а допустим, какой-нибудь коллега по работе и по каким-то причинам, Вы не можете получить доступ к исходному коду программы "Супер блокнот". То есть первый вариант, копирование метода из исходника, не прокатит и данный метод придется писать самому ммм, печалька.
Но, тут вам звонит ваш коллега по работе и говорит: Ты знаешь, я тут вспомнил, когда я разрабатывал данный метод, я подумал, что возможно мне придется его использовать ещё где-то, и по этому я решил вынести его в отдельную сборку, в виде файла динамической библиотеки (dll).Ты просто скопируй этот файл dll в свой проект, и подключи его, как внешнюю сборку, после чего ты получишь доступ к моему методу и сможешь им пользоваться.
Отлично! Вы проделываете все описанные действия, в программе “Супер парсер” появляется нужный метод, задача решена и вам вновь не пришлось повторно писать код руками.
На этом присказка закончена и теперь переходим к более подробному изучению.
Что такое DLL
DLL (dynamic-link library) - это динамически подключаемая библиотека, или сокращено динамическая библиотека.
Как уже писал ранее, динамические библиотеки, позволяют повторно использовать ранее написанный код, а так же они обеспечивают лучшую переносимость кода. Достаточно, скинуть файл на флешку, или скачать dll файл из Интернета, после чего добавить его в текущий проект и тут же получить набор разных дополнительных функций для вашего приложения. Так же стоит знать, что в одном dll файле может храниться любое количество типов, членов и пространств имён.
Создание файла dll
Для начала выберем тип нового создаваемого приложения, а точнее проекта.
Выбираем Class Library, то есть создаем файл динамической библиотеки (dll)
Так же Вы можете указать, под какую версию Фреймворка будет создаваться данный проект.
После того, как Visual Studio создаст каркас проекта, Вы увидите следующее:
Так будет выглядеть окно Solution Explorer
А так будет выглядеть рабочая область, где Вы обычно пишите код программы
И так дано пространство имён: Car и класс: Class1. Class1 не удачное название, давайте немного изменим наш код, заменив Class1 на BMW, и добавим метод, который будет выводить имя нашего класса.
И так код написан, и теперь необходимо выполнить компиляцию, чтобы получить сборку.
Если сейчас попытаться нажать F5 или Ctrl+F5, то вы увидите данное окно
Данная ошибка, говорит лишь о том, что был создан файл динамической библиотеки (dll), а не исполняемый файл (exe), который не может быть запущен.
Для того, чтобы скомпилировать проект, нажмите клавишу F6, после чего в директории bin\Debug появиться файл Car.dll.
Чтобы проверить был ли создан файл библиотеки, воспользуйтесь кнопкой Show All Files на вкладке Solution Explorer
Сборка, в виде файла динамической библиотеки успешно создана.
Теперь перейдем в папку bin\Debug, для того, чтобы быстро переместиться в текущую директорию проекта, в том же Solution Explorer воспользуйтесь пунктом Open Folder in Windows Explorer
Скопируйте полученный файл сборки (в нашем случае - это файл Car.dll) в какую-нибудь временную папку. На самом деле, это делать необязательно, Вы можете оставить данный файл в этой папке, но для удобства, создадим новую папку, и поместим туда созданный файл библиотеки.
Создаем новый проект.
Новый проект создан. Теперь подключим в текущий проект, нашу библиотеку (Car.dll)
Подключение dll
Для этого на папке References, в окне Solution Explorer нужно нажать правую кнопку мыши и выбрать пункт Add Reference, откроется вот такое окно:
- Выберите вкладку Browse
- Укажите папку, в которой лежит файл созданной библиотеки (в нашем примере - Car.dll)
- Выделите файл нужной библиотеки и нажмите кнопку ОК;
На ней видно, что в наш текущий проект была успешна, добавлена ссылка на нашу сборку Car.dll, в которой храниться наш код на языке IL. Надеюсь, Вы помните, что после компиляции весь написанный вами код преобразуется в промежуточный код на языке IL (CIL, MSIL - это одно и тоже). А так же видно, что в папку bin\Debug была помещёна копия нашей библиотеки.
Если вдруг Вы забыли, какое пространство имен, типы, или члены содержит ваша созданная библиотека. Вы всегда можете воспользоваться таким инструментом Visual Studio, как Object Browser. Для этого перейдите на вкладку Solution Explorer, откройте папку References, и просто щёлкните правой кнопкой мыши по добавленной ранее библиотеке, в нашем случае напоминаю - это файл (Car.dll) и выберите пункт View in Object Browser, появиться вот такое окно.
В окне Object Browser можно посмотреть содержимое нашей сборки.
Сборка подключена и теперь Вы можете работать с её содержимым. Далее выполним необязательный пункт.
Добавим, с помощью ключевого слова using пространство имен Car из созданной нами библиотеки Car.dll, после чего создадим объект класса BMW и выполним метод Вывести_Имя_Класса().
- Создаем файл динамической библиотеки (dll)
- Подключаем созданную библиотеку в наш проект, путем добавления в папку References ссылки на наш файл dll.
- (Необязательный пункт) Подключаем пространство имен из подключенной сборки, с помощью ключевого слова using, либо используем полное наименование, то есть Пространство имён.Тип (Car.BMW).
- Profit
И в конце не много информации о типах сборок:
Сборки бывают следующих основных видов: общие и частные.
Частная сборка (private assembly)
Это файлы библиотек, как наш ранее созданный файл Car.dll, которые содержаться на протяжении всего времени в каталоге текущего приложения или любом из его подкаталогов.
Вернёмся к началу статьи.
После того, как было создано приложение “Супер парсер”, мы получили сборку в виде файла (exe). Затем мы решили протестировать нашу программу и отдаём её нашему другу, при этом Вы так же упоминаете, что если он хочет иметь дополнительные функции в программе, то ему нужно просто рядом с его exe файлом поместить файл библиотеки Car.dll. После чего он получит возможность подсчёта слов в тексте. То есть библиотека будет храниться в той же директории, что и исполняемый файл.
Общие сборки (shared assembly)
Это сборки, предназначенные для множественного использования разными приложениями, установленными на одном компьютере.
Если вам уже приходилось заменять значки папок, то вы, стало быть, в курсе, что при этом система предлагает поискать альтернативный значок в файле SHELL32.dll, расположенный в папке system32. Спрашивается, почему Windows хранит иконки именно в DLL-файлах, а не в отдельных папках и что вообще представляют собой файлы DLL?
Файлы DLL или Dynamic Link Library они же динамически подключаемые библиотеки представляют собой контейнеры, нечто вроде архивов, в которых могут храниться различные используемые исполняемыми файлами EXE компоненты, например, фрагменты кода или графические элементы, в нашем случае иконки. Использование DLL в Windows основано на принципе модульности, причем каждая отдельная совместимая библиотека может быть подключена к тому или иному приложению, тем самым расширяя его функциональные возможности.
При этом будущая динамическая библиотека отобразится в левой колонке рабочего окна утилиты. После этого нажмите на панели инструментов кнопку Import и в открывшемся окошке укажите путь к файлу иконки, нажав кнопку «Browse». Больше ничего в настройках менять не нужно.
Нажмите «OK» и в левой колонке утилиты у вас появятся две папки Icon Image и Icon Directory, в них и будут храниться ваши иконки.
Если хотите, можете попробовать поиграть с настройками встроенного редактора иконок, изменив метод интерполяции или добавив задний фон.
Точно таким же образом одна за другой в библиотеку добавляются прочие иконки. Чтобы сохранить результаты работы, жмем кнопку Save – библиотека будет сохранена в каталог, из которого вы брали иконки.
Готово, теперь можете использовать свою библиотеку по назначению, подставляя к ней путь в окошке смены значка.
Читайте также: