Как сделать динамическую загрузку
Динамическая подгрузка Javascript
Добрый день всему хабрасообществу! Недавно мне выпало разрабатывать огромный веб-проект (точнее доделать), на котором была просто уйма Ajax. Проблема заключалась в том, что все яваскриптовые файлы грузились сразу. Тобиш, если б мне захотелось добавить ещё функционал — то это новый js-файл, который следует грузить пользователю (хотя возможно ему этот функционал может даже не потребоватся). Недавно прочтенная статья «Динамическая подгрузка модулей на Javascript» дала мне понять, что js можно подгружать динамически, поэтому я стал искать метод решения.
И вот какое решения нашел. Поскольку переписывать проект у меня не было не времени не желания, мне оставалось только найти быстрый способ как осуществить быструю подгрузку js-файлов по требованию. В Интернете я случайно наткнулся на такую библиотеку как JSAN (JavaScript Archive Network, не путайте с JSON). Разработчики данной библиотеки пытаются сделать аналог CPAN только для JavaScript.
Вот ссылка на источник.
Там все красиво описывается, но я объясню самое основное. JSAN предоставляет средства для динамической подгрузки кода, используя XMLHttpRequest к серверу. Библиотека содержится в одном js-файле, который и требуется подключить к странице. Дальше — проще. К примеру:
JSAN.require(‘js.creating’);
var creating = new js.creating();
Cледующий ниже вызов JSAN.require постарается загрузить js/creating.js, пробуя искать во всех источниках, указанных в JSAN include_path, который по умолчанию равен [‘.’, ‘lib’]. И все!
Единственным требованием является, чтобы все модули находились внутри своего namespace, так для примера, приведенного выше, начало s/creating.js будет выглядеть примерно так:
if(js == undefined) var js = <>;
if(js.creating == undefined) js.creating = <>;
Но я бы отнес это скорее к плюсу нежели к минусу, т.к это заставляет разработчика изолировать свои библиотеки от внешнего мира, тем самым сводя проблему конфликта имен практически к нулю. Кстати, помимо JSAN.require, существует также метод JSAN.use, который позволяет экспортировать только необходимый функционал из модуля, что как раз мне потребовалось. Первое что пришлось сделать, так это преобразовать вид старых функций с
поскольку только так функции подгружались. Дальше я просто добавил в шаблонах простую замену. Например есть функция itemsCreating в файле js/creating.js. По клику на обьект на странице она должна выполнятсяю Для этого вместо
ЗЫ Моя первая статья на Хабре, поэтому критика не мало важна.
Как с помощью чистого CSS создать красивую анимацию загрузки для приложения
Если вы в последнее время заходили в интернет, то, скорее всего, видели красивую анимацию загрузки, которая заполняет страницу, прежде чем элегантно подгрузится ее содержимое.
Некоторые социальные гиганты, такие как Facebook, даже используют этот подход для улучшения загрузки страниц. Как мы можем добиться такого же результата с помощью одного только простого CSS?
Что мы собираемся сделать?
Мы создадим анимацию загрузки с помощью CSS-класса, который вы можете применить практически к любому элементу (в пределах разумного).
Лишь с одним CSS можно добиться высокой гибкости, изящности приложения и в то же время простоты.
Хотя сниппет довольно мал и вы можете просто скопировать и вставить его, я расскажу о том, что в нем происходит, и приведу пример его динамического использования при загрузке данных.
Просто хотите сниппет?
Нужно ли мне знать, как анимировать, для прочтения этого руководства?
Нет! Мы подробно рассмотрим, что именно вам нужно сделать. На самом деле анимация здесь относительно проста, так что давайте приступим к делу!
Часть 1: Создаем анимацию загрузки
В этой первой части сосредоточимся на том, чтобы собрать анимацию загрузки в одно целое и увидеть ее на статическом HTML-сайте. Цель состоит в том, чтобы пошагово создать настоящий сниппет. Мы будем использовать здесь только HTML и CSS.
Шаг 1: Создаем некоторый образец контента
Для начала нам понадобится небольшой образец контента страницы. Здесь действительно нет никаких ограничений, вы можете создать его с помощью базового HTML и CSS или добавить в свое приложение Create React!
Для этого руководства я собираюсь использовать HTML и CSS с несколькими примерами контента, которые позволят нам увидеть наш сниппет в действии.
Для начала создайте новый HTML-файл. Заполните его чем-нибудь, что даст возможность поиграться с анимацией. Я воспользуюсь сайтом fillerama со строчками из моего любимого сериала “Футурама”!
Если вы собираетесь следовать моему примеру, то вот как выглядит мой проект:
Отслеживайте через коммит!
Шаг 2. Начинаем с основы класса загрузки
В качестве основы давайте создадим новый CSS-класс. В наш CSS-файл добавим:
Теперь, когда у нас есть такой класс, давайте добавим его к нескольким или ко всем элементам на нашей странице. Я добавил его к нескольким абзацам, заголовкам и спискам.
Это дает нам основной фон, но мы, вероятно, хотели бы скрыть имеющийся текст. Во время загрузки у нас этого текста еще не будет, поэтому, скорее всего, мы захотим использовать заполняющий текст или фиксированную высоту. В любом случае, мы можем изменить цвет на прозрачный:
И вот теперь всё начинает складываться воедино, но пока что выглядит скорее, как поле для заполнения. Так что давайте анимируем то, что у нас есть, для создания эффекта загрузки.
Шаг 3. Применяем к классу загрузки стили и анимируем
Это означает, что нам нужен линейный градиент с наклоном в 100 градусов, где мы начинаем с #eceff1 и переходим к #f6f7f8 на 30% и обратно к #eceff1 на 70%;
Его трудно заметить поначалу: без движения он может выглядеть, как обычный блик на экране компьютера. Если хотите увидеть его, то прежде чем идти дальше, не стесняйтесь поиграть с цветами, обозначенными выше.
Теперь, когда есть основа для анимации, нам сначала нужно создать правило ключевых кадров:
Это правило меняет положение фона от начальных 100% до 0% по оси X.
Как вы, возможно, заметили, после сохранения этих параметров ничего по-прежнему не происходит. Причина в том, что мы устанавливаем градиент от одного конца элемента DOM до другого, так что нам некуда двигаться!
Теперь, поскольку наш фон расширяется за пределы выбранного элемента DOM (эта расширенная часть вам не видна), у него есть некоторое пространство для анимации, и вот она!
Отслеживайте через коммит!
Часть 2: Используем анимацию загрузки в динамическом приложении
Теперь, когда в нашем распоряжении есть анимация загрузки, давайте задействуем ее в базовом примере, где имитируется состояние загрузки.
Хитрость с фактическим использованием здесь в том, что, как правило, у нас нет в доступе контента, поэтому в большинстве случаев приходится его подделывать.
Чтобы показать, как это можно сделать, создадим простое приложение React с Next.js.
Шаг 1. Создаем пример приложения React с Next.js
Перейдите в каталог, где вы хотите создать свой новый проект, и введите в командной строке следующее:
Вам будет предложено выбрать некоторые параметры, в частности имя, определяющее каталог, в котором создается проект, и тип проекта. Я использую my-css-loading-animation-dynamic и “Default Starter App”.
После установки перейдите в ваш новый каталог и запустите сервер разработки:
Чтобы отобразить этот контент, давайте произведем следующую замену внутри :
Содержание
История
Общие библиотеки были добавлены в Unix в 1980-х годах, но изначально не позволяли программе загружать дополнительные библиотеки после запуска.
Использует
В C / C ++
Резюме
имя | Стандартный POSIX / UNIX API | Microsoft Windows API |
---|---|---|
Включение файла заголовка | #include | #include |
Определения заголовка | dl |
LoadLibraryEx
Загрузка библиотеки
Большинство UNIX-подобных операционных систем (Solaris, Linux, * BSD и т. Д.)
macOS
В качестве библиотеки UNIX :
Или, если фреймворк или пакет содержит код Objective-C:
Windows
Извлечение содержимого библиотеки
UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и др.)
В macOS при использовании пакетов Objective-C также можно:
Windows
Преобразование указателя библиотечной функции
Результат dlsym() или GetProcAddress() должен быть преобразован в указатель соответствующего типа, прежде чем его можно будет использовать.
Windows
В случае Windows преобразование несложно, поскольку FARPROC по сути уже является указателем на функцию:
Это может быть проблематично, если нужно получить адрес объекта, а не функции. Однако обычно все равно хочется извлечь функции, так что обычно это не проблема.
UNIX (POSIX)
Согласно спецификации POSIX, результатом dlsym() является void указатель. Однако не требуется, чтобы указатель функции имел даже тот же размер, что и указатель объекта данных, и поэтому допустимое преобразование между типом void* и указателем на функцию может быть нелегко реализовать на всех платформах.
В большинстве систем, используемых сегодня, указатели на функции и объекты де-факто конвертируемы. Следующий фрагмент кода демонстрирует один обходной путь, который позволяет в любом случае выполнить преобразование во многих системах:
что отключает предупреждение, даже если действует строгий псевдоним. При этом используется тот факт, что чтение из члена объединения, отличного от члена объединения, в который была записана последняя запись (так называемая » каламбур типов «), является обычным явлением и явно разрешено, даже если действует строгий псевдоним, при условии, что доступ к памяти осуществляется через тип объединения. прямо. Однако в данном случае это не совсем так, поскольку указатель функции копируется для использования вне объединения. Обратите внимание, что этот трюк может не работать на платформах, где размер указателей данных и размер указателей функций не совпадают.
Решение проблемы указателя на функцию в системах POSIX
Факт остается фактом: любое преобразование между указателями на функции и объекты данных следует рассматривать как расширение реализации (по своей сути непереносимое), и что не существует «правильного» способа прямого преобразования, поскольку в этом отношении стандарты POSIX и ISO противоречат друг с другом.
Из-за этой проблемы в документации POSIX по dlsym() устаревшей проблеме 6 говорится, что «будущая версия может либо добавить новую функцию для возврата указателей функций, либо текущий интерфейс может быть устаревшим в пользу двух новых функций: одна, которая возвращает указатели данных. а другой возвращает указатели на функции «.
В следующей версии стандарта (выпуск 7, 2008 г.) проблема обсуждалась, и был сделан вывод о том, что указатели функций должны быть преобразованы в void* соответствие с POSIX. Это требует от производителей компилятора реализации рабочего приведения для этого случая.
Если содержимое библиотеки может быть изменено (например, в случае пользовательской библиотеки), в дополнение к самой функции может быть экспортирован указатель на нее. Поскольку указатель на указатель функции сам по себе является указателем на объект, этот указатель всегда можно легально получить путем вызова dlsym() и последующего преобразования. Однако этот подход требует поддержки отдельных указателей на все функции, которые должны использоваться извне, и преимущества обычно невелики.
Выгрузка библиотеки
UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и др.)
Windows
Специальная библиотека
Реализации динамической загрузки в UNIX-подобных операционных системах и Windows позволяют программистам извлекать символы из текущего выполняемого процесса.
UNIX-подобные операционные системы позволяют программистам получить доступ к глобальной таблице символов, которая включает как основной исполняемый файл, так и загружаемые впоследствии динамические библиотеки.
Windows позволяет программистам получать доступ к символам, экспортируемым основным исполняемым файлом. Windows не использует глобальную таблицу символов и не имеет API для поиска в нескольких модулях, чтобы найти символ по имени.
UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и др.)
Windows
В Java
Механизм отражения также предоставляет средства для загрузки класса, если он еще не загружен. Он использует загрузчик классов текущего класса:
Однако нет простого способа выгрузить класс управляемым способом. Загруженные классы могут быть выгружены только контролируемым образом, т. Е. Когда программист хочет, чтобы это произошло, если загрузчик классов, используемый для загрузки класса, не является загрузчиком системного класса, а сам выгружается. При этом необходимо учитывать различные детали, чтобы гарантировать, что класс действительно выгружен. Это делает выгрузку классов утомительной.
Неявная выгрузка классов, т.е. неконтролируемая сборщиком мусора, несколько раз менялась в Java. До Java 1.2. сборщик мусора мог выгружать класс всякий раз, когда он чувствовал, что ему нужно пространство, независимо от того, какой загрузчик классов использовался для загрузки класса. Начиная с Java 1.2, классы, загруженные через системный загрузчик классов, никогда не выгружались, а классы загружались через другие загрузчики классов только тогда, когда этот другой загрузчик классов был выгружен. Начиная с Java 6 классы могут содержать внутренний маркер, указывающий сборщику мусора, что они могут быть выгружены, если сборщик мусора пожелает это сделать, независимо от загрузчика классов, используемого для загрузки класса. Сборщик мусора может проигнорировать эту подсказку.
Точно так же библиотеки, реализующие собственные методы, динамически загружаются с использованием System.loadLibrary метода. Нет никакого System.unloadLibrary метода.
Платформы без динамической загрузки
Работа с библиотеками динамической компоновки (DLL)
Рассмотрим ряд аспектов создания и использования библиотек DLL:
Использование DLL
Практически невозможно создать приложение Windows, в котором не использовались бы библиотеки DLL. В DLL содержатся все функции Win32 API и несчетное количество других функций операционных систем Win32.
Однако, если библиотека используется только одним приложением, лучше сделать ее обычной, статической. Конечно, если входящие в ее состав функции будут использоваться только в одной программе, можно просто вставить в нее соответствующий файл с исходным текстом.
Чаще всего проект подключается к DLL статически, или неявно, на этапе компоновки. Загрузкой DLL при выполнении программы управляет операционная система. Однако, DLL можно загрузить и явно, или динамически, в ходе работы приложения.
Библиотеки импортирования
Согласование интерфейсов
При использовании собственных библиотек или библиотек независимых разработчиков придется обратить внимание на согласование вызова функции с ее прототипом.
По умолчанию в Visual C++ интерфейсы функций согласуются по правилам C++. Это значит, что параметры заносятся в стек справа налево, вызывающая программа отвечает за их удаление из стека при выходе из функции и расширении ее имени. Расширение имен (name mangling) позволяет редактору связей различать перегруженные функции, т.е. функции с одинаковыми именами, но разными списками аргументов. Однако в старой библиотеке С функции с расширенными именами отсутствуют.
Хотя все остальные правила вызова функции в С идентичны правилам вызова функции в C++, в библиотеках С имена функций не расширяются. К ним только добавляется впереди символ подчеркивания (_).
Если необходимо подключить библиотеку на С к приложению на C++, все функции из этой библиотеки придется объявить как внешние в формате С:
Объявления функций библиотеки обычно помещаются в файле заголовка этой библиотеки, хотя заголовки большинства библиотек С не рассчитаны на применение в проектах на C++. В этом случае необходимо создать копию файла заголовка и включить в нее модификатор extern «C» к объявлению всех используемых функций библиотеки. Модификатор extern «C» можно применить и к целому блоку, к которому с помощью директивы #tinclude подключен файл старого заголовка С. Таким образом, вместо модификации каждой функции в отдельности можно обойтись всего тремя строками:
В программах для старых версий Windows использовались также соглашения о вызове функций языка PASCAL для функций Windows API. В новых программах следует использовать модификатор winapi, преобразуемый в _stdcall. Хотя это и не стандартный интерфейс функций С или C++, но именно он используется для обращений к функциям Windows API. Однако обычно все это уже учтено в стандартных заголовках Windows.
Загрузка неявно подключаемой DLL
При запуске приложение пытается найти все файлы DLL, неявно подключенные к приложению, и поместить их в область оперативной памяти, занимаемую данным процессом. Поиск файлов DLL операционной системой осуществляется в следующей последовательности.
Если библиотека DLL не обнаружена, приложение выводит диалоговое окно с сообщением о ее отсутствии и путях, по которым осуществлялся поиск. Затем процесс отключается.
Если нужная библиотека найдена, она помещается в оперативную память процесса, где и остается до его окончания. Теперь приложение может обращаться к функциям, содержащимся в DLL.
Динамическая загрузка и выгрузка DLL
Вместо того, чтобы Windows выполняла динамическое связывание с DLL при первой загрузке приложения в оперативную память, можно связать программу с модулем библиотеки во время выполнения программы (при таком способе в процессе создания приложения не нужно использовать библиотеку импорта). В частности, можно определить, какая из библиотек DLL доступна пользователю, или разрешить пользователю выбрать, какая из библиотек будет загружаться. Таким образом можно использовать разные DLL, в которых реализованы одни и те же функции, выполняющие различные действия. Например, приложение, предназначенное для независимой передачи данных, сможет в ходе выполнения принять решение, загружать ли DLL для протокола TCP/IP или для другого протокола.
Загрузка обычной DLL
Когда Windows обнаружит файл, его полный путь будет сравнен с путем библиотек DLL, уже загруженных данным процессом. Если обнаружится тождество, вместо загрузки копии приложения возвращается дескриптор уже подключенной библиотеки.
Если файл обнаружен и библиотека успешно загрузилась, функция ::LoadLibrary возвращает ее дескриптор, который используется для доступа к функциям библиотеки.
Перед тем, как использовать функции библиотеки, необходимо получить их адрес. Для этого сначала следует воспользоваться директивой typedef для определения типа указателя на функцию и определить переменную этого нового типа, например:
Затем следует получить дескриптор библиотеки, при помощи которого и определить адреса функций, например адрес функции с именем MyFunction:
Адрес функции определяется при помощи функции ::GetProcAddress, ей следует передать имя библиотеки и имя функции. Последнее должно передаваться в том виде, в котором экспортируется из DLL.
Можно также сослаться на функцию по порядковому номеру, по которому она экспортируется (при этом для создания библиотеки должен использоваться def-файл, об этом будет рассказано далее):
После завершения работы с библиотекой динамической компоновки, ее можно выгрузить из памяти процесса с помощью функции ::FreeLibrary:
Загрузка MFC-расширений динамических библиотек
При загрузке MFC-расширений для DLL (подробно о которых рассказывается далее) вместо функций LoadLibraryи FreeLibrary используются функции AfxLoadLibrary и AfxFreeLibrary. Последние почти идентичны функциям Win32 API. Они лишь гарантируют дополнительно, что структуры MFC, инициализированные расширением DLL, не были запорчены другими потоками.
Ресурсы DLL
Динамическая загрузка применима и к ресурсам DLL, используемым MFC для загрузки стандартных ресурсов приложения. Для этого сначала необходимо вызвать функцию LoadLibrary и разместить DLL в памяти. Затем с помощью функции AfxSetResourceHandle нужно подготовить окно программы к приему ресурсов из вновь загруженной библиотеки. В противном случае ресурсы будут загружаться из файлов, подключенных к выполняемому файлу процесса. Такой подход удобен, если нужно использовать различные наборы ресурсов, например для разных языков.
Замечание. С помощью функции LoadLibrary можно также загружать в память исполняемые файлы (не запускать их на выполнение!). Дескриптор выполняемого модуля может затем использоваться при обращении к функциям FindResource и LoadResource для поиска и загрузки ресурсов приложения. Выгружают модули из памяти также при помощи функции FreeLibrary.
Пример обычной DLL и способов загрузки
Приведем исходный код динамически подключаемой библиотеки, которая называется MyDLL и содержит одну функцию MyFunction, которая просто выводит сообщение.
Сначала в заголовочном файле определяется макроконтстанта EXPORT. Использование этого ключевого слова при определении некоторой функции динамически подключаемой библиотеке позволяет сообщить компоновщику, что эта функция доступна для использования другими программами, в результате чего он заносит ее в библиотеку импорта. Кроме этого, такая функция, точно так же, как и оконная процедура, должна определяться с помощью константы CALLBACK:
Файл библиотеки также несколько отличается от обычных файлов на языке C для Windows. В нем вместо функции WinMain имеется функция DllMain. Эта функция используется для выполнения инициализации, о чем будет рассказано позже. Для того, чтобы библиотека осталась после ее загрузки в памяти, и можно было вызывать ее функции, необходимо, чтобы ее возвращаемым значением было TRUE:
Пример неявного подключения DLL приложением
Приведем теперь исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll:
Эта программа выглядит как обычная программ для Windows, чем она в сущности и является. Тем не менее, следует обратить внимание, что в исходный ее текст помимо вызова функции MyFunction из DLL-библиотеки включен и заголовочный файл этой библиотеки MyDLL.h. Также необходимо на этапе компоновки приложения подключить к нему библиотеку импорта MyDLL.lib (процесс неявного подключения DLL к исполняемому модулю).
Чрезвычайно важно понимать, что сам код функции MyFunction не включается в файл MyApp.exe. Вместо этого там просто имеется ссылка на файл MyDLL.dll и ссылка на функцию MyFunction, которая находится в этом файле. Файл MyApp.exe требует запуска файла MyDLL.dll.
Заголовочный файл MyDLL.h включен в файл с исходным текстом программы MyApp.c точно так же, как туда включен файл windows.h. Включение библиотеки импорта MyDLL.lib для компоновки аналогично включению туда всех библиотек импорта Windows. Когда программа MyApp.exe работает, она подключается к библиотеке MyDLL.dll точно так же, как ко всем стандартным динамически подключаемым библиотекам Windows.
Пример динамической загрузки DLL приложением
Приведем теперь полностью исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll, используя динамическую загрузку библиотеки:
Создание DLL
Теперь, познакомившись с принципами работы библиотек DLL в приложениях, рассмотрим способы их создания. При разработке приложении функции, к которым обращается несколько процессов, желательно размещать в DLL. Это позволяет более рационально использовать память в Windows.
Проще всего создать новый проект DLL с помощью мастера AppWizard, который автоматически выполняет многие операции. Для простых DLL, таких как рассмотренные в этой главе, необходимо выбрать тип проекта Win32 Dynamic-Link Library. Новому проекту будут присвоены все необходимые параметры для создания библиотеки DLL. Файлы исходных текстов придется добавлять к проекту вручную.
Если же планируется в полной мере использовать функциональные возможности MFC, такие как документы и представления, или намерены создать сервер автоматизации OLE, лучше выбрать тип проекта MFC AppWizard (dll). В этом случае, помимо присвоения проекту параметров для подключения динамических библиотек, мастер проделает некоторую дополнительную работу. В проект будут добавлены необходимые ссылки на библиотеки MFC и файлы исходных текстов, содержащие описание и реализацию в библиотеке DLL объекта класса приложения, производного от CWinApp.
Функция DllMain
Функция DllMain вызывается в нескольких случаях. Причина ее вызова определяется параметром dwReason, который может принимать одно из следующих значений.
При первой загрузке библиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании процессом нового потока DllMainO вызывается с dwReason, равным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае dwReason равен DLL_PROCESS_ATTACH).
По окончании работы процесса с DLL функция DllMain вызывается с параметром dwReason, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) dwReason будет равен DLL_THREAD_DETACH.
Все операции по инициализации и очистке для процессов и потоков, в которых нуждается DLL, необходимо выполнять на основании значения dwReason, как было показано в предыдущем примере. Инициализация процессов обычно ограничивается выделением ресурсов, совместно используемых потоками, в частности загрузкой разделяемых файлов и инициализацией библиотек. Инициализация потоков применяется для настройки режимов, свойственных только данному потоку, например для инициализации локальной памяти.
В состав DLL могут входить ресурсы, не принадлежащие вызывающему эту библиотеку приложению. Если функции DLL работают с ресурсами DLL, было бы, очевидно, полезно сохранить где-нибудь в укромном месте дескриптор hInst и использовать его при загрузке ресурсов из DLL. Указатель IpReserved зарезервирован для внутреннего использования Windows. Следовательно, приложение не должно претендовать на него. Можно лишь проверить его значение. Если библиотека DLL была загружена динамически, оно будет равно NULL. При статической загрузке этот указатель будет ненулевым.
В случае успешного завершения функция DllMain должна возвращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются.
Замечание. Если не написать собственной функции DllMain(), компилятор подключит стандартную версию, которая просто возвращает TRUE.
Экспортирование функций из DLL
Чтобы приложение могло обращаться к функциям динамической библиотеки, каждая из них должна занимать строку в таблице экспортируемых функций DLL. Есть два способа занести функцию в эту таблицу на этапе компиляции.
Метод __declspec (dllexport)
Метод __declspec применяется не так часто, как второй метод, работающий с файлами определения модуля (.def), и позволяет лучше управлять процессом экспортирования.
Файлы определения модуля
В строке экспорта функции можно указать ее порядковый номер, поставив перед ним символ @. Этот номер будет затем использоваться при обращении к GetProcAddress (). На самом деле компилятор присваивает порядковые номера всем экспортируемым объектам. Однако способ, которым он это делает, отчасти непредсказуем, если не присвоить эти номера явно.
В строке экспорта можно использовать параметр NONAME. Он запрещает компилятору включать имя функции в таблицу экспортирования DLL:
Иногда это позволяет сэкономить много места в файле DLL. Приложения, использующие библиотеку импортирования для неявного подключения DLL, не «заметят» разницы, поскольку при неявном подключении порядковые номера используются автоматически. Приложениям, загружающим библиотеки DLL динамически, потребуется передавать в GetProcAddress порядковый номер, а не имя функции.
При использовании вышеприведенного def-файл описания экспортируемых функций DLL-библиотеки может быть,например, не таким:
Экспортирование классов
Если взглянуть на реализованный в классе файл распределения памяти, в нем можно заметить некоторые весьма необычные функции. Оказывается, здесь есть неявные конструкторы и деструкторы, функции, объявленные в макросах MFC, в частности _DECLARE_MESSAGE_MAP, а также функции, которые написанные программистом.
Хотя можно экспортировать каждую из этих функций в отдельности, есть более простой способ. Если в объявлении класса воспользоваться макромодификатором AFX_CLASS_EXPORT, компилятор сам позаботится об экспортировании необходимых функций, позволяющих приложению использовать класс, содержащийся в DLL.
Память DLL
В отличие от статических библиотек, которые, по существу, становятся частью кода приложения, библиотеки динамической компоновки в 16-разрядных версиях Windows работали с памятью несколько иначе. Под управлением Win 16 память DLL размещалась вне адресного пространства задачи. Размещение динамических библиотек в глобальной памяти обеспечивало возможность совместного использования их различными задачами.
В Win32 библиотека DLL располагается в области памяти загружающего ее процесса. Каждому процессу предоставляется отдельная копия «глобальной» памяти DLL, которая реинициализируется каждый раз, когда ее загружает новый процесс. Это означает, что динамическая библиотека не может использоваться совместно, в общей памяти, как это было в Winl6.
И все же, выполнив ряд замысловатых манипуляций над сегментом данных DLL, можно создать общую область памяти для всех процессов, использующих данную библиотеку.
Допустим, имеется массив целых чисел, который должен использоваться всеми процессами, загружающими данную DLL. Это можно запрограммировать следующим образом:
Полная компиляция DLL
Для MFC предусмотрен ряд особых режимов, касающихся использования динамической библиотекой библиотек MFC. Этому вопросу посвящен следующий раздел.
DLL и MFC
Программист не обязан использовать MFC при создании динамических библиотек. Однако использование MFC открывает ряд очень важных возможностей.
Обычные MFC DLL
Обычные MFC DLL позволяют применять MFC в динамических библиотеках. При этом приложения, обращающиеся к таким библиотекам, не обязательно должны быть построены на основе MFC. В обычных DLL можно использовать MFC любым способом, в том числе создавая в DLL новые классы на базе классов MFC и экспортируя их в приложения.
Однако обычные DLL не могут обмениваться с приложениями указателями на классы, производные от MFC.
Если приложению необходимо обмениваться с DLL указателями на объекты классов MFC или их производных, нужно использовать расширение DLL, описанное в следующем разделе.
Архитектура обычных DLL рассчитана на использование другими средами программирования, такими как Visual Basic и PowerBuilder.
Управление информацией о состоянии MFC
В каждом модуле процесса MFC содержится информация о его состоянии. Таким образом, информация о состоянии DLL отлична от информации о состоянии вызвавшего ее приложения. Поэтому любые экспортируемые из библиотеки функции, обращение к которым исходит непосредственно из приложений, должны сообщать MFC, какую информацию состояния использовать. В обычной MFC DLL, использующей динамические библиотеки MFC, перед вызовом любой подпрограммы MFC в начале экспортируемой функции нужно поместить следующую строку:
Данный оператор определяет использование соответствующей информации о состоянии во время выполнения функции, обратившейся к данной подпрограмме.
Динамические расширения MFC
MFC позволяет создавать такие библиотеки DLL, которые воспринимаются приложениями не как набор отдельных функций, а как расширения MFC. С помощью данного вида DLL можно создавать новые классы, производные от классов MFC, и использовать их в своих приложениях.
Чтобы обеспечить возможность свободного обмена указателями на объекты MFC между приложением и DLL, нужно создать динамическое расширение MFC. DLL этого типа подключаются к динамическим библиотекам MFC так же, как и любые приложения, использующие динамическое расширение MFC.
Чтобы создать новое динамическое расширение MFC, проще всего, воспользовавшись мастером приложении, присвоить проекту тип MFC AppWizard (dll) и на шаге 1 включить режим «MFC Extension DLL». В результате новому проекту будут присвоены все необходимые атрибуты динамического расширения MFC. Кроме того, будет создана функция DllMain для DLL, выполняющая ряд специфических операций по инициализации расширения DLL. Следует обратить внимание, что динамические библиотеки данного типа не содержат и не должны содержать объектов, производных от CWinApp.
Инициализация динамических расширений
Чтобы «вписаться» в структуру MFC, динамические расширения MFC требуют дополнительной начальной настройки. Соответствующие операции выполняются функцией DllMain. Рассмотрим пример этой функции, созданный мастером AppWizard.
Самой важной частью этой функции является вызов AfxInitExtensionModule. Это инициализация динамической библиотеки, позволяющая ей корректно работать в составе структуры MFC. Аргументами данной функции являются передаваемый в DllMain дескриптор библиотеки DLL и структура AFX_EXTENSION_MODULE, содержащая информацию о подключаемой к MFC динамической библиотеке.
Нет необходимости инициализировать структуру AFX_EXTENSION_MODULE явно. Однако объявить ее нужно обязательно. Инициализацией же займется конструктор CDynLinkLibrary. В DLL необходимо создать класс CDynLinkLibrary. Его конструктор не только будет инициализировать структуру AFX_EXTENSION_MODULE, но и добавит новую библиотеку в список DLL, с которыми может работать MFC.
Загрузка динамических расширений MFC
Начиная с версии 4.0 MFC позволяет динамически загружать и выгружать DLL, в том числе и расширения. Для корректного выполнения этих операций над создаваемой DLL в ее функцию DllMain в момент отключения от процесса необходимо добавить вызов AfxTermExtensionModule. Последней функции в качестве параметра передается уже использовавшаяся выше структура AFX_EXTENSION_MODULE. Для этого в текст DllMain нужно добавить следующие строки.
Кроме того, следует помнить, что новая библиотека DLL является динамическим расширением и должна загружаться и выгружаться динамически, с помощью функций AfxLoadLibrary и AfxFreeLibrary,а не LoadLibrary и FreeLibrary.
Экспортирование функций из динамических расширений
Рассмотрим теперь, как осуществляется экспортирование в приложение функций и классов из динамического расширения. Хотя добавить в DEF-файл все расширенные имена можно и вручную, лучше использовать модификаторы для объявлений экспортируемых классов и функций, такие как AFX_EXT_CLASS и AFX_EXT_API,например: